Merge branch 'stable-2.16' into stable-2.17 stable-2.17
authorBrian Foley <bpfoley@google.com>
Fri, 16 Dec 2016 17:23:46 +0000 (17:23 +0000)
committerBrian Foley <bpfoley@google.com>
Fri, 16 Dec 2016 17:40:55 +0000 (17:40 +0000)
* stable-2.15
  Fix gnt-instance console instance unpausing for xl toolstack
  Disable pylint too-many-nested-blocks in _RunCmdPipe
  Reduce nesting in import-export ProcessChildIO
  Reduce nesting in LUOobCommand.Exec
  Reduce nesting in LUInstanceCreate.RunOsScripts
  Reduce nesting in RemoveNodeSshKeyBulk key calculation
  Reduce nesting in RemoveNodeSshKeyBulk ssh logic
  Reduce nesting in gnt-cluster VerifyDisks missing disk loop
  Reduce nesting in _CheckVLANArguments
  Reduce nesting in StartDaemon
  Disable pylint bad-continuation warning
  Disable pylint superfluous-parens warning
  Disable pylint redefined-variable-type warning
  Disable pylint too-many-branches warnings
  Disable pylint broad-except warnings
  Disable incorrect pylint assigning-non-slot warning
  Quell pylint unbalanced-tuple-unpacking warning
  Cleanup: Use new-style classes everywhere
  Quell pylint socket.timeout warning
  Quell the pylint wrong-import-order warnings
  Quell cell-var-from-loop warning
  Use default value lambda param to avoid cell-var-from-loop
  Quell too-many-boolean-expressions
  Remove pylint tests removed in pylint 2.0
  Quell trailing newline
  Quell bad-whitespace warning
  Quell consider-using-enumerate warning
  Disable pylint unsubscriptable-object warning
  Disable pylint bare-except warning
  Disable unwanted pylint wrong-import-position warnings
  Disable pylint unused-wildcard-import warning
  Disable incorrect pylint not-callable warning
  Disable pylint unpacking-non-sequence warning
  Disable pylint misplaced-comparison-constant warning
  Disable incorect pylint simplify-if-statement warning
  Disable pylint eval-used warning
  Disable pylint invalid-name warning
  Disable pylint import-self warning
  Disable some pylint unused-import warnings
  Replace deprecated pylint >=0.27 pragma with new form
  Delete old warning disables removed from pylint 1.6
  Fix pylint >1.4 pycurl no-member warnings
  Cleanup: Remove unused/duplicate module/fn import
  Cleanup: Fix unidiomatic-typecheck
  Cleanup: Remove some unneeded pylint disables
  Cleanup: Iterate dict rather than key list
  Cleanup: Remove unused format key
  Cleanup: StartInstance and RebootInstance return None
  Cleanup: Fix for/else with no break in AddAuthorizedKeys
  Cleanup: Replace map/filters with list comprehensions
  Cleanup: del is a statement not a function
  Cleanup: Use FOO not in BAR instead of not FOO in BAR
  Cleanup: Simplify boolean assignment
  Cleanup: Remove some unnecessary if (...) parens
  Fix invalid variable error for file-based disks
  FIX: Refactor DiagnoseOS to use a loop, not an inner fn
  FIX: Set INSTANCE_NICn_NETWORK_NAME only if net is defined
  StartInstance restores instance state if running
  Allow migrate --cleanup to adopt an instance
  Make migrate --cleanup more robust
  Make finalize_migration_{src,dst} a single op
  Make FinalizeMigration{Src,Dst} more robust
  Fix instance state detection in _Shutdowninstance
  Code cleanup in hypervisor backend
  Fix for incorrect parsing of DRBD versions
  Fix for instance reinstall not updating config
  Change a few errors to report names, not UUIDs
  Give atomicWriteFile temp filenames a more distinct pattern
  LV check failure should print instance name
  Add ganeti-noded and ganeti-rapi --max-clients options
  Disable logging CallRPCMethod timings in non-debug configs
  568 Update hv_kvm to handle output from qemu >= 1.6.0
  Improve cluster verify ssh key errors
  Fix inconsistent spaces vs tabs indent in makefile

* stable-2.13
  Bugfix: migrate needs HypervisorClass, not an instance

Fix easy merge conflict in lib/backend.py -- dead code removed in 2.15

Signed-off-by: Brian Foley <bpfoley@google.com>
Reviewed-by: Federico Pareschi <morg@google.com>

98 files changed:
Makefile.am
autotools/build-bash-completion
autotools/build-rpc
autotools/check-imports
daemons/import-export
lib/__init__.py
lib/backend.py
lib/bootstrap.py
lib/build/sphinx_ext.py
lib/cli.py
lib/cli_opts.py
lib/client/gnt_cluster.py
lib/client/gnt_debug.py
lib/client/gnt_instance.py
lib/client/gnt_network.py
lib/client/gnt_os.py
lib/cmdlib/backup.py
lib/cmdlib/cluster/__init__.py
lib/cmdlib/cluster/verify.py
lib/cmdlib/common.py
lib/cmdlib/instance.py
lib/cmdlib/instance_create.py
lib/cmdlib/instance_migration.py
lib/cmdlib/instance_operation.py
lib/cmdlib/instance_set_params.py
lib/cmdlib/instance_storage.py
lib/cmdlib/instance_utils.py
lib/cmdlib/misc.py
lib/cmdlib/network.py
lib/cmdlib/query.py
lib/cmdlib/test.py
lib/compat.py
lib/config/__init__.py
lib/config/verify.py
lib/errors.py
lib/http/__init__.py
lib/http/auth.py
lib/http/client.py
lib/http/server.py
lib/hypervisor/hv_base.py
lib/hypervisor/hv_fake.py
lib/hypervisor/hv_kvm/__init__.py
lib/hypervisor/hv_kvm/monitor.py
lib/hypervisor/hv_lxc.py
lib/hypervisor/hv_xen.py
lib/jqueue/__init__.py
lib/luxi.py
lib/masterd/iallocator.py
lib/metad.py
lib/netutils.py
lib/objects.py
lib/ovf.py
lib/rapi/baserlib.py
lib/rapi/client.py
lib/rapi/testutils.py
lib/rpc/client.py
lib/rpc/node.py
lib/rpc/transport.py
lib/rpc_defs.py
lib/server/noded.py
lib/server/rapi.py
lib/ssh.py
lib/storage/bdev.py
lib/storage/container.py
lib/storage/drbd.py
lib/storage/drbd_info.py
lib/storage/filestorage.py
lib/tools/burnin.py
lib/tools/common.py
lib/tools/node_cleanup.py
lib/uidpool.py
lib/utils/__init__.py
lib/utils/algo.py
lib/utils/io.py
lib/utils/livelock.py
lib/utils/process.py
lib/utils/retry.py
lib/utils/security.py
lib/utils/text.py
lib/utils/x509.py
lib/watcher/__init__.py
lib/workerpool.py
man/ganeti-noded.rst
man/ganeti-rapi.rst
pylintrc
pylintrc-test
qa/qa_config.py
src/Ganeti/THH/PyRPC.hs
src/Ganeti/Utils/Atomic.hs
test/py/cmdlib/testsupport/cmdlib_testcase.py
test/py/ganeti.hypervisor.hv_kvm_unittest.py
test/py/ganeti.storage.drbd_unittest.py
test/py/testutils/config_mock.py
tools/cluster-merge
tools/confd-client
tools/ganeti-listrunner
tools/move-instance
tools/xen-console-wrapper

index cb81b6a..d71e145 100644 (file)
@@ -235,17 +235,17 @@ DIRS = \
        test/data/ovfdata \
        test/data/ovfdata/other \
        test/data/cgroup_root \
-        test/data/cgroup_root/memory \
-        test/data/cgroup_root/memory/lxc \
-        test/data/cgroup_root/memory/lxc/instance1 \
-        test/data/cgroup_root/cpuset \
-        test/data/cgroup_root/cpuset/some_group \
-        test/data/cgroup_root/cpuset/some_group/lxc \
-        test/data/cgroup_root/cpuset/some_group/lxc/instance1 \
-        test/data/cgroup_root/devices \
-        test/data/cgroup_root/devices/some_group \
-        test/data/cgroup_root/devices/some_group/lxc \
-        test/data/cgroup_root/devices/some_group/lxc/instance1 \
+       test/data/cgroup_root/memory \
+       test/data/cgroup_root/memory/lxc \
+       test/data/cgroup_root/memory/lxc/instance1 \
+       test/data/cgroup_root/cpuset \
+       test/data/cgroup_root/cpuset/some_group \
+       test/data/cgroup_root/cpuset/some_group/lxc \
+       test/data/cgroup_root/cpuset/some_group/lxc/instance1 \
+       test/data/cgroup_root/devices \
+       test/data/cgroup_root/devices/some_group \
+       test/data/cgroup_root/devices/some_group/lxc \
+       test/data/cgroup_root/devices/some_group/lxc/instance1 \
        test/py \
        test/py/testutils \
        test/py/cmdlib \
@@ -540,9 +540,9 @@ hypervisor_PYTHON = \
        lib/hypervisor/hv_xen.py
 
 hypervisor_hv_kvm_PYTHON = \
-  lib/hypervisor/hv_kvm/__init__.py \
-  lib/hypervisor/hv_kvm/monitor.py \
-  lib/hypervisor/hv_kvm/netdev.py
+       lib/hypervisor/hv_kvm/__init__.py \
+       lib/hypervisor/hv_kvm/monitor.py \
+       lib/hypervisor/hv_kvm/netdev.py
 
 jqueue_PYTHON = \
        lib/jqueue/__init__.py \
@@ -1233,11 +1233,11 @@ endif
        PYTHONPATH=. ENABLE_MANPAGES=$(ENABLE_MANPAGES) COPY_DOC=1 \
         HTML_THEME=$(SPHINX_HTML_THEME) \
        $(RUN_IN_TEMPDIR) autotools/sphinx-wrapper $(SPHINX) -q -W -b html \
-           -d . \
-           -D version="$(VERSION_MAJOR).$(VERSION_MINOR)" \
-           -D release="$(PACKAGE_VERSION)" \
-           -D graphviz_dot="$(DOT)" \
-           doc $(CURDIR)/$$dir && \
+               -d . \
+               -D version="$(VERSION_MAJOR).$(VERSION_MINOR)" \
+               -D release="$(PACKAGE_VERSION)" \
+               -D graphviz_dot="$(DOT)" \
+       doc $(CURDIR)/$$dir && \
        rm -f $$dir/.buildinfo $$dir/objects.inv
        touch $@
 
@@ -1515,10 +1515,10 @@ dist_sbin_SCRIPTS = \
 
 nodist_sbin_SCRIPTS = \
        daemons/ganeti-cleaner \
-  src/ganeti-kvmd \
-  src/ganeti-luxid \
-  src/ganeti-confd \
-  src/ganeti-wconfd
+       src/ganeti-kvmd \
+       src/ganeti-luxid \
+       src/ganeti-confd \
+       src/ganeti-wconfd
 
 src/ganeti-luxid: src/hluxid
        cp -f $< $@
@@ -1572,7 +1572,7 @@ dist_tools_python_SCRIPTS = \
 
 nodist_tools_python_SCRIPTS = \
        tools/node-cleanup \
-        $(python_scripts_shebang)
+       $(python_scripts_shebang)
 
 tools_python_basenames = \
        $(patsubst shebang/%,%,\
@@ -1652,7 +1652,7 @@ EXTRA_DIST += \
        tools/vif-ganeti-metad.in \
        tools/net-common.in \
        tools/vcluster-setup.in \
-        $(python_scripts) \
+       $(python_scripts) \
        $(docinput) \
        doc/html \
        $(BUILT_EXAMPLES:%=%.in) \
@@ -1863,9 +1863,9 @@ TEST_FILES = \
        test/data/bdev-rbd/output_invalid.txt \
        test/data/cert1.pem \
        test/data/cert2.pem \
-        test/data/cgroup_root/memory/lxc/instance1/memory.limit_in_bytes \
-        test/data/cgroup_root/cpuset/some_group/lxc/instance1/cpuset.cpus \
-        test/data/cgroup_root/devices/some_group/lxc/instance1/devices.list \
+       test/data/cgroup_root/memory/lxc/instance1/memory.limit_in_bytes \
+       test/data/cgroup_root/cpuset/some_group/lxc/instance1/cpuset.cpus \
+       test/data/cgroup_root/devices/some_group/lxc/instance1/devices.list \
        test/data/cluster_config_2.7.json \
        test/data/cluster_config_2.8.json \
        test/data/cluster_config_2.9.json \
@@ -1922,7 +1922,7 @@ TEST_FILES = \
        test/data/ovfdata/wrong_ova.ova \
        test/data/ovfdata/wrong_xml.ovf \
        test/data/proc_cgroup.txt \
-        test/data/proc_diskstats.txt \
+       test/data/proc_diskstats.txt \
        test/data/proc_drbd8.txt \
        test/data/proc_drbd80-emptyline.txt \
        test/data/proc_drbd80-emptyversion.txt \
@@ -2757,13 +2757,27 @@ PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(built_python_sources
 
 # A space-separated list of pylint warnings to completely ignore:
 # I0013 = disable warnings for ignoring whole files
-LINT_DISABLE = I0013
+# R0912 = disable too many branches warning. It's useful, but ganeti requires
+#         a lot of refactoring to fix this.
+# R0204 = disable redefined-variable-type warning. There are a large number of
+#         cases where Ganeti assigns multiple types (eg set/list, float/int) to
+#         the same variable, and these are benign.
+# C0325 = disable superfluous-parens. There are a lot of cases where this is
+#         overzealous, eg where we use parens to make it clear that we're
+#         deliberately doing a comparison that should yield bool, or are using
+#         parens clarify precedence or to allow multi-line expressions.
+# C0330 = disable wrong indentation warnings. pylint is much more strict than
+#         pep8, and it would be too invasive to fix all these.
+LINT_DISABLE = I0013 R0912 R0204 C0325 C0330
 # Additional pylint options
 LINT_OPTS =
 # The combined set of pylint options
 LINT_OPTS_ALL = $(LINT_OPTS) \
   $(addprefix --disable=,$(LINT_DISABLE))
 
+# Whitelist loading pycurl C extension for attribute checking
+LINT_OPTS_ALL += --extension-pkg-whitelist=pycurl
+
 LINT_TARGETS = pylint pylint-qa pylint-test
 if HAS_PEP8
 LINT_TARGETS += pep8
@@ -2964,7 +2978,7 @@ TAGS: $(GENERATED_FILES)
          $(filter-out -O -Werror,$(HFLAGS)) \
                -osuf tags.o \
                -hisuf tags.hi \
-    -lcurl \
+       -lcurl \
          $(HS_LIBTEST_SRCS)
        find . -path './lib/*.py' -o -path './scripts/gnt-*' -o \
          -path './daemons/ganeti-*' -o -path './tools/*' -o \
index 6a0f79f..604006d 100755 (executable)
@@ -703,8 +703,6 @@ def HaskellOptToOptParse(opts, kind):
       kept in sync
 
   """
-  # pylint: disable=W0142
-  # since we pass *opts in a number of places
   opts = opts.split(",")
   if kind == "none":
     return cli.cli_option(*opts, action="store_true")
@@ -764,8 +762,6 @@ def HaskellArgToCliArg(kind, min_cnt, max_cnt):
     max_cnt = None
   else:
     max_cnt = int(max_cnt)
-  # pylint: disable=W0142
-  # since we pass **kwargs
   kwargs = {"min": min_cnt, "max": max_cnt}
 
   if kind.startswith("choices=") or kind.startswith("suggest="):
@@ -786,18 +782,18 @@ def ParseHaskellOptsArgs(script, output):
   cli_args = []
   for line in output.splitlines():
     v = line.split(None)
-    exc = lambda msg: Exception("Invalid %s output from %s: %s" %
-                                (msg, script, v))
+    exc = lambda msg, v: Exception("Invalid %s output from %s: %s" %
+                                   (msg, script, v))
     if len(v) < 2:
-      raise exc("help completion")
+      raise exc("help completion", v)
     if v[0].startswith("-"):
       if len(v) != 2:
-        raise exc("option format")
+        raise exc("option format", v)
       (opts, kind) = v
       cli_opts.append(HaskellOptToOptParse(opts, kind))
     else:
       if len(v) != 3:
-        raise exc("argument format")
+        raise exc("argument format", v)
       (kind, min_cnt, max_cnt) = v
       cli_args.append(HaskellArgToCliArg(kind, min_cnt, max_cnt))
   return (cli_opts, cli_args)
index d3ccad6..1d59aae 100755 (executable)
@@ -212,7 +212,7 @@ def main():
     for (clsname, calls) in sorted(module.CALLS.items()):
       _WriteBaseClass(sw, clsname, calls.values())
 
-  print buf.getvalue()
+  print buf.getvalue().rstrip()
 
 
 if __name__ == "__main__":
index d82bd40..27790cf 100755 (executable)
@@ -32,8 +32,9 @@
 
 """
 
-# pylint: disable=C0103
+# pylint: disable=C0103, C0413
 # C0103: Invalid name
+# C0413: Wrong import position
 
 import sys
 
@@ -62,7 +63,7 @@ def main():
 
   for filename in args:
     # Reset global state
-    for name in sys.modules.keys():
+    for name in sys.modules:
       if name not in _STANDARD_MODULES:
         sys.modules.pop(name, None)
 
index 6c794f6..2dad4e6 100755 (executable)
@@ -323,27 +323,37 @@ def ProcessChildIO(child, socat_stderr_read_fd, dd_stderr_read_fd,
 
           # Read up to 1 KB of data
           data = from_.read(1024)
-          if data:
-            if to:
-              to.write(data)
-            elif fd == signal_notify.fileno():
-              # Signal handling
-              if signal_handler.called:
-                signal_handler.Clear()
-                if exit_timeout:
-                  logging.info("Child process still has about %0.2f seconds"
-                               " to exit", exit_timeout.Remaining())
-                else:
-                  logging.info("Giving child process %0.2f seconds to exit",
-                               constants.CHILD_LINGER_TIMEOUT)
-                  exit_timeout = \
-                    utils.RunningTimeout(constants.CHILD_LINGER_TIMEOUT, True)
-          else:
+
+          # On error, remove the mapping
+          if not data:
             poller.unregister(fd)
             del fdmap[fd]
-
-        elif event & (select.POLLNVAL | select.POLLHUP |
-                      select.POLLERR):
+            continue
+
+          # If the data needs to be sent to another fd, write it
+          if to:
+            to.write(data)
+            continue
+
+          # Did we get a signal?
+          if fd != signal_notify.fileno():
+            continue
+
+          # Has it been handled?
+          if not signal_handler.called:
+            continue
+
+          # If so, clean up after it.
+          signal_handler.Clear()
+          if exit_timeout:
+            logging.info("Child process still has about %0.2f seconds"
+                         " to exit", exit_timeout.Remaining())
+          else:
+            logging.info("Giving child process %0.2f seconds to exit",
+                         constants.CHILD_LINGER_TIMEOUT)
+            exit_timeout = \
+              utils.RunningTimeout(constants.CHILD_LINGER_TIMEOUT, True)
+        elif event & (select.POLLNVAL | select.POLLHUP | select.POLLERR):
           poller.unregister(fd)
           del fdmap[fd]
 
index 1bb4f1a..d754172 100644 (file)
@@ -33,7 +33,7 @@
 """Ganeti python modules"""
 
 try:
-  from ganeti import ganeti
+  from ganeti import ganeti # pylint: disable=W0406
 except ImportError:
   pass
 else:
index 58c8b3a..5b83290 100644 (file)
 
 
 import base64
+import contextlib
+import collections
 import errno
 import logging
 import os
 import os.path
-import pycurl
 import random
 import re
 import shutil
@@ -60,8 +61,8 @@ import stat
 import tempfile
 import time
 import zlib
-import contextlib
-import collections
+
+import pycurl
 
 from ganeti import errors
 from ganeti import http
@@ -1018,16 +1019,17 @@ def _VerifySshSetup(node_status_list, my_name, ssh_key_type,
     missing_uuids = set([])
     if pub_uuids_set != pot_mc_uuids_set:
       unknown_uuids = pub_uuids_set - pot_mc_uuids_set
+      pub_key_path = "%s:%s" % (my_name, ganeti_pub_keys_file)
       if unknown_uuids:
-        result.append("The following node UUIDs are listed in the public key"
-                      " file on node '%s', but are not potential master"
-                      " candidates: %s."
-                      % (my_name, ", ".join(list(unknown_uuids))))
+        result.append("The following node UUIDs are listed in the shared public"
+                      " keys file %s, but are not potential master"
+                      " candidates: %s." %
+                      (pub_key_path, ", ".join(list(unknown_uuids))))
       missing_uuids = pot_mc_uuids_set - pub_uuids_set
       if missing_uuids:
         result.append("The following node UUIDs of potential master candidates"
-                      " are missing in the public key file on node %s: %s."
-                      % (my_name, ", ".join(list(missing_uuids))))
+                      " are missing in the shared public keys file %s: %s." %
+                      (pub_key_path, ", ".join(list(missing_uuids))))
 
     (_, key_files) = \
       ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
@@ -1036,17 +1038,19 @@ def _VerifySshSetup(node_status_list, my_name, ssh_key_type,
     my_keys = pub_keys[my_uuid]
 
     node_pub_key = utils.ReadFile(node_pub_key_file)
+    node_pub_key_path = "%s:%s" % (my_name, node_pub_key_file)
     if node_pub_key.strip() not in my_keys:
-      result.append("The dsa key of node %s does not match this node's key"
-                    " in the pub key file." % my_name)
+      result.append("The key for node %s in the cluster config does not match"
+                    " this node's key in the node public key file %s." %
+                    (my_name, node_pub_key_path))
     if len(my_keys) != 1:
-      result.append("There is more than one key for node %s in the public key"
-                    " file." % my_name)
+      result.append("There is more than one key for node %s in the node public"
+                    " key file %s." % (my_name, node_pub_key_path))
   else:
     if len(pub_keys.keys()) > 0:
-      result.append("The public key file of node '%s' is not empty, although"
-                    " the node is not a potential master candidate."
-                    % my_name)
+      result.append("The public key file %s is not empty, although"
+                    " the node is not a potential master candidate." %
+                    node_pub_key_path)
 
   # Check that all master candidate keys are in the authorized_keys file
   (auth_key_file, _) = \
@@ -1849,9 +1853,10 @@ def RemoveNodeSshKeyBulk(node_list,
         if master_uuid:
           master_keys = ssh.QueryPubKeyFile([master_uuid],
                                             key_file=pub_key_file)
-          for master_key in master_keys:
-            if master_key in keys[node_info.uuid]:
-              keys[node_info.uuid].remove(master_key)
+
+          # Remove any master keys from the list of keys to remove from the node
+          keys[node_info.uuid] = list(
+              set(keys[node_info.uuid]) - set(master_keys))
 
       all_keys_to_remove.update(keys)
 
@@ -1914,9 +1919,13 @@ def RemoveNodeSshKeyBulk(node_list,
         error_msg_final = ("When removing the key of node '%s', updating the"
                            " SSH key files of node '%s' failed. Last error"
                            " was: %s.")
-        if node in potential_master_candidates:
-          logging.debug("Updating key setup of potential master candidate node"
-                        " %s.", node)
+
+        if node in potential_master_candidates or from_authorized_keys:
+          if node in potential_master_candidates:
+            node_desc = "potential master candidate"
+          else:
+            node_desc = "normal"
+          logging.debug("Updating key setup of %s node %s.", node_desc, node)
           try:
             backoff = 5  # seconds
             utils.RetryByNumberOfTimes(
@@ -1931,23 +1940,6 @@ def RemoveNodeSshKeyBulk(node_list,
             result_msgs.append((node, error_msg))
             logging.error(error_msg)
 
-        else:
-          if from_authorized_keys:
-            logging.debug("Updating key setup of normal node %s.", node)
-            try:
-              backoff = 5  # seconds
-              utils.RetryByNumberOfTimes(
-                  constants.SSHS_MAX_RETRIES, backoff, errors.SshUpdateError,
-                  run_cmd_fn, cluster_name, node, pathutils.SSH_UPDATE,
-                  ssh_port, base_data,
-                  debug=ssh_update_debug, verbose=ssh_update_verbose,
-                  use_cluster_key=False, ask_key=False, strict_host_check=False)
-            except errors.SshUpdateError as last_exception:
-              error_msg = error_msg_final % (
-                  node_info.name, node, last_exception)
-              result_msgs.append((node, error_msg))
-              logging.error(error_msg)
-
   for node_info in node_list:
     if node_info.clear_authorized_keys or node_info.from_public_keys or \
         node_info.clear_public_keys:
@@ -2921,15 +2913,21 @@ def StartInstance(instance, startup_paused, reason, store_reason=True):
   @rtype: None
 
   """
-  instance_info = _GetInstanceInfo(instance)
+  try:
+    instance_info = _GetInstanceInfo(instance)
+    hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
-  if instance_info and not _IsInstanceUserDown(instance_info):
-    logging.info("Instance '%s' already running, not starting", instance.name)
-    return
+    if instance_info and not _IsInstanceUserDown(instance_info):
+      logging.info("Instance '%s' already running, not starting", instance.name)
+      if hyper.VerifyInstance(instance):
+        return
+      logging.info("Instance '%s' hypervisor config out of date. Restoring.",
+                   instance.name)
+      block_devices = _GatherAndLinkBlockDevs(instance)
+      hyper.RestoreInstance(instance, block_devices)
+      return
 
-  try:
     block_devices = _GatherAndLinkBlockDevs(instance)
-    hyper = hypervisor.GetHypervisor(instance.hypervisor)
     hyper.StartInstance(instance, block_devices, startup_paused)
     if store_reason:
       _StoreInstReasonTrail(instance.name, reason)
@@ -3050,9 +3048,8 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
     try:
       InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
-      result = StartInstance(instance, False, reason, store_reason=False)
+      StartInstance(instance, False, reason, store_reason=False)
       _StoreInstReasonTrail(instance.name, reason)
-      return result
     except errors.HypervisorError, err:
       _Fail("Failed to hard reboot instance '%s': %s", instance.name, err)
   else:
index 8eb0b4c..ffa5b05 100644 (file)
@@ -679,7 +679,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
   for template, dt_params in diskparams.items():
     param_keys = set(dt_params.keys())
     default_param_keys = set(constants.DISK_DT_DEFAULTS[template].keys())
-    if not (param_keys <= default_param_keys):
+    if param_keys > default_param_keys:
       unknown_params = param_keys - default_param_keys
       raise errors.OpPrereqError("Invalid parameters for disk template %s:"
                                  " %s" % (template,
index 2150288..4ab7b29 100644 (file)
@@ -32,6 +32,9 @@
 
 """
 
+# pylint: disable=C0413
+# C0413: Wrong import position
+
 import re
 from cStringIO import StringIO
 
@@ -281,15 +284,14 @@ def PythonEvalRole(role, rawtext, text, lineno, inliner,
   The expression's result is included as a literal.
 
   """
-  # pylint: disable=W0102,W0613,W0142
+  # pylint: disable=W0102,W0613
   # W0102: Dangerous default value as argument
-  # W0142: Used * or ** magic
   # W0613: Unused argument
 
   code = docutils.utils.unescape(text, restore_backslashes=True)
 
   try:
-    result = eval(code, EVAL_NS)
+    result = eval(code, EVAL_NS) # pylint: disable=W0123
   except Exception, err: # pylint: disable=W0703
     msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err),
                                  line=lineno)
@@ -322,7 +324,7 @@ class PythonAssert(s_compat.Directive):
     code = "\n".join(self.content)
 
     try:
-      result = eval(code, EVAL_NS)
+      result = eval(code, EVAL_NS) # pylint: disable=W0123
     except Exception, err:
       raise self.error("Failed to evaluate %r: %s" % (code, err))
 
index 73c9b96..ba1e665 100644 (file)
@@ -39,7 +39,9 @@ import logging
 import errno
 import itertools
 import shlex
+
 from cStringIO import StringIO
+from optparse import (OptionParser, TitledHelpFormatter)
 
 from ganeti import utils
 from ganeti import errors
@@ -55,12 +57,10 @@ from ganeti import pathutils
 from ganeti import serializer
 import ganeti.cli_opts
 # Import constants
-from ganeti.cli_opts import *  # pylint: disable=W0401
+from ganeti.cli_opts import *  # pylint: disable=W0401,W0614
 
 from ganeti.runtime import (GetClient)
 
-from optparse import (OptionParser, TitledHelpFormatter)
-
 
 __all__ = [
   # Generic functions for CLI programs
@@ -1715,8 +1715,8 @@ def GenerateTable(headers, fields, separator, data,
   if unitfields is None:
     unitfields = []
 
-  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
-  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
+  numfields = utils.FieldSet(*numfields)
+  unitfields = utils.FieldSet(*unitfields)
 
   format_fields = []
   for field in fields:
@@ -2341,10 +2341,9 @@ def GetNodesSshPorts(nodes, cl):
   @rtype: a list of tuples
 
   """
-  return [t[0] for t in
-             cl.QueryNodes(names=nodes,
-                           fields=["ndp/ssh_port"],
-                           use_locking=False)]
+  return [t[0] for t in cl.QueryNodes(names=nodes,
+                                      fields=["ndp/ssh_port"],
+                                      use_locking=False)]
 
 
 def GetNodeUUIDs(nodes, cl):
@@ -2358,10 +2357,9 @@ def GetNodeUUIDs(nodes, cl):
   @rtype: a list of tuples
 
   """
-  return [t[0] for t in
-             cl.QueryNodes(names=nodes,
-                           fields=["uuid"],
-                           use_locking=False)]
+  return [t[0] for t in cl.QueryNodes(names=nodes,
+                                      fields=["uuid"],
+                                      use_locking=False)]
 
 
 def _ToStream(stream, txt, *args):
@@ -2781,7 +2779,7 @@ def _InitISpecsFromSplitOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count,
   else:
     forced_type = TISPECS_CLUSTER_TYPES
   for specs in ispecs_transposed.values():
-    assert type(specs) is dict
+    assert isinstance(specs, dict)
     utils.ForceDictType(specs, forced_type)
 
   # then transpose
@@ -2891,7 +2889,7 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None,
 
   split_specs = (ispecs_mem_size or ispecs_cpu_count or ispecs_disk_count or
                  ispecs_disk_size or ispecs_nic_count)
-  if (split_specs and (minmax_ispecs is not None or std_ispecs is not None)):
+  if split_specs and (minmax_ispecs is not None or std_ispecs is not None):
     raise errors.OpPrereqError("A --specs-xxx option cannot be specified"
                                " together with any --ipolicy-xxx-specs option",
                                errors.ECODE_INVAL)
@@ -2902,7 +2900,7 @@ def CreateIPolicyFromOpts(ispecs_mem_size=None,
     _InitISpecsFromSplitOpts(ipolicy_out, ispecs_mem_size, ispecs_cpu_count,
                              ispecs_disk_count, ispecs_disk_size,
                              ispecs_nic_count, group_ipolicy, fill_all)
-  elif (minmax_ispecs is not None or std_ispecs is not None):
+  elif minmax_ispecs is not None or std_ispecs is not None:
     _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs,
                             group_ipolicy, allowed_values)
 
@@ -2932,7 +2930,7 @@ def _NotAContainer(data):
   @rtype: bool
 
   """
-  return not (isinstance(data, (list, dict, tuple)))
+  return not isinstance(data, (list, dict, tuple))
 
 
 def _GetAlignmentMapping(data):
index 19a497d..76e7cab 100644 (file)
@@ -31,6 +31,9 @@
 """Module containing Ganeti's command line parsing options"""
 
 import re
+
+from optparse import (Option, OptionValueError)
+
 import simplejson
 
 from ganeti import utils
@@ -40,8 +43,6 @@ from ganeti import compat
 from ganeti import pathutils
 from ganeti import serializer
 
-from optparse import (Option, OptionValueError)
-
 
 __all__ = [
   "ABSOLUTE_OPT",
@@ -560,7 +561,7 @@ class CliOption(Option):
 
 
 # optparse.py sets make_option, so we do it for our own option class, too
-cli_option = CliOption
+cli_option = CliOption # pylint: disable=C0103
 
 
 _YORNO = "yes|no"
index 83dfca2..9d135ad 100644 (file)
 # W0614: Unused import %s from wildcard import (since we need cli)
 # C0103: Invalid name gnt-cluster
 
-from cStringIO import StringIO
+import itertools
 import os
 import time
-import OpenSSL
 import tempfile
-import itertools
+
+from cStringIO import StringIO
+
+import OpenSSL
 
 from ganeti.cli import *
 from ganeti import bootstrap
@@ -203,7 +205,7 @@ def InitCluster(opts, args):
   # check the disk template types here, as we cannot rely on the type check done
   # by the opcode parameter types
   diskparams_keys = set(diskparams.keys())
-  if not (diskparams_keys <= constants.DISK_TEMPLATES):
+  if diskparams_keys > constants.DISK_TEMPLATES:
     unknown = utils.NiceSort(diskparams_keys - constants.DISK_TEMPLATES)
     ToStderr("Disk templates unknown: %s" % utils.CommaJoin(unknown))
     return 1
@@ -305,10 +307,7 @@ def InitCluster(opts, args):
 
   default_ialloc_params = opts.default_iallocator_params
 
-  if opts.enabled_user_shutdown:
-    enabled_user_shutdown = True
-  else:
-    enabled_user_shutdown = False
+  enabled_user_shutdown = bool(opts.enabled_user_shutdown)
 
   if opts.ssh_key_type:
     ssh_key_type = opts.ssh_key_type
@@ -848,14 +847,15 @@ def VerifyDisks(opts, args):
         if all_missing:
           ToStdout("Instance %s cannot be verified as it lives on"
                    " broken nodes", iname)
-        else:
-          ToStdout("Instance %s has missing logical volumes:", iname)
-          ival.sort()
-          for node, vol in ival:
-            if node in bad_nodes:
-              ToStdout("\tbroken node %s /dev/%s", node, vol)
-            else:
-              ToStdout("\t%s /dev/%s", node, vol)
+          continue
+
+        ToStdout("Instance %s has missing logical volumes:", iname)
+        ival.sort()
+        for node, vol in ival:
+          if node in bad_nodes:
+            ToStdout("\tbroken node %s /dev/%s", node, vol)
+          else:
+            ToStdout("\t%s /dev/%s", node, vol)
 
       ToStdout("You need to replace or recreate disks for all the above"
                " instances if this message persists after fixing broken nodes.")
index 48a557f..b0affa3 100644 (file)
 # W0614: Unused import %s from wildcard import (since we need cli)
 # C0103: Invalid name gnt-backup
 
-import simplejson
-import time
-import socket
 import logging
+import socket
+import time
+
+import simplejson
 
 from ganeti.cli import *
 from ganeti import cli
@@ -103,7 +104,6 @@ def GenericOpCodes(opts, args):
     ToStdout("Loading...")
   for job_idx in range(opts.rep_job):
     for fname in args:
-      # pylint: disable=W0142
       op_data = simplejson.loads(utils.ReadFile(fname))
       op_list = [opcodes.OpCode.LoadOpCode(val) for val in op_data]
       op_list = op_list * opts.rep_op
index dd1013f..e86c9b9 100644 (file)
 
 import copy
 import itertools
-import simplejson
 import logging
 
+import simplejson
+
 from ganeti.cli import *
 from ganeti import opcodes
 from ganeti import constants
@@ -101,7 +102,6 @@ def _ExpandMultiNames(mode, names, client=None):
   @raise errors.OpPrereqError: for invalid input parameters
 
   """
-  # pylint: disable=W0142
 
   if client is None:
     client = GetClient()
@@ -300,7 +300,7 @@ def BatchCreate(opts, args):
                                  (idx, utils.CommaJoin(unknown)),
                                  errors.ECODE_INVAL)
 
-    op = opcodes.OpInstanceCreate(**inst) # pylint: disable=W0142
+    op = opcodes.OpInstanceCreate(**inst)
     op.Validate(False)
     instances.append(op)
 
index 09c1121..41348c4 100644 (file)
@@ -303,7 +303,6 @@ def SetNetworkParams(opts, args):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
-  # pylint: disable=W0142
   op = opcodes.OpNetworkSetParams(network_name=args[0], **all_changes)
 
   # TODO: add feedback to user, e.g. list the modifications
index a3c82b9..df69f03 100644 (file)
@@ -233,13 +233,6 @@ def DiagnoseOS(opts, args):
       status = "partial valid"
       has_bad = True
 
-    def _OutputPerNodeOSStatus(msg_map):
-      map_k = utils.NiceSort(msg_map.keys())
-      for node_name in map_k:
-        ToStdout("  Node: %s, status: %s", node_name, msg_map[node_name])
-        for msg in nodes_hidden[node_name]:
-          ToStdout(msg)
-
     st_msg = "OS: %s [global status: %s]" % (os_name, status)
     if hid:
       st_msg += " [hidden]"
@@ -248,8 +241,13 @@ def DiagnoseOS(opts, args):
     ToStdout(st_msg)
     if os_variants:
       ToStdout("  Variants: [%s]" % utils.CommaJoin(os_variants))
-    _OutputPerNodeOSStatus(nodes_valid)
-    _OutputPerNodeOSStatus(nodes_bad)
+
+    for msg_map in (nodes_valid, nodes_bad):
+      map_k = utils.NiceSort(msg_map.keys())
+      for node_name in map_k:
+        ToStdout("  Node: %s, status: %s", node_name, msg_map[node_name])
+        for msg in nodes_hidden[node_name]:
+          ToStdout(msg)
     ToStdout("")
 
   return int(has_bad)
index 84ceecf..88e0f0e 100644 (file)
 
 """Logical units dealing with backup operations."""
 
-import OpenSSL
 import logging
 
+import OpenSSL
+
 from ganeti import compat
 from ganeti import constants
 from ganeti import errors
index 76b50f1..21200f3 100644 (file)
@@ -67,7 +67,7 @@ from ganeti.cmdlib.common import ShareAll, RunPostHook, \
   CheckIpolicyVsDiskTemplates, CheckDiskAccessModeValidity, \
   CheckDiskAccessModeConsistency, GetClientCertDigest, \
   AddInstanceCommunicationNetworkOp, ConnectInstanceCommunicationNetworkOp, \
-  CheckImageValidity, CheckDiskAccessModeConsistency, EnsureKvmdOnNodes
+  CheckImageValidity, EnsureKvmdOnNodes
 
 import ganeti.masterd.instance
 
index 8c68039..f27c33d 100644 (file)
@@ -141,7 +141,6 @@ class _VerifyErrors(object):
     # Report messages via the feedback_fn
     # pylint: disable=E1101
     self._feedback_fn(constants.ELOG_MESSAGE_LIST, prefixed_list)
-    # pylint: enable=E1101
 
     # do not mark the operation as failed for WARN cases only
     if log_type == self.ETYPE_ERROR:
@@ -210,7 +209,7 @@ class LUClusterVerify(NoHooksLU):
       for group in groups)
 
     # Fix up all parameters
-    for op in itertools.chain(*jobs): # pylint: disable=W0142
+    for op in itertools.chain(*jobs):
       op.debug_simulate_errors = self.op.debug_simulate_errors
       op.verbose = self.op.verbose
       op.error_codes = self.op.error_codes
@@ -514,7 +513,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     # We detect here the nodes that will need the extra RPC calls for verifying
     # split LV volumes; they should be locked.
-    extra_lv_nodes = set()
+    extra_lv_nodes = {}
 
     for inst in self.my_inst_info.values():
       disks = self.cfg.GetInstanceDisks(inst.uuid)
@@ -522,16 +521,23 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         inst_nodes = self.cfg.GetInstanceNodes(inst.uuid)
         for nuuid in inst_nodes:
           if self.all_node_info[nuuid].group != self.group_uuid:
-            extra_lv_nodes.add(nuuid)
+            if nuuid in extra_lv_nodes:
+              extra_lv_nodes[nuuid].append(inst.name)
+            else:
+              extra_lv_nodes[nuuid] = [inst.name]
 
+    extra_lv_nodes_set = set(extra_lv_nodes.iterkeys())
     unlocked_lv_nodes = \
-        extra_lv_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
+        extra_lv_nodes_set.difference(self.owned_locks(locking.LEVEL_NODE))
 
     if unlocked_lv_nodes:
+      node_strings = ['%s: [%s]' % (
+          self.cfg.GetNodeName(node), utils.CommaJoin(extra_lv_nodes[node]))
+            for node in unlocked_lv_nodes]
       raise errors.OpPrereqError("Missing node locks for LV check: %s" %
-                                 utils.CommaJoin(unlocked_lv_nodes),
+                                 utils.CommaJoin(node_strings),
                                  errors.ECODE_STATE)
-    self.extra_lv_nodes = list(extra_lv_nodes)
+    self.extra_lv_nodes = list(extra_lv_nodes_set)
 
   def _VerifyNode(self, ninfo, nresult):
     """Perform some basic validation on data returned from a node.
@@ -705,7 +711,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     # exclusive_storage wants all PVs to have the same size (approximately),
     # if the smallest and the biggest ones are okay, everything is fine.
     # pv_min is None iff pv_max is None
-    vals = filter((lambda ni: ni.pv_min is not None), node_image.values())
+    vals = [ni for ni in node_image.values() if ni.pv_min is not None]
     if not vals:
       return
     (pvmin, minnode_uuid) = min((ni.pv_min, ni.uuid) for ni in vals)
index 6ee86b9..b6e673e 100644 (file)
@@ -125,10 +125,11 @@ def CheckNodeGroupInstances(cfg, group_uuid, owned_instance_names):
   wanted_instances = frozenset(cfg.GetInstanceNames(
                                  cfg.GetNodeGroupInstances(group_uuid)))
   if owned_instance_names != wanted_instances:
+    group_name = cfg.GetNodeGroup(group_uuid).name
     raise errors.OpPrereqError("Instances in node group '%s' changed since"
                                " locks were acquired, wanted '%s', have '%s';"
                                " retry the operation" %
-                               (group_uuid,
+                               (group_name,
                                 utils.CommaJoin(wanted_instances),
                                 utils.CommaJoin(owned_instance_names)),
                                errors.ECODE_STATE)
@@ -1588,15 +1589,15 @@ def EnsureKvmdOnNodes(lu, feedback_fn, nodes=None):
   if start_nodes:
     results = lu.rpc.call_node_ensure_daemon(start_nodes, constants.KVMD, True)
     for node_uuid in start_nodes:
-      results[node_uuid].Warn("Failed to start KVM daemon in node '%s'" %
-                              node_uuid, feedback_fn)
+      results[node_uuid].Warn("Failed to start KVM daemon on node '%s'" %
+                              lu.cfg.GetNodeName(node_uuid), feedback_fn)
 
   # Stop KVM where necessary
   if stop_nodes:
     results = lu.rpc.call_node_ensure_daemon(stop_nodes, constants.KVMD, False)
     for node_uuid in stop_nodes:
-      results[node_uuid].Warn("Failed to stop KVM daemon in node '%s'" %
-                              node_uuid, feedback_fn)
+      results[node_uuid].Warn("Failed to stop KVM daemon on node '%s'" %
+                              lu.cfg.GetNodeName(node_uuid), feedback_fn)
 
 
 def WarnAboutFailedSshUpdates(result, master_uuid, feedback_fn):
index 4f46910..d20aa81 100644 (file)
@@ -179,7 +179,7 @@ class LUInstanceRename(LogicalUnit):
     renamed_storage = [d for d in disks
                        if (d.dev_type in constants.DTS_FILEBASED and
                            d.dev_type != constants.DT_GLUSTER)]
-    if (renamed_storage and self.op.new_name != self.instance.name):
+    if renamed_storage and self.op.new_name != self.instance.name:
       disks = self.cfg.GetInstanceDisks(self.instance.uuid)
       old_file_storage_dir = os.path.dirname(disks[0].logical_id[1])
       rename_file_storage = True
@@ -658,7 +658,7 @@ class LUInstanceMultiAlloc(NoHooksLU):
 
     """
     if self.op.iallocator:
-      (allocatable, failed_insts) = self.ia_result
+      (allocatable, failed_insts) = self.ia_result # pylint: disable=W0633
       allocatable_insts = map(compat.fst, allocatable)
     else:
       allocatable_insts = [op.instance_name for op in self.op.instances]
@@ -676,7 +676,7 @@ class LUInstanceMultiAlloc(NoHooksLU):
     jobs = []
     if self.op.iallocator:
       op2inst = dict((op.instance_name, op) for op in self.op.instances)
-      (allocatable, failed) = self.ia_result
+      (allocatable, failed) = self.ia_result # pylint: disable=W0633
 
       for (name, node_names) in allocatable:
         op = op2inst.pop(name)
index 4b2c6ee..445e9c8 100644 (file)
 
 """Logical unit for creating a single instance."""
 
-import OpenSSL
 import logging
 import os
 
+import OpenSSL
 
 from ganeti import compat
 from ganeti import constants
@@ -77,6 +77,12 @@ from ganeti.cmdlib.instance_utils import \
 import ganeti.masterd.instance
 
 
+def _ValidateTrunkVLAN(vlan):
+  if not compat.all(vl.isdigit() for vl in vlan[1:].split(':')):
+    raise errors.OpPrereqError("Specified VLAN parameter is invalid"
+                               " : %s" % vlan, errors.ECODE_INVAL)
+
+
 class LUInstanceCreate(LogicalUnit):
   """Create an instance.
 
@@ -113,7 +119,7 @@ class LUInstanceCreate(LogicalUnit):
     for disk in self.op.disks:
       if self.op.disk_template != constants.DT_EXT:
         utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
-      if constants.IDISK_ADOPT in disk:
+      if constants.IDISK_ADOPT in disk: # pylint: disable=R0102
         has_adopt = True
       else:
         has_no_adopt = True
@@ -152,19 +158,10 @@ class LUInstanceCreate(LogicalUnit):
           # vlan starting with dot means single untagged vlan,
           # might be followed by trunk (:)
           if not vlan[1:].isdigit():
-            vlanlist = vlan[1:].split(':')
-            for vl in vlanlist:
-              if not vl.isdigit():
-                raise errors.OpPrereqError("Specified VLAN parameter is "
-                                           "invalid : %s" % vlan,
-                                             errors.ECODE_INVAL)
+            _ValidateTrunkVLAN(vlan)
         elif vlan[0] == ":":
           # Trunk - tagged only
-          vlanlist = vlan[1:].split(':')
-          for vl in vlanlist:
-            if not vl.isdigit():
-              raise errors.OpPrereqError("Specified VLAN parameter is invalid"
-                                           " : %s" % vlan, errors.ECODE_INVAL)
+          _ValidateTrunkVLAN(vlan)
         elif vlan.isdigit():
           # This is the simplest case. No dots, only single digit
           # -> Create untagged access port, dot needs to be added
@@ -473,8 +470,8 @@ class LUInstanceCreate(LogicalUnit):
                                  (self.op.iallocator, ial.info),
                                  ecode)
 
-    (self.op.pnode_uuid, self.op.pnode) = \
-      ExpandNodeUuidAndName(self.cfg, None, ial.result[0])
+    (self.op.pnode_uuid, self.op.pnode) = ExpandNodeUuidAndName(
+        self.cfg, None, ial.result[0]) # pylint: disable=E1136
     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
                  self.op.instance_name, self.op.iallocator,
                  utils.CommaJoin(ial.result))
@@ -482,8 +479,8 @@ class LUInstanceCreate(LogicalUnit):
     assert req.RequiredNodes() in (1, 2), "Wrong node count from iallocator"
 
     if req.RequiredNodes() == 2:
-      (self.op.snode_uuid, self.op.snode) = \
-        ExpandNodeUuidAndName(self.cfg, None, ial.result[1])
+      (self.op.snode_uuid, self.op.snode) = ExpandNodeUuidAndName(
+          self.cfg, None, ial.result[1]) # pylint: disable=E1136
 
   def BuildHooksEnv(self):
     """Build hooks env.
@@ -1199,122 +1196,127 @@ class LUInstanceCreate(LogicalUnit):
     @param iobj: instance object
 
     """
-    if iobj.disks and not self.adopt_disks:
-      disks = self.cfg.GetInstanceDisks(iobj.uuid)
-      if self.op.mode == constants.INSTANCE_CREATE:
-        os_image = objects.GetOSImage(self.op.osparams)
-
-        if os_image is None and not self.op.no_install:
-          pause_sync = (not self.op.wait_for_sync and
-                        utils.AnyDiskOfType(disks, constants.DTS_INT_MIRROR))
-          if pause_sync:
-            feedback_fn("* pausing disk sync to install instance OS")
-            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
-                                                              (disks, iobj),
-                                                              True)
-            for idx, success in enumerate(result.payload):
-              if not success:
-                logging.warn("pause-sync of instance %s for disk %d failed",
-                             self.op.instance_name, idx)
-
-          feedback_fn("* running the instance OS create scripts...")
+    if not iobj.disks:
+      return
+
+    if self.adopt_disks:
+      return
+
+    disks = self.cfg.GetInstanceDisks(iobj.uuid)
+    if self.op.mode == constants.INSTANCE_CREATE:
+      os_image = objects.GetOSImage(self.op.osparams)
+
+      if os_image is None and not self.op.no_install:
+        pause_sync = (not self.op.wait_for_sync and
+                      utils.AnyDiskOfType(disks, constants.DTS_INT_MIRROR))
+        if pause_sync:
+          feedback_fn("* pausing disk sync to install instance OS")
+          result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
+                                                            (disks, iobj),
+                                                            True)
+          for idx, success in enumerate(result.payload):
+            if not success:
+              logging.warn("pause-sync of instance %s for disk %d failed",
+                           self.op.instance_name, idx)
+
+        feedback_fn("* running the instance OS create scripts...")
+        # FIXME: pass debug option from opcode to backend
+        os_add_result = \
+          self.rpc.call_instance_os_add(self.pnode.uuid,
+                                        (iobj, self.op.osparams_secret),
+                                        False,
+                                        self.op.debug_level)
+        if pause_sync:
+          feedback_fn("* resuming disk sync")
+          result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
+                                                            (disks, iobj),
+                                                            False)
+          for idx, success in enumerate(result.payload):
+            if not success:
+              logging.warn("resume-sync of instance %s for disk %d failed",
+                           self.op.instance_name, idx)
+
+        os_add_result.Raise("Could not add os for instance %s"
+                            " on node %s" % (self.op.instance_name,
+                                             self.pnode.name))
+
+    else:
+      if self.op.mode == constants.INSTANCE_IMPORT:
+        feedback_fn("* running the instance OS import scripts...")
+
+        transfers = []
+
+        for idx, image in enumerate(self.src_images):
+          if not image:
+            continue
+
+          if iobj.os:
+            dst_io = constants.IEIO_SCRIPT
+            dst_ioargs = ((disks[idx], iobj), idx)
+          else:
+            dst_io = constants.IEIO_RAW_DISK
+            dst_ioargs = (disks[idx], iobj)
+
           # FIXME: pass debug option from opcode to backend
-          os_add_result = \
-            self.rpc.call_instance_os_add(self.pnode.uuid,
-                                          (iobj, self.op.osparams_secret),
-                                          False,
-                                          self.op.debug_level)
-          if pause_sync:
-            feedback_fn("* resuming disk sync")
-            result = self.rpc.call_blockdev_pause_resume_sync(self.pnode.uuid,
-                                                              (disks, iobj),
-                                                              False)
-            for idx, success in enumerate(result.payload):
-              if not success:
-                logging.warn("resume-sync of instance %s for disk %d failed",
-                             self.op.instance_name, idx)
-
-          os_add_result.Raise("Could not add os for instance %s"
-                              " on node %s" % (self.op.instance_name,
-                                               self.pnode.name))
+          dt = masterd.instance.DiskTransfer("disk/%s" % idx,
+                                             constants.IEIO_FILE, (image, ),
+                                             dst_io, dst_ioargs,
+                                             None)
+          transfers.append(dt)
+
+        import_result = \
+          masterd.instance.TransferInstanceData(self, feedback_fn,
+                                                self.op.src_node_uuid,
+                                                self.pnode.uuid,
+                                                self.pnode.secondary_ip,
+                                                self.op.compress,
+                                                iobj, transfers)
+        if not compat.all(import_result):
+          self.LogWarning("Some disks for instance %s on node %s were not"
+                          " imported successfully" % (self.op.instance_name,
+                                                      self.pnode.name))
+
+        rename_from = self._old_instance_name
+
+      elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
+        feedback_fn("* preparing remote import...")
+        # The source cluster will stop the instance before attempting to make
+        # a connection. In some cases stopping an instance can take a long
+        # time, hence the shutdown timeout is added to the connection
+        # timeout.
+        connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
+                           self.op.source_shutdown_timeout)
+        timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
+
+        assert iobj.primary_node == self.pnode.uuid
+        disk_results = \
+          masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
+                                        self.source_x509_ca,
+                                        self._cds, self.op.compress, timeouts)
+        if not compat.all(disk_results):
+          # TODO: Should the instance still be started, even if some disks
+          # failed to import (valid for local imports, too)?
+          self.LogWarning("Some disks for instance %s on node %s were not"
+                          " imported successfully" % (self.op.instance_name,
+                                                      self.pnode.name))
+
+        rename_from = self.source_instance_name
 
       else:
-        if self.op.mode == constants.INSTANCE_IMPORT:
-          feedback_fn("* running the instance OS import scripts...")
-
-          transfers = []
-
-          for idx, image in enumerate(self.src_images):
-            if not image:
-              continue
-
-            if iobj.os:
-              dst_io = constants.IEIO_SCRIPT
-              dst_ioargs = ((disks[idx], iobj), idx)
-            else:
-              dst_io = constants.IEIO_RAW_DISK
-              dst_ioargs = (disks[idx], iobj)
-
-            # FIXME: pass debug option from opcode to backend
-            dt = masterd.instance.DiskTransfer("disk/%s" % idx,
-                                               constants.IEIO_FILE, (image, ),
-                                               dst_io, dst_ioargs,
-                                               None)
-            transfers.append(dt)
-
-          import_result = \
-            masterd.instance.TransferInstanceData(self, feedback_fn,
-                                                  self.op.src_node_uuid,
-                                                  self.pnode.uuid,
-                                                  self.pnode.secondary_ip,
-                                                  self.op.compress,
-                                                  iobj, transfers)
-          if not compat.all(import_result):
-            self.LogWarning("Some disks for instance %s on node %s were not"
-                            " imported successfully" % (self.op.instance_name,
-                                                        self.pnode.name))
-
-          rename_from = self._old_instance_name
-
-        elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
-          feedback_fn("* preparing remote import...")
-          # The source cluster will stop the instance before attempting to make
-          # a connection. In some cases stopping an instance can take a long
-          # time, hence the shutdown timeout is added to the connection
-          # timeout.
-          connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
-                             self.op.source_shutdown_timeout)
-          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
-
-          assert iobj.primary_node == self.pnode.uuid
-          disk_results = \
-            masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
-                                          self.source_x509_ca,
-                                          self._cds, self.op.compress, timeouts)
-          if not compat.all(disk_results):
-            # TODO: Should the instance still be started, even if some disks
-            # failed to import (valid for local imports, too)?
-            self.LogWarning("Some disks for instance %s on node %s were not"
-                            " imported successfully" % (self.op.instance_name,
-                                                        self.pnode.name))
-
-          rename_from = self.source_instance_name
-
-        else:
-          # also checked in the prereq part
-          raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
-                                       % self.op.mode)
-
-        assert iobj.name == self.op.instance_name
-
-        # Run rename script on newly imported instance
-        if iobj.os:
-          feedback_fn("Running rename script for %s" % self.op.instance_name)
-          result = self.rpc.call_instance_run_rename(self.pnode.uuid, iobj,
-                                                     rename_from,
-                                                     self.op.debug_level)
-          result.Warn("Failed to run rename script for %s on node %s" %
-                      (self.op.instance_name, self.pnode.name), self.LogWarning)
+        # also checked in the prereq part
+        raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
+                                     % self.op.mode)
+
+      assert iobj.name == self.op.instance_name
+
+      # Run rename script on newly imported instance
+      if iobj.os:
+        feedback_fn("Running rename script for %s" % self.op.instance_name)
+        result = self.rpc.call_instance_run_rename(self.pnode.uuid, iobj,
+                                                   rename_from,
+                                                   self.op.debug_level)
+        result.Warn("Failed to run rename script for %s on node %s" %
+                    (self.op.instance_name, self.pnode.name), self.LogWarning)
 
   def GetOsInstallPackageEnvironment(self, instance, script):
     """Returns the OS scripts environment for the helper VM
index b93b334..357994c 100644 (file)
@@ -499,7 +499,8 @@ class TLMigrateInstance(Tasklet):
                                  " iallocator '%s': %s" %
                                  (self.lu.op.iallocator, ial.info),
                                  errors.ECODE_NORES)
-    self.target_node_uuid = self.cfg.GetNodeInfoByName(ial.result[0]).uuid
+    self.target_node_uuid = self.cfg.GetNodeInfoByName(
+        ial.result[0]).uuid # pylint: disable=E1136
     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
                     self.instance_name, self.lu.op.iallocator,
                     utils.CommaJoin(ial.result))
@@ -598,12 +599,58 @@ class TLMigrateInstance(Tasklet):
       nres.Raise("Cannot change disks config on node %s" %
                  self.cfg.GetNodeName(node_uuid))
 
+  def _FindInstanceLocations(self, name):
+    """Returns a list of nodes that have the given instance running
+
+    Args:
+      name: string, instance name string to search for
+
+    Returns:
+      list of strings, node uuids
+    """
+    self.feedback_fn("* checking where the instance actually runs (if this"
+                     " hangs, the hypervisor might be in a bad state)")
+
+    cluster_hvparams = self.cfg.GetClusterInfo().hvparams
+    online_node_uuids = self.cfg.GetOnlineNodeList()
+    instance_list = self.rpc.call_instance_list(
+        online_node_uuids, [self.instance.hypervisor], cluster_hvparams)
+
+    # Verify each result and raise an exception if failed
+    for node_uuid, result in instance_list.items():
+      result.Raise("Can't contact node %s" % self.cfg.GetNodeName(node_uuid))
+
+    # Xen renames the instance during migration, unfortunately we don't have
+    # a nicer way of identifying that it's the same instance. This is an awful
+    # leaking abstraction.
+
+    # xm and xl have different (undocumented) naming conventions
+    # xm: (in tools/python/xen/xend/XendCheckpoint.py save() & restore())
+    #                   source dom name    target dom name
+    # during copy:      migrating-$DOM     $DOM
+    # finalize migrate: <none>             $DOM
+    # finished:         <none>             $DOM
+    #
+    # xl: (in tools/libxl/xl_cmdimpl.c migrate_domain() & migrate_receive())
+    #                   source dom name    target dom name
+    # during copy:      $DOM               $DOM--incoming
+    # finalize migrate: $DOM--migratedaway $DOM
+    # finished:         <none>             $DOM
+    variants = [
+        name, 'migrating-' + name, name + '--incoming', name + '--migratedaway']
+    node_uuids = [node for node, data in instance_list.items()
+                  if any(var in data.payload for var in variants)]
+    self.feedback_fn("* instance running on: %s" % ','.join(
+        self.cfg.GetNodeName(uuid) for uuid in node_uuids))
+    return node_uuids
+
   def _ExecCleanup(self):
     """Try to cleanup after a failed migration.
 
     The cleanup is done by:
       - check that the instance is running only on one node
-        (and update the config if needed)
+      - try 'aborting' migration if it is running on two nodes
+      - update the config if needed
       - change disks on its secondary node to secondary
       - wait until disks are fully synchronized
       - disconnect from the network
@@ -611,33 +658,55 @@ class TLMigrateInstance(Tasklet):
       - wait again until disks are fully synchronized
 
     """
-    # check running on only one node
-    self.feedback_fn("* checking where the instance actually runs"
-                     " (if this hangs, the hypervisor might be in"
-                     " a bad state)")
-    cluster_hvparams = self.cfg.GetClusterInfo().hvparams
-    ins_l = self.rpc.call_instance_list(self.all_node_uuids,
-                                        [self.instance.hypervisor],
-                                        cluster_hvparams)
-    for node_uuid, result in ins_l.items():
-      result.Raise("Can't contact node %s" % node_uuid)
-
-    runningon_source = self.instance.name in \
-                         ins_l[self.source_node_uuid].payload
-    runningon_target = self.instance.name in \
-                         ins_l[self.target_node_uuid].payload
+    instance_locations = self._FindInstanceLocations(self.instance.name)
+    runningon_source = self.source_node_uuid in instance_locations
+    runningon_target = self.target_node_uuid in instance_locations
 
     if runningon_source and runningon_target:
+      # If we have an instance on both the source and the destination, we know
+      # that instance migration was interrupted in the middle, we can try to
+      # do effectively the same as when aborting an interrupted migration.
+      self.feedback_fn("Trying to cleanup after failed migration")
+      result = self.rpc.call_migration_info(
+          self.source_node_uuid, self.instance)
+      if result.fail_msg:
+        raise errors.OpExecError(
+            "Failed fetching source migration information from %s: %s" %
+            (self.cfg.GetNodeName(self.source_node_uuid), result.fail_msg))
+      self.migration_info = result.payload
+      abort_results = self._AbortMigration()
+
+      if abort_results[0].fail_msg or abort_results[1].fail_msg:
+        raise errors.OpExecError(
+            "Instance migration cleanup failed: %s" % ','.join([
+                abort_results[0].fail_msg, abort_results[1].fail_msg]))
+
+      # AbortMigration() should have fixed instance locations, so query again
+      instance_locations = self._FindInstanceLocations(self.instance.name)
+      runningon_source = self.source_node_uuid in instance_locations
+      runningon_target = self.target_node_uuid in instance_locations
+
+    # Abort didn't work, manual intervention required
+    if runningon_source and runningon_target:
       raise errors.OpExecError("Instance seems to be running on two nodes,"
                                " or the hypervisor is confused; you will have"
                                " to ensure manually that it runs only on one"
                                " and restart this operation")
 
     if not (runningon_source or runningon_target):
-      raise errors.OpExecError("Instance does not seem to be running at all;"
-                               " in this case it's safer to repair by"
-                               " running 'gnt-instance stop' to ensure disk"
-                               " shutdown, and then restarting it")
+      if len(instance_locations) == 1:
+        # The instance is running on a differrent node than expected, let's
+        # adopt it as if it was running on the secondary
+        self.target_node_uuid = instance_locations[0]
+        self.feedback_fn("* instance running on unexpected node (%s),"
+                         " updating as the new secondary" %
+                         self.cfg.GetNodeName(self.target_node_uuid))
+        runningon_target = True
+      else:
+        raise errors.OpExecError("Instance does not seem to be running at all;"
+                                 " in this case it's safer to repair by"
+                                 " running 'gnt-instance stop' to ensure disk"
+                                 " shutdown, and then restarting it")
 
     if runningon_target:
       # the migration has actually succeeded, we need to update the config
@@ -655,6 +724,7 @@ class TLMigrateInstance(Tasklet):
 
     disks = self.cfg.GetInstanceDisks(self.instance.uuid)
 
+    # TODO: Cleanup code duplication of _RevertDiskStatus()
     self._CloseInstanceDisks(demoted_node_uuid)
 
     if utils.AnyDiskOfType(disks, constants.DTS_INT_MIRROR):
@@ -697,24 +767,27 @@ class TLMigrateInstance(Tasklet):
   def _AbortMigration(self):
     """Call the hypervisor code to abort a started migration.
 
+    Returns:
+      tuple of rpc call results
     """
-    abort_result = self.rpc.call_instance_finalize_migration_dst(
-                     self.target_node_uuid, self.instance, self.migration_info,
-                     False)
-    abort_msg = abort_result.fail_msg
+    src_result = self.rpc.call_instance_finalize_migration_dst(
+        self.target_node_uuid, self.instance, self.migration_info, False)
+    abort_msg = src_result.fail_msg
     if abort_msg:
       logging.error("Aborting migration failed on target node %s: %s",
                     self.cfg.GetNodeName(self.target_node_uuid), abort_msg)
-      # Don't raise an exception here, as we stil have to try to revert the
-      # disk status, even if this step failed.
 
-    abort_result = self.rpc.call_instance_finalize_migration_src(
-      self.source_node_uuid, self.instance, False, self.live)
-    abort_msg = abort_result.fail_msg
+    # Don't raise an exception here, as we stil have to try to revert the
+    # disk status, even if this step failed.
+    dst_result = self.rpc.call_instance_finalize_migration_src(
+        self.source_node_uuid, self.instance, False, self.live)
+    abort_msg = dst_result.fail_msg
     if abort_msg:
       logging.error("Aborting migration failed on source node %s: %s",
                     self.cfg.GetNodeName(self.source_node_uuid), abort_msg)
 
+    return src_result, dst_result
+
   def _ExecMigration(self):
     """Migrate an instance.
 
@@ -746,7 +819,7 @@ class TLMigrateInstance(Tasklet):
         self.feedback_fn("* warning: hypervisor version mismatch between"
                          " source (%s) and target (%s) node" %
                          (src_version, dst_version))
-        hv = hypervisor.GetHypervisor(self.instance.hypervisor)
+        hv = hypervisor.GetHypervisorClass(self.instance.hypervisor)
         if hv.VersionsSafeForMigration(src_version, dst_version):
           self.feedback_fn("  migrating from hypervisor version %s to %s should"
                            " be safe" % (src_version, dst_version))
@@ -872,30 +945,41 @@ class TLMigrateInstance(Tasklet):
 
       time.sleep(self._MIGRATION_POLL_INTERVAL)
 
-    result = self.rpc.call_instance_finalize_migration_src(
-               self.source_node_uuid, self.instance, True, self.live)
-    msg = result.fail_msg
-    if msg:
+    # Always call finalize on both source and target, they should compose
+    # a single operation, consisting of (potentially) parallel steps, that
+    # should be always attempted/retried together (like in _AbortMigration)
+    # without setting any expecetations in what order they execute.
+    result_src = self.rpc.call_instance_finalize_migration_src(
+        self.source_node_uuid, self.instance, True, self.live)
+
+    result_dst = self.rpc.call_instance_finalize_migration_dst(
+        self.target_node_uuid, self.instance, migration_info, True)
+
+    err_msg = []
+    if result_src.fail_msg:
+      logging.error("Instance migration succeeded, but finalization failed"
+                    " on the source node: %s", result_src.fail_msg)
+      err_msg.append(self.cfg.GetNodeName(self.source_node_uuid) + ': '
+                     + result_src.fail_msg)
+
+    if result_dst.fail_msg:
       logging.error("Instance migration succeeded, but finalization failed"
-                    " on the source node: %s", msg)
-      raise errors.OpExecError("Could not finalize instance migration: %s" %
-                               msg)
+                    " on the target node: %s", result_dst.fail_msg)
+      err_msg.append(self.cfg.GetNodeName(self.target_node_uuid) + ': '
+                     + result_dst.fail_msg)
+
+    if err_msg:
+      raise errors.OpExecError(
+          "Could not finalize instance migration: %s" % ' '.join(err_msg))
 
+    # Update instance location only after finalize completed. This way, if
+    # either finalize fails, the config still stores the old primary location,
+    # so we can know which instance to delete if we need to (manually) clean up.
     self.cfg.SetInstancePrimaryNode(self.instance.uuid, self.target_node_uuid)
     self.instance = self.cfg.GetInstanceInfo(self.instance_uuid)
-    disks = self.cfg.GetInstanceDisks(self.instance_uuid)
-
-    result = self.rpc.call_instance_finalize_migration_dst(
-               self.target_node_uuid, self.instance, migration_info, True)
-    msg = result.fail_msg
-    if msg:
-      logging.error("Instance migration succeeded, but finalization failed"
-                    " on the target node: %s", msg)
-      raise errors.OpExecError("Could not finalize instance migration: %s" %
-                               msg)
 
     self._CloseInstanceDisks(self.source_node_uuid)
-
+    disks = self.cfg.GetInstanceDisks(self.instance_uuid)
     if utils.AnyDiskOfType(disks, constants.DTS_INT_MIRROR):
       self._WaitUntilSync()
       self._GoStandalone()
index 048b1e4..9a14ad0 100644 (file)
@@ -429,8 +429,7 @@ class LUInstanceReinstall(LogicalUnit):
     os_image = objects.GetOSImage(self.op.osparams)
 
     if os_image is not None:
-      feedback_fn("Using OS image '%s', not changing instance"
-                  " configuration" % os_image)
+      feedback_fn("Using OS image '%s'" % os_image)
     else:
       os_image = objects.GetOSImage(self.instance.osparams)
 
@@ -447,6 +446,11 @@ class LUInstanceReinstall(LogicalUnit):
       self.LogInfo("No OS scripts or OS image specified or found in the"
                    " instance's configuration, nothing to install")
     else:
+      if self.op.osparams is not None:
+        self.instance.osparams = self.op.osparams
+      if self.op.osparams_private is not None:
+        self.instance.osparams_private = self.op.osparams_private
+      self.cfg.Update(self.instance, feedback_fn)
       StartInstanceDisks(self, self.instance, None)
       self.instance = self.cfg.GetInstanceInfo(self.instance.uuid)
       try:
index 486c14e..150f4ad 100644 (file)
@@ -1268,7 +1268,7 @@ class LUInstanceSetParams(LogicalUnit):
       res_min = ComputeIPolicyInstanceSpecViolation(ipolicy, ispec_min,
                                                     new_disk_types)
 
-      if (res_max or res_min):
+      if res_max or res_min:
         # FIXME: Improve error message by including information about whether
         # the upper or lower limit of the parameter fails the ipolicy.
         msg = ("Instance allocation to group %s (%s) violates policy: %s" %
@@ -1654,7 +1654,7 @@ class LUInstanceSetParams(LogicalUnit):
     disk = self.GenericGetDiskInfo(uuid, name)
 
     # Rename disk before attaching (if disk is filebased)
-    if disk.dev_type in (constants.DTS_INSTANCE_DEPENDENT_PATH):
+    if disk.dev_type in constants.DTS_INSTANCE_DEPENDENT_PATH:
       # Add disk size/mode, else GenerateDiskTemplate will not work.
       params[constants.IDISK_SIZE] = disk.size
       params[constants.IDISK_MODE] = str(disk.mode)
index 513d61b..3abc5f8 100644 (file)
@@ -525,7 +525,6 @@ def CalculateFileStorageDir(disk_type, cfg, instance_name,
       joinargs.append(instance_name)
 
     if len(joinargs) > 1:
-      # pylint: disable=W0142
       instance_file_storage_dir = utils.PathJoin(*joinargs)
     else:
       instance_file_storage_dir = joinargs[0]
@@ -597,8 +596,8 @@ def GenerateDiskTemplate(
       raise errors.ProgrammerError("Wrong template configuration")
     remote_node_uuid = secondary_node_uuids[0]
 
-    (drbd_params, _, _) = objects.Disk.ComputeLDParams(template_name,
-                                                       full_disk_params)
+    drbd_params = objects.Disk.ComputeLDParams(template_name,
+                                               full_disk_params)[0]
     drbd_default_metavg = drbd_params[constants.LDP_DEFAULT_METAVG]
 
     names = []
@@ -648,9 +647,9 @@ def GenerateDiskTemplate(
 
     elif template_name in constants.DTS_FILEBASED: # Gluster handled above
       logical_id_fn = \
-        lambda _, disk_index, disk: (file_driver,
-                                     "%s/%s" % (file_storage_dir,
-                                                names[idx]))
+        lambda idx, disk_index, disk: (file_driver,
+                                       "%s/%s" % (file_storage_dir,
+                                                  names[idx]))
       if template_name == constants.DT_FILE:
         disk_nodes = [primary_node_uuid]
 
@@ -2247,7 +2246,7 @@ class TLReplaceDisks(Tasklet):
                                  " %s" % (iallocator_name, ial.info),
                                  errors.ECODE_NORES)
 
-    remote_node_name = ial.result[0]
+    remote_node_name = ial.result[0] # pylint: disable=E1136
     remote_node = lu.cfg.GetNodeInfoByName(remote_node_name)
 
     if remote_node is None:
@@ -3015,7 +3014,7 @@ class TLReplaceDisks(Tasklet):
       self._RemoveOldStorage(self.target_node_uuid, iv_names)
 
 
-class TemporaryDisk():
+class TemporaryDisk(object):
   """ Creates a new temporary bootable disk, and makes sure it is destroyed.
 
   Is a context manager, and should be used with the ``with`` statement as such.
index b703c1e..46cf28d 100644 (file)
@@ -38,14 +38,13 @@ from ganeti import errors
 from ganeti import ht
 from ganeti import locking
 from ganeti.masterd import iallocator
-from ganeti import network
 from ganeti import netutils
 from ganeti import objects
 from ganeti import pathutils
 from ganeti import utils
 from ganeti.cmdlib.common import AnnotateDiskParams, \
   ComputeIPolicyInstanceViolation, CheckDiskTemplateEnabled, \
-  CheckDiskTemplateEnabled, ComputeIPolicySpecViolation
+  ComputeIPolicySpecViolation
 
 
 #: Type description for changes as returned by L{ApplyContainerMods}'s
@@ -131,7 +130,7 @@ def BuildInstanceHookEnv(name, primary_node_name, secondary_node_names, os_type,
       if netinfo:
         nobj = objects.Network.FromDict(netinfo)
         env.update(nobj.HooksDict("INSTANCE_NIC%d_" % idx))
-      elif network:
+      elif net:
         # FIXME: broken network reference: the instance NIC specifies a
         # network, but the relevant network entry was not in the config. This
         # should be made impossible.
@@ -214,7 +213,7 @@ def BuildInstanceHookEnvByObject(lu, instance, secondary_nodes=None,
   }
   if override:
     args.update(override)
-  return BuildInstanceHookEnv(**args) # pylint: disable=W0142
+  return BuildInstanceHookEnv(**args)
 
 
 def GetClusterDomainSecret():
index d0bad88..35b51e6 100644 (file)
@@ -151,43 +151,44 @@ class LUOobCommand(NoHooksLU):
         self.LogWarning("Out-of-band RPC failed on node '%s': %s",
                         node.name, result.fail_msg)
         node_entry.append((constants.RS_NODATA, None))
+        continue
+
+      try:
+        self._CheckPayload(result)
+      except errors.OpExecError, err:
+        self.LogWarning("Payload returned by node '%s' is not valid: %s",
+                        node.name, err)
+        node_entry.append((constants.RS_NODATA, None))
       else:
-        try:
-          self._CheckPayload(result)
-        except errors.OpExecError, err:
-          self.LogWarning("Payload returned by node '%s' is not valid: %s",
-                          node.name, err)
-          node_entry.append((constants.RS_NODATA, None))
-        else:
-          if self.op.command == constants.OOB_HEALTH:
-            # For health we should log important events
-            for item, status in result.payload:
-              if status in [constants.OOB_STATUS_WARNING,
-                            constants.OOB_STATUS_CRITICAL]:
-                self.LogWarning("Item '%s' on node '%s' has status '%s'",
-                                item, node.name, status)
-
-          if self.op.command == constants.OOB_POWER_ON:
-            node.powered = True
-          elif self.op.command == constants.OOB_POWER_OFF:
-            node.powered = False
-          elif self.op.command == constants.OOB_POWER_STATUS:
-            powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
-            if powered != node.powered:
-              logging.warning(("Recorded power state (%s) of node '%s' does not"
-                               " match actual power state (%s)"), node.powered,
-                              node.name, powered)
-
-          # For configuration changing commands we should update the node
-          if self.op.command in (constants.OOB_POWER_ON,
-                                 constants.OOB_POWER_OFF):
-            self.cfg.Update(node, feedback_fn)
-
-          node_entry.append((constants.RS_NORMAL, result.payload))
-
-          if (self.op.command == constants.OOB_POWER_ON and
-              idx < len(self.nodes) - 1):
-            time.sleep(self.op.power_delay)
+        if self.op.command == constants.OOB_HEALTH:
+          # For health we should log important events
+          for item, status in result.payload:
+            if status in [constants.OOB_STATUS_WARNING,
+                          constants.OOB_STATUS_CRITICAL]:
+              self.LogWarning("Item '%s' on node '%s' has status '%s'",
+                              item, node.name, status)
+
+        if self.op.command == constants.OOB_POWER_ON:
+          node.powered = True
+        elif self.op.command == constants.OOB_POWER_OFF:
+          node.powered = False
+        elif self.op.command == constants.OOB_POWER_STATUS:
+          powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
+          if powered != node.powered:
+            logging.warning(("Recorded power state (%s) of node '%s' does not"
+                             " match actual power state (%s)"), node.powered,
+                            node.name, powered)
+
+        # For configuration changing commands we should update the node
+        if self.op.command in (constants.OOB_POWER_ON,
+                               constants.OOB_POWER_OFF):
+          self.cfg.Update(node, feedback_fn)
+
+        node_entry.append((constants.RS_NORMAL, result.payload))
+
+        if (self.op.command == constants.OOB_POWER_ON and
+            idx < len(self.nodes) - 1):
+          time.sleep(self.op.power_delay)
 
     return ret
 
index ec112d8..6347c5b 100644 (file)
@@ -146,7 +146,7 @@ class LUNetworkAdd(LogicalUnit):
       "mac_prefix": self.op.mac_prefix,
       "tags": self.op.tags,
       }
-    return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
+    return _BuildNetworkHookEnv(**args)
 
   def Exec(self, feedback_fn):
     """Add the ip pool to the cluster.
@@ -341,7 +341,7 @@ class LUNetworkSetParams(LogicalUnit):
       "mac_prefix": self.mac_prefix,
       "tags": self.tags,
       }
-    return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
+    return _BuildNetworkHookEnv(**args)
 
   def BuildHooksNodes(self):
     """Build hooks nodes.
index 7b53063..52a8391 100644 (file)
@@ -66,7 +66,6 @@ class LUQuery(NoHooksLU):
   """Query for resources/items of a certain kind.
 
   """
-  # pylint: disable=W0142
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -88,7 +87,6 @@ class LUQueryFields(NoHooksLU):
   """Query for resources/items of a certain kind.
 
   """
-  # pylint: disable=W0142
   REQ_BGL = False
 
   def CheckArguments(self):
index 167be3d..f4d96c3 100644 (file)
@@ -173,7 +173,6 @@ class LUTestDelay(NoHooksLU):
       # Instance of '_socketobject' has no ... member
       conn.settimeout(time_to_go)
       conn.recv(1)
-      # pylint: enable=E1101
     except socket.timeout, _:
       # A second timeout can occur if no data is sent
       return False
index bd791e1..7aac59e 100644 (file)
@@ -59,12 +59,12 @@ try:
   from hashlib import md5 as md5_hash # pylint: disable=W0611,E0611,F0401
   from hashlib import sha1 as sha1_hash # pylint: disable=W0611,E0611,F0401
   # this additional version is needed for compatibility with the hmac module
-  sha1 = sha1_hash
+  sha1 = sha1_hash # pylint: disable=C0103
 except ImportError:
-  from md5 import new as md5_hash
+  from md5 import new as md5_hash # pylint: disable=W0611
   import sha
   sha1 = sha
-  sha1_hash = sha.new
+  sha1_hash = sha.new # pylint: disable=C0103
 
 
 def _all(seq):
@@ -119,7 +119,7 @@ def _partial(func, *args, **keywords): # pylint: disable=W0622
   def newfunc(*fargs, **fkeywords):
     newkeywords = keywords.copy()
     newkeywords.update(fkeywords)
-    return func(*(args + fargs), **newkeywords) # pylint: disable=W0142
+    return func(*(args + fargs), **newkeywords)
 
   newfunc.func = func
   newfunc.args = args
index 16b6ee1..0aa2a2f 100644 (file)
@@ -1833,7 +1833,7 @@ class ConfigWriter(object):
 
     if expanded_name is not None:
       # there has to be exactly one instance with that name
-      inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
+      inst = [n for n in all_insts if n.name == expanded_name][0]
       return (inst.uuid, inst.name)
     else:
       return (None, None)
@@ -2118,7 +2118,7 @@ class ConfigWriter(object):
 
     if expanded_name is not None:
       # there has to be exactly one node with that name
-      node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
+      node = [n for n in all_nodes if n.name == expanded_name][0]
       return (node.uuid, node.name)
     else:
       return (None, None)
@@ -2577,7 +2577,7 @@ class ConfigWriter(object):
 
     # Update timestamps and serials (only once per node/group object)
     now = time.time()
-    for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
+    for obj in frozenset(itertools.chain(*resmod)):
       obj.serial_no += 1
       obj.mtime = now
 
index e53b384..2d8b99e 100644 (file)
@@ -107,8 +107,8 @@ def VerifyIpolicy(owner, ipolicy, iscluster, callback):
     callback("%s has invalid instance policy: %s" % (owner, err))
   for key, value in ipolicy.items():
     if key == constants.ISPECS_MINMAX:
-      for k in range(len(value)):
-        VerifyIspecs(owner, "ipolicy/%s[%s]" % (key, k), value[k], callback)
+      for i, val in enumerate(value):
+        VerifyIspecs(owner, "ipolicy/%s[%s]" % (key, i), val, callback)
     elif key == constants.ISPECS_STD:
       VerifyType(owner, "ipolicy/" + key, value,
                  constants.ISPECS_PARAMETER_TYPES, callback)
index 826e761..835c502 100644 (file)
@@ -547,5 +547,4 @@ def MaybeRaise(result):
   error = GetEncodedError(result)
   if error:
     (errcls, args) = error
-    # pylint: disable=W0142
     raise errcls(*args)
index 596dd3d..1b04c2b 100644 (file)
 
 """
 
+import errno
 import logging
 import mimetools
-import OpenSSL
 import select
 import socket
-import errno
 
 from cStringIO import StringIO
 
+import OpenSSL
+
 from ganeti import constants
 from ganeti import utils
 
index 35b0b32..e7d4dc0 100644 (file)
@@ -36,12 +36,12 @@ import re
 import base64
 import binascii
 
+from cStringIO import StringIO
+
 from ganeti import compat
 from ganeti import http
 from ganeti import utils
 
-from cStringIO import StringIO
-
 # Digest types from RFC2617
 HTTP_BASIC_AUTH = "Basic"
 HTTP_DIGEST_AUTH = "Digest"
index 4da5aec..aa61718 100644 (file)
 """
 
 import logging
-import pycurl
 import threading
+
 from cStringIO import StringIO
 
+import pycurl
+
 from ganeti import http
 from ganeti import compat
 from ganeti import netutils
@@ -379,7 +381,7 @@ def ProcessRequests(requests, lock_monitor_cb=None, _curl=pycurl.Curl,
   # Prepare all requests
   curl_to_client = \
     dict((client.GetCurlHandle(), client)
-         for client in map(lambda req: _StartRequest(_curl(), req), requests))
+         for client in [_StartRequest(_curl(), req) for req in requests])
 
   assert len(curl_to_client) == len(requests)
 
index 9a4563e..b4a41c8 100644 (file)
@@ -479,9 +479,8 @@ class HttpServer(http.HttpBase, asyncore.dispatcher):
   """Generic HTTP server class
 
   """
-  MAX_CHILDREN = 20
 
-  def __init__(self, mainloop, local_address, port, handler,
+  def __init__(self, mainloop, local_address, port, max_clients, handler,
                ssl_params=None, ssl_verify_peer=False,
                request_executor_class=None, ssl_verify_callback=None):
     """Initializes the HTTP server
@@ -492,6 +491,11 @@ class HttpServer(http.HttpBase, asyncore.dispatcher):
     @param local_address: Local IP address to bind to
     @type port: int
     @param port: TCP port to listen on
+    @type max_clients: int
+    @param max_clients: maximum number of client connections
+        open simultaneously.
+    @type handler: HttpServerHandler
+    @param handler: Request handler object
     @type ssl_params: HttpSslParams
     @param ssl_params: SSL key and certificate
     @type ssl_verify_peer: bool
@@ -524,6 +528,7 @@ class HttpServer(http.HttpBase, asyncore.dispatcher):
     self._children = []
     self.set_socket(self.socket)
     self.accepting = True
+    self.max_clients = max_clients
     mainloop.RegisterSignal(self)
 
   def Start(self):
@@ -549,7 +554,7 @@ class HttpServer(http.HttpBase, asyncore.dispatcher):
     """
     if not quick:
       # Don't wait for other processes if it should be a quick check
-      while len(self._children) > self.MAX_CHILDREN:
+      while len(self._children) > self.max_clients:
         try:
           # Waiting without a timeout brings us into a potential DoS situation.
           # As soon as too many children run, we'll not respond to new
index 8aaa062..5663b47 100644 (file)
@@ -300,9 +300,37 @@ class BaseHypervisor(object):
   CAN_MIGRATE = False
 
   def StartInstance(self, instance, block_devices, startup_paused):
-    """Start an instance."""
+    """Start an instance.
+
+    @type instance: L{objects.Instance}
+    @param instance: instance to start
+    @type block_devices: list of tuples (disk_object, link_name, drive_uri)
+    @param block_devices: blockdevices assigned to this instance
+    @type startup_paused: bool
+    @param startup_paused: if instance should be paused at startup
+    """
     raise NotImplementedError
 
+  def VerifyInstance(self, instance):  # pylint: disable=R0201,W0613
+    """Verify if running instance (config) is in correct state.
+
+    @type instance: L{objects.Instance}
+    @param instance: instance to verify
+
+    @return: bool, if instance in correct state
+    """
+    return True
+
+  def RestoreInstance(self, instance, block_devices):
+    """Fixup running instance's (config) state.
+
+    @type instance: L{objects.Instance}
+    @param instance: instance to restore
+    @type block_devices: list of tuples (disk_object, link_name, drive_uri)
+    @param block_devices: blockdevices assigned to this instance
+    """
+    pass
+
   def StopInstance(self, instance, force=False, retry=False, name=None,
                    timeout=None):
     """Stop an instance
index 2de4444..07e2c98 100644 (file)
@@ -327,17 +327,6 @@ class FakeHypervisor(hv_base.BaseHypervisor):
       # ensure it's down
       self._MarkDown(instance.name)
 
-  def PostMigrationCleanup(self, instance):
-    """Clean-up after a migration.
-
-    To be executed on the source node.
-
-    @type instance: L{objects.Instance}
-    @param instance: the instance that was migrated
-
-    """
-    pass
-
   def FinalizeMigrationSource(self, instance, success, live):
     """Finalize the instance migration on the source node.
 
index 580606b..ac02ff5 100644 (file)
@@ -200,7 +200,7 @@ def _GetDriveURI(disk, link, uri):
   access_mode = disk.params.get(constants.LDP_ACCESS,
                                 constants.DISK_KERNELSPACE)
   # If uri is available, use it during startup/hot-add
-  if (uri and access_mode == constants.DISK_USERSPACE):
+  if uri and access_mode == constants.DISK_USERSPACE:
     drive_uri = uri
   # Otherwise use the link previously created
   else:
@@ -540,7 +540,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
   _MIGRATION_STATUS_RE = re.compile(r"Migration\s+status:\s+(\w+)",
                                     re.M | re.I)
   _MIGRATION_PROGRESS_RE = \
-    re.compile(r"\s*transferred\s+ram:\s+(?P<transferred>\d+)\s+kbytes\s*\n"
+    re.compile(r"\s*transferred\s+ram:\s+(?P<transferred>\d+)\s+kbytes\s*\n.*"
                r"\s*remaining\s+ram:\s+(?P<remaining>\d+)\s+kbytes\s*\n"
                r"\s*total\s+ram:\s+(?P<total>\d+)\s+kbytes\s*\n", re.I)
 
@@ -1279,7 +1279,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       # TODO (2.8): kernel_irqchip and kvm_shadow_mem machine properties, as
       # extra hypervisor parameters. We should also investigate whether and how
       # shadow_mem should be considered for the resource model.
-      if (hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_ENABLED):
+      if hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_ENABLED:
         specprop = ",accel=kvm"
       else:
         specprop = ""
@@ -1899,10 +1899,9 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       self._RunKVMCmd(name, kvm_cmd, tapfds)
 
     utils.EnsureDirs([(self._InstanceNICDir(instance.name),
-                     constants.RUN_DIRS_MODE)])
+                       constants.RUN_DIRS_MODE)])
     for nic_seq, tap in enumerate(taps):
-      utils.WriteFile(self._InstanceNICFile(instance.name, nic_seq),
-                      data=tap)
+      utils.WriteFile(self._InstanceNICFile(instance.name, nic_seq), data=tap)
 
     if vnc_pwd:
       change_cmd = "change vnc password %s" % vnc_pwd
@@ -2385,6 +2384,32 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     self._ExecuteKVMRuntime(instance, kvm_runtime, kvmhelp,
                             incoming=incoming_address)
 
+  def _ConfigureRoutedNICs(self, instance, info):
+    """Configures all NICs in routed mode
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance to be configured
+    @type info: string
+    @param info: serialized KVM runtime info
+    """
+    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
+    kvm_nics = kvm_runtime[1]
+
+    for nic_seq, nic in enumerate(kvm_nics):
+      if nic.nicparams[constants.NIC_MODE] != constants.NIC_MODE_ROUTED:
+        # Bridged/OVS interfaces have already been configured
+        continue
+      try:
+        tap = utils.ReadFile(self._InstanceNICFile(instance.name, nic_seq))
+      except EnvironmentError, err:
+        logging.warning("Failed to find host interface for %s NIC #%d: %s",
+                        instance.name, nic_seq, str(err))
+        continue
+      try:
+        self._ConfigureNIC(instance, nic_seq, nic, tap)
+      except errors.HypervisorError, err:
+        logging.warning(str(err))
+
   def FinalizeMigrationDst(self, instance, info, success):
     """Finalize the instance migration on the target node.
 
@@ -2395,29 +2420,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
 
     """
     if success:
-      kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
-      kvm_nics = kvm_runtime[1]
-
-      for nic_seq, nic in enumerate(kvm_nics):
-        if nic.nicparams[constants.NIC_MODE] != constants.NIC_MODE_ROUTED:
-          # Bridged/OVS interfaces have already been configured
-          continue
-        try:
-          tap = utils.ReadFile(self._InstanceNICFile(instance.name, nic_seq))
-        except EnvironmentError, err:
-          logging.warning("Failed to find host interface for %s NIC #%d: %s",
-                          instance.name, nic_seq, str(err))
-          continue
-        try:
-          self._ConfigureNIC(instance, nic_seq, nic, tap)
-        except errors.HypervisorError, err:
-          logging.warning(str(err))
-
+      self._ConfigureRoutedNICs(instance, info)
       self._WriteKVMRuntime(instance.name, info)
     else:
       self.StopInstance(instance, force=True)
 
-  def MigrateInstance(self, cluster_name, instance, target, live):
+  def MigrateInstance(self, cluster_name, instance, target, live_migration):
     """Migrate an instance to a target node.
 
     The migration will not be attempted if the instance is not
@@ -2429,8 +2437,8 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     @param instance: the instance to be migrated
     @type target: string
     @param target: ip address of the target node
-    @type live: boolean
-    @param live: perform a live migration
+    @type live_migration: boolean
+    @param live_migration: perform a live migration
 
     """
     instance_name = instance.name
@@ -2439,7 +2447,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     if not alive:
       raise errors.HypervisorError("Instance not running, cannot migrate")
 
-    if not live:
+    if not live_migration:
       self._CallMonitorCommand(instance_name, "stop")
 
     migrate_command = ("migrate_set_speed %dm" %
@@ -2459,24 +2467,28 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     migrate_command = "migrate -d tcp:%s:%s" % (target, port)
     self._CallMonitorCommand(instance_name, migrate_command)
 
-  def FinalizeMigrationSource(self, instance, success, live):
+  def FinalizeMigrationSource(self, instance, success, _):
     """Finalize the instance migration on the source node.
 
     @type instance: L{objects.Instance}
     @param instance: the instance that was migrated
     @type success: bool
     @param success: whether the migration succeeded or not
-    @type live: bool
-    @param live: whether the user requested a live migration or not
 
     """
     if success:
       pidfile, pid, _ = self._InstancePidAlive(instance.name)
       utils.KillProcess(pid)
       self._RemoveInstanceRuntimeFiles(pidfile, instance.name)
-    elif live:
-      self._CallMonitorCommand(instance.name, self._CONT_CMD)
-    self._ClearUserShutdown(instance.name)
+      self._ClearUserShutdown(instance.name)
+    else:
+      # Detect if PID is alive rather than deciding if we were to perform a live
+      # migration.
+      _, _, alive = self._InstancePidAlive(instance.name)
+      if alive:
+        self._CallMonitorCommand(instance.name, self._CONT_CMD)
+      else:
+        self.CleanupInstance(instance.name)
 
   def GetMigrationStatus(self, instance):
     """Get the migration status
index 658f20c..1a355eb 100644 (file)
@@ -105,7 +105,7 @@ class QmpMessage(object):
     """Delete the specified element from the QmpMessage.
 
     """
-    del(self.data[key])
+    del self.data[key]
 
   @staticmethod
   def BuildFromJsonString(json_string):
@@ -390,7 +390,7 @@ class QmpConnection(MonitorSocket):
       self.sock.sendall(message_str)
     except socket.timeout, err:
       raise errors.HypervisorError("Timeout while sending a QMP message: "
-                                   "%s (%s)" % (err.string, err.errno))
+                                   "%s" % err)
     except socket.error, err:
       raise errors.HypervisorError("Unable to send data from KVM using the"
                                    " QMP protocol: %s" % err)
index 8625777..bb2c132 100644 (file)
@@ -62,7 +62,7 @@ def _CreateBlankFile(path, mode):
     raise HypervisorError("Failed to create file %s: %s" % (path, err))
 
 
-class LXCVersion(tuple): # pylint: disable=R0924
+class LXCVersion(tuple):
   """LXC version class.
 
   """
index fc4f7b1..bab777a 100644 (file)
@@ -34,6 +34,7 @@
 
 import logging
 import errno
+import os
 import string # pylint: disable=W0402
 import shutil
 import time
@@ -148,6 +149,7 @@ def _ParseInstanceList(lines, include_node):
       raise errors.HypervisorError("Can't parse instance list,"
                                    " line: %s" % line)
     try:
+      # TODO: Cleanup this mess - introduce a namedtuple/dict/class
       data[1] = int(data[1])
       data[2] = int(data[2])
       data[3] = int(data[3])
@@ -168,6 +170,26 @@ def _ParseInstanceList(lines, include_node):
   return result
 
 
+def _InstanceDomID(info):
+  """Get instance domain ID from instance info tuple.
+  @type info: tuple
+  @param info: instance info as parsed by _ParseInstanceList()
+
+  @return: int, instance domain ID
+  """
+  return info[1]
+
+
+def _InstanceRuntime(info):
+  """Get instance runtime from instance info tuple.
+  @type info: tuple
+  @param info: instance info as parsed by _ParseInstanceList()
+
+  @return: float value of instance runtime
+  """
+  return info[5]
+
+
 def _GetAllInstanceList(fn, include_node, delays, timeout):
   """Return the list of instances including running and shutdown.
 
@@ -347,7 +369,7 @@ def _ParseNodeInfo(info):
     if len(fields) < 2:
       continue
 
-    (key, val) = (s.strip() for s in fields)
+    (key, val) = [s.strip() for s in fields]
 
     # Note: in Xen 3, memory has changed to total_memory
     if key in ("memory", "total_memory"):
@@ -842,12 +864,12 @@ class XenHypervisor(hv_base.BaseHypervisor):
 
     """
     instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
-    result = None
+
     for data in instance_list:
       if data[0] == instance_name:
-        result = data
-        break
-    return result
+        return data
+
+    return None
 
   def GetAllInstancesInfo(self, hvparams=None):
     """Get properties of all instances.
@@ -875,9 +897,39 @@ class XenHypervisor(hv_base.BaseHypervisor):
 
     self._WriteConfigFile(instance.name, buf.getvalue())
 
+  def VerifyInstance(self, instance):
+    """Verify if running instance (configuration) is in correct state.
+
+    @type instance: L{objects.Instance}
+    @param instance: instance to verify
+
+    @return: bool, if instance in correct state
+    """
+    config_file = utils.PathJoin(self._cfgdir, "auto", instance.name)
+    return os.path.exists(config_file)
+
+  def RestoreInstance(self, instance, block_devices):
+    """Fixup running instance's state.
+
+    @type instance: L{objects.Instance}
+    @param instance: instance to restore
+    @type block_devices: list of tuples (disk_object, link_name, drive_uri)
+    @param block_devices: blockdevices assigned to this instance
+    """
+    startup_memory = self._InstanceStartupMemory(instance)
+    self._MakeConfigFile(instance, startup_memory, block_devices)
+
   def StartInstance(self, instance, block_devices, startup_paused):
     """Start an instance.
 
+    @type instance: L{objects.Instance}
+    @param instance: instance to start
+    @type block_devices: list of tuples (cfdev, rldev)
+      - cfdev: dict containing ganeti config disk part
+      - rldev: ganeti.block.bdev.BlockDev object
+    @param block_devices: blockdevices assigned to this instance
+    @type startup_paused: bool
+    @param startup_paused: if instance should be paused at startup
     """
     startup_memory = self._InstanceStartupMemory(instance)
 
@@ -932,16 +984,16 @@ class XenHypervisor(hv_base.BaseHypervisor):
                     or None for no timeout
 
     """
-    instance_info = self.GetInstanceInfo(name, hvparams=hvparams)
+    info = self.GetInstanceInfo(name, hvparams=hvparams)
 
-    if instance_info is None or _IsInstanceShutdown(instance_info[4]):
+    if info is None or hv_base.HvInstanceState.IsShutdown(info[4]):
       logging.info("Failed to shutdown instance %s, not running", name)
       return None
 
     return self._RunXen(["shutdown", "-w", name], hvparams, timeout)
 
   def _DestroyInstance(self, name, hvparams):
-    """Destroy an instance if the instance if the instance exists.
+    """Destroy an instance if the instance exists.
 
     @type name: string
     @param name: name of the instance to destroy
@@ -974,6 +1026,19 @@ class XenHypervisor(hv_base.BaseHypervisor):
     else:
       self._DestroyInstance(name, hvparams)
 
+  def _RenameInstance(self, old_name, new_name, hvparams):
+    """Rename an instance (domain).
+
+    @type old_name: string
+    @param old_name: current name of the instance
+    @type new_name: string
+    @param new_name: future (requested) name of the instace
+    @type hvparams: dict of string
+    @param hvparams: hypervisor parameters of the instance
+
+    """
+    return self._RunXen(["rename", old_name, new_name], hvparams)
+
   def _StopInstance(self, name, force, hvparams, timeout):
     """Stop an instance.
 
@@ -1034,7 +1099,8 @@ class XenHypervisor(hv_base.BaseHypervisor):
 
       # check if the domain ID has changed or the run time has decreased
       if (new_info is not None and
-          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
+          (_InstanceDomID(new_info) != _InstanceDomID(ini_info) or (
+              _InstanceRuntime(new_info) < _InstanceRuntime(ini_info)))):
         return
 
       raise utils.RetryAgain()
@@ -1208,24 +1274,39 @@ class XenHypervisor(hv_base.BaseHypervisor):
                                      " on port %d: check if port is available" %
                                      port)
 
-  def FinalizeMigrationDst(self, instance, info, success):
+  def FinalizeMigrationDst(self, instance, config, success):
     """Finalize an instance migration.
 
-    After a successful migration we write the xen config file.
-    We do nothing on a failure, as we did not change anything at accept time.
+    Write a config file if the instance is running on the destination node
+    regardles if we think the migration succeeded or not. This will cover cases,
+    when the migration succeeded but due to a timeout on the source node we
+    think it failed. If we think the migration failed and there is an unstarted
+    domain, clean it up.
 
     @type instance: L{objects.Instance}
     @param instance: instance whose migration is being finalized
-    @type info: string
-    @param info: content of the xen config file on the source node
+    @type config: string
+    @param config: content of the xen config file from the source node
     @type success: boolean
-    @param success: whether the migration was a success or a failure
+    @param success: whether the master node thinks the migration succeeded
 
     """
-    if success:
-      self._WriteConfigFile(instance.name, info)
-    elif self._UseMigrationDaemon(instance.hvparams):
-      XenHypervisor._KillMigrationDaemon(instance)
+
+    # We should recreate the config file if the domain is present and running,
+    # regardless if we think the migration succeeded or not.
+    info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
+    if info and _InstanceRuntime(info) != 0:
+      self._WriteConfigFile(instance.name, config)
+
+    if not success:
+      if self._UseMigrationDaemon(instance.hvparams):
+        XenHypervisor._KillMigrationDaemon(instance)
+
+      # Fix the common failure when the domain was created but never started:
+      # this happens if the memory transfer didn't complete and the instance
+      # is running on the source node.
+      if info and _InstanceRuntime(info) == 0:
+        self._DestroyInstance(instance.name, instance.hvparams)
 
   def MigrateInstance(self, _cluster_name, instance, target, live):
     """Migrate an instance to a target node.
@@ -1291,24 +1372,39 @@ class XenHypervisor(hv_base.BaseHypervisor):
       raise errors.HypervisorError("Failed to migrate instance %s: %s" %
                                    (instance_name, result.output))
 
-  def FinalizeMigrationSource(self, instance, success, live):
+  def FinalizeMigrationSource(self, instance, success, _):
     """Finalize the instance migration on the source node.
 
     @type instance: L{objects.Instance}
     @param instance: the instance that was migrated
     @type success: bool
-    @param success: whether the migration succeeded or not
-    @type live: bool
-    @param live: whether the user requested a live migration or not
+    @param success: whether the master thinks the migration succeeded
 
     """
     # pylint: disable=W0613
     if success:
-      # remove old xen file after migration succeeded
+      # Remove old xen file after migration succeeded
+      # Note that _RemoveConfigFile silently succeeds if the file is already
+      # deleted, that makes this function idempotent
       try:
         self._RemoveConfigFile(instance.name)
       except EnvironmentError:
         logging.exception("Failure while removing instance config file")
+    else:
+      # Cleanup the most common failure case when the source instance fails
+      # to freeze and remains running renamed:
+      # XM: renamed to 'migrating-${oldname}'
+      # XL: renamed to '${oldname}--migratedaway'
+
+      temp_name, info = None, None
+      for n in ['migrating-' + instance.name, instance.name + '--migratedaway']:
+        info = self.GetInstanceInfo(n, hvparams=instance.hvparams)
+        if info:
+          temp_name = n
+          break
+
+      if info:
+        self._RenameInstance(temp_name, instance.name, instance.hvparams)
 
   def GetMigrationStatus(self, instance):
     """Get the migration status
index 9384f55..d78625c 100644 (file)
@@ -415,7 +415,7 @@ class _QueuedJob(object):
 
     entries = []
     for op in self.ops:
-      entries.extend(filter(lambda entry: entry[0] > serial, op.log))
+      entries.extend([entry for entry in op.log if entry[0] > serial])
 
     return entries
 
@@ -682,7 +682,7 @@ def _EncodeOpError(err):
   return errors.EncodeException(to_encode)
 
 
-class _TimeoutStrategyWrapper:
+class _TimeoutStrategyWrapper(object):
   def __init__(self, fn):
     """Initializes this class.
 
@@ -714,7 +714,7 @@ class _TimeoutStrategyWrapper:
     return result
 
 
-class _OpExecContext:
+class _OpExecContext(object):
   def __init__(self, op, index, log_prefix, timeout_strategy_factory):
     """Initializes this class.
 
@@ -1143,7 +1143,7 @@ class _JobProcessor(object):
       assert job.writable, "Job became read-only while being processed"
 
 
-class _JobDependencyManager:
+class _JobDependencyManager(object):
   """Keeps track of job dependencies.
 
   """
index 4391a4f..9cc3122 100644 (file)
@@ -117,12 +117,13 @@ class Client(cl.AbstractClient):
 
   def SubmitJob(self, ops):
     ops_state = [op.__getstate__()
-                               if not isinstance(op, objects.ConfigObject)
-                               else op.ToDict(_with_private=True) for op in ops]
+                 if not isinstance(op, objects.ConfigObject)
+                 else op.ToDict(_with_private=True)
+                 for op in ops]
     return self.CallMethod(REQ_SUBMIT_JOB, (ops_state, ))
 
   def SubmitJobToDrainedQueue(self, ops):
-    ops_state = map(lambda op: op.__getstate__(), ops)
+    ops_state = [op.__getstate__() for op in ops]
     return self.CallMethod(REQ_SUBMIT_JOB_TO_DRAINED_QUEUE, (ops_state, ))
 
   def SubmitManyJobs(self, jobs):
index 631acff..736bf4f 100644 (file)
 
 """Module implementing the iallocator code."""
 
+import logging
+
 from ganeti import compat
 from ganeti import constants
 from ganeti import errors
 from ganeti import ht
 from ganeti import outils
 from ganeti import opcodes
-import ganeti.rpc.node as rpc
 from ganeti import serializer
 from ganeti import utils
 
+import ganeti.rpc.node as rpc
 import ganeti.masterd.instance as gmi
 
-import logging
-
 _STRING_LIST = ht.TListOf(ht.TString)
 _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
    # pylint: disable=E1101
@@ -156,7 +156,7 @@ class IARequestBase(outils.ValidatedSlots):
     @raises ResultValidationError: If validation fails
 
     """
-    if ia.success and not self.REQ_RESULT(result):
+    if ia.success and not self.REQ_RESULT(result): # pylint: disable=E1102
       raise errors.ResultValidationError("iallocator returned invalid result,"
                                          " expected %s, got %s" %
                                          (self.REQ_RESULT, result))
index 48f543e..fbd24f1 100644 (file)
@@ -40,7 +40,7 @@ from ganeti import constants
 from ganeti import errors
 import ganeti.rpc.client as cl
 from ganeti.rpc.transport import Transport
-from ganeti.rpc import errors
+from ganeti.rpc.errors import TimeoutError
 
 
 # If the metadata daemon is disabled, there is no stub generated for it.
@@ -70,7 +70,7 @@ if constants.ENABLE_METAD:
         try:
           self._InitTransport()
           return
-        except errors.TimeoutError:
+        except TimeoutError:
           logging.debug("Timout trying to connect to MetaD")
           if try_no == retries - 1:
             raise
index 2eada25..ab94723 100644 (file)
@@ -428,7 +428,7 @@ class IPAddress(object):
     @return: True if valid, False otherwise
 
     """
-    assert (isinstance(netmask, (int, long)))
+    assert isinstance(netmask, (int, long))
 
     return 0 < netmask <= cls.iplen
 
index f53f846..7e20fc2 100644 (file)
@@ -35,11 +35,14 @@ pass to and from external parties.
 
 """
 
-# pylint: disable=E0203,W0201,R0902
+# pylint: disable=E0203,E0237,W0201,R0902
 
 # E0203: Access to member %r before its definition, since we use
 # objects.py which doesn't explicitly initialise its members
 
+# E0237: Assigning to attribute not defined in class slots. pylint doesn't
+# appear to notice many of the slots defined in __slots__ for several objects.
+
 # W0201: Attribute '%s' defined outside __init__
 
 # R0902: Allow instances of these objects to have more than 20 attributes
@@ -50,6 +53,7 @@ import copy
 import logging
 import time
 from cStringIO import StringIO
+from socket import AF_INET
 
 from ganeti import errors
 from ganeti import constants
@@ -58,8 +62,6 @@ from ganeti import outils
 from ganeti import utils
 from ganeti import serializer
 
-from socket import AF_INET
-
 
 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
            "OS", "Node", "NodeGroup", "Cluster", "FillDict", "Network",
@@ -278,7 +280,7 @@ class ConfigObject(outils.ValidatedSlots):
       raise errors.ConfigurationError("Invalid object passed to FromDict:"
                                       " expected dict, got %s" % type(val))
     val_str = dict([(str(k), v) for k, v in val.iteritems()])
-    obj = cls(**val_str) # pylint: disable=W0142
+    obj = cls(**val_str)
     return obj
 
   def Copy(self):
@@ -1275,7 +1277,7 @@ class Instance(TaggableObject):
     if _with_private:
       bo["osparams_private"] = self.osparams_private.Unprivate()
 
-    for attr in "nics", :
+    for attr in ("nics",):
       alist = bo.get(attr, None)
       if alist:
         nlist = outils.ContainerToDicts(alist)
@@ -2101,8 +2103,7 @@ class Cluster(TaggableObject):
       - at public visibility:  {public}
       - at private visibility: {private}
       - at secret visibility:  {secret}
-      """.format(dupes=formatter(duplicate_keys),
-                 public=formatter(params_public & duplicate_keys),
+      """.format(public=formatter(params_public & duplicate_keys),
                  private=formatter(params_private & duplicate_keys),
                  secret=formatter(params_secret & duplicate_keys))
       raise errors.OpPrereqError(msg)
index 4b65cb5..66dbf83 100644 (file)
 
 """
 
-# pylint: disable=F0401, E1101
+# pylint: disable=F0401, E1101, C0413
 
 # F0401 because ElementTree is not default for python 2.4
 # E1101 makes no sense - pylint assumes that ElementTree object is a tuple
+# C0413 Wrong import position
 
 
 import ConfigParser
@@ -1278,7 +1279,7 @@ class OVFImporter(Converter):
       options
 
     """
-    assert type(self.options.hypervisor) is tuple
+    assert isinstance(self.options.hypervisor, tuple)
     assert len(self.options.hypervisor) == 2
     results = {}
     if self.options.hypervisor[0]:
index 6a4014a..081e54b 100644 (file)
@@ -206,7 +206,7 @@ def FillOpcode(opcls, body, static, rename=None):
   params = dict((str(key), value) for (key, value) in params.items())
 
   try:
-    op = opcls(**params) # pylint: disable=W0142
+    op = opcls(**params)
     op.Validate(False)
   except (errors.OpPrereqError, TypeError), err:
     raise http.HttpBadRequest("Invalid body parameters: %s" % err)
index c1f0aba..4f6c8b6 100644 (file)
 # be standalone.
 
 import logging
-import simplejson
 import socket
-import urllib
 import threading
-import pycurl
 import time
+import urllib
+
+import pycurl
+import simplejson
 
 try:
   from cStringIO import StringIO
index 4d054cd..ed9caad 100644 (file)
 
 """
 
+import base64
 import logging
 import re
-import base64
-import pycurl
+
 from cStringIO import StringIO
 
+import pycurl
+
 from ganeti import errors
 from ganeti import opcodes
 from ganeti import http
@@ -49,8 +51,8 @@ import ganeti.rpc.client as rpccl
 from ganeti import rapi
 
 import ganeti.http.server # pylint: disable=W0611
-import ganeti.server.rapi
-import ganeti.rapi.client
+import ganeti.server.rapi # pylint: disable=W0611
+import ganeti.rapi.client # pylint: disable=W0611
 
 
 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
@@ -103,7 +105,7 @@ def VerifyOpInput(op_id, data):
   op_cls = _GetOpById(op_id)
 
   try:
-    op = op_cls(**data) # pylint: disable=W0142
+    op = op_cls(**data)
   except TypeError, err:
     raise VerificationError("Unable to create opcode instance: %s" % err)
 
index 267353b..ce586bd 100644 (file)
@@ -146,8 +146,8 @@ def CallRPCMethod(transport_cb, method, args, version=None):
   t3 = time.time() * 1000
   (success, result, resp_version) = ParseResponse(response_msg)
   t4 = time.time() * 1000
-  logging.info("CallRPCMethod %s: format: %dms, sock: %dms, parse: %dms",
-               method, int(t2 - t1), int(t3 - t2), int(t4 - t3))
+  logging.debug("CallRPCMethod %s: format: %dms, sock: %dms, parse: %dms",
+                method, int(t2 - t1), int(t3 - t2), int(t4 - t3))
   # Verify version if there was one in the response
   if resp_version is not None and resp_version != version:
     raise LuxiError("RPC version mismatch, client %s, response %s" %
index e0f4659..3c11541 100644 (file)
 # if they need to start using instance attributes
 # R0904: Too many public methods
 
-import logging
-import zlib
 import base64
-import pycurl
-import threading
 import copy
+import logging
 import os
+import threading
+import zlib
+
+import pycurl
 
 from ganeti import utils
 from ganeti import objects
@@ -252,7 +253,7 @@ class RpcResult(object):
       args = (msg, ecode)
     else:
       args = (msg, )
-    raise ec(*args) # pylint: disable=W0142
+    raise ec(*args)
 
   def Warn(self, msg, feedback_fn):
     """If the result has failed, call the feedback_fn.
@@ -304,7 +305,7 @@ def _SsconfResolver(ssconf_ips, node_list, _,
   return result
 
 
-class _StaticResolver:
+class _StaticResolver(object):
   def __init__(self, addresses):
     """Initializes this class.
 
@@ -362,7 +363,7 @@ def _NodeConfigResolver(single_node_fn, all_nodes_fn, node_uuids, opts):
             for uuid in node_uuids]
 
 
-class _RpcProcessor:
+class _RpcProcessor(object):
   def __init__(self, resolver, port, lock_monitor_cb=None):
     """Initializes this class.
 
@@ -471,7 +472,7 @@ class _RpcProcessor:
     return self._CombineResults(results, requests, procedure)
 
 
-class _RpcClientBase:
+class _RpcClientBase(object):
   def __init__(self, resolver, encoder_fn, lock_monitor_cb=None,
                _req_process_fn=None):
     """Initializes this class.
@@ -952,7 +953,7 @@ class RpcRunner(_RpcClientBase,
     """Wrapper for L{AnnotateDiskParams}.
 
     """
-    (anno_disk,) = self._DisksDictDP(node, ([disk], instance))
+    anno_disk = self._DisksDictDP(node, ([disk], instance))[0]
     return anno_disk
 
   def _EncodeNodeToDiskDictDP(self, node, value):
index 47f1433..8be198f 100644 (file)
@@ -52,7 +52,7 @@ DEF_CTMO = constants.LUXI_DEF_CTMO
 DEF_RWTO = constants.LUXI_DEF_RWTO
 
 
-class Transport:
+class Transport(object):
   """Low-level transport class.
 
   This is used on the client side.
@@ -244,7 +244,7 @@ class Transport:
       self.socket = None
 
 
-class FdTransport:
+class FdTransport(object):
   """Low-level transport class that works on arbitrary file descriptors.
 
   Unlike L{Transport}, this doesn't use timeouts.
index 48f2ecb..5bd032e 100644 (file)
@@ -147,7 +147,7 @@ def _NodeInfoPreProc(node, args):
   assert len(args) == 2
   # The storage_units argument is either a dictionary with one value for each
   # node, or a fixed value to be used for all the nodes
-  if type(args[0]) is dict:
+  if isinstance(args[0], dict):
     return [args[0][node], args[1]]
   else:
     return args
index 1397fbd..477a358 100644 (file)
 
 """Ganeti node daemon"""
 
-# pylint: disable=C0103,W0142
+# pylint: disable=C0103
 
 # C0103: Functions in this module need to have a given name structure,
 # and the name of the daemon doesn't match
 
-# W0142: Used * or ** magic, since we do use it extensively in this
-# module
-
 import os
 import sys
 import logging
@@ -203,7 +200,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
       # And return the error's arguments, which must be already in
       # correct tuple format
       result = err.args
-    except Exception, err:
+    except Exception, err: # pylint: disable=W0703
       logging.exception("Error in RPC call")
       result = (False, "Error while executing backend function: %s" % str(err))
 
@@ -1314,7 +1311,7 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     return backend.CleanupImportExport(params[0])
 
 
-def CheckNoded(_, args):
+def CheckNoded(options, args):
   """Initial checks whether to run or exit with a failure.
 
   """
@@ -1322,6 +1319,12 @@ def CheckNoded(_, args):
     print >> sys.stderr, ("Usage: %s [-f] [-d] [-p port] [-b ADDRESS]" %
                           sys.argv[0])
     sys.exit(constants.EXIT_FAILURE)
+
+  if options.max_clients < 1:
+    print >> sys.stderr, ("%s --max-clients argument must be >= 1" %
+                          sys.argv[0])
+    sys.exit(constants.EXIT_FAILURE)
+
   try:
     codecs.lookup("string-escape")
   except LookupError:
@@ -1401,7 +1404,6 @@ def SSLVerifyPeer(conn, cert, errnum, errdepth, ok):
   else:
     logging.error("Invalid errdepth value: %s.", errdepth)
     return False
-  # pylint: enable=W0613
 
 
 def PrepNoded(options, _):
@@ -1435,11 +1437,11 @@ def PrepNoded(options, _):
   handler = NodeRequestHandler()
 
   mainloop = daemon.Mainloop()
-  server = \
-    http.server.HttpServer(mainloop, options.bind_address, options.port,
-                           handler, ssl_params=ssl_params, ssl_verify_peer=True,
-                           request_executor_class=request_executor_class,
-                           ssl_verify_callback=SSLVerifyPeer)
+  server = http.server.HttpServer(
+      mainloop, options.bind_address, options.port, options.max_clients,
+      handler, ssl_params=ssl_params, ssl_verify_peer=True,
+      request_executor_class=request_executor_class,
+      ssl_verify_callback=SSLVerifyPeer)
   server.Start()
 
   return (mainloop, server)
@@ -1468,6 +1470,10 @@ def Main():
   parser.add_option("--no-mlock", dest="mlock",
                     help="Do not mlock the node memory in ram",
                     default=True, action="store_false")
+  parser.add_option("--max-clients", dest="max_clients",
+                    default=20, type="int",
+                    help="Number of simultaneous connections accepted"
+                    " by noded")
 
   daemon.GenericMain(constants.NODED, parser, CheckNoded, PrepNoded, ExecNoded,
                      default_ssl_cert=pathutils.NODED_CERT_FILE,
index 9782ada..6bdb24f 100644 (file)
@@ -31,7 +31,7 @@
 
 """
 
-# pylint: disable=C0103,W0142
+# pylint: disable=C0103
 
 # C0103: Invalid name ganeti-watcher
 
@@ -61,7 +61,6 @@ from ganeti.rapi import connector
 from ganeti.rapi import baserlib
 
 import ganeti.http.auth   # pylint: disable=W0611
-import ganeti.http.server
 
 
 class RemoteApiRequestContext(object):
@@ -318,6 +317,11 @@ def CheckRapi(options, args):
                           sys.argv[0])
     sys.exit(constants.EXIT_FAILURE)
 
+  if options.max_clients < 1:
+    print >> sys.stderr, ("%s --max-clients argument must be >= 1" %
+                          sys.argv[0])
+    sys.exit(constants.EXIT_FAILURE)
+
   ssconf.CheckMaster(options.debug)
 
   # Read SSL certificate (this is a little hackish to read the cert as root)
@@ -344,10 +348,9 @@ def PrepRapi(options, _):
 
   users.Load(pathutils.RAPI_USERS_FILE)
 
-  server = \
-    http.server.HttpServer(mainloop, options.bind_address, options.port,
-                           handler,
-                           ssl_params=options.ssl_params, ssl_verify_peer=False)
+  server = http.server.HttpServer(
+      mainloop, options.bind_address, options.port, options.max_clients,
+      handler, ssl_params=options.ssl_params, ssl_verify_peer=False)
   server.Start()
 
   return (mainloop, server)
@@ -377,6 +380,10 @@ def Main():
                     default=False, action="store_true",
                     help=("Disable anonymous HTTP requests and require"
                           " authentication"))
+  parser.add_option("--max-clients", dest="max_clients",
+                    default=20, type="int",
+                    help="Number of simultaneous connections accepted"
+                    " by ganeti-rapi")
 
   daemon.GenericMain(constants.RAPI, parser, CheckRapi, PrepRapi, ExecRapi,
                      default_ssl_cert=pathutils.RAPI_CERT_FILE,
index 0fb592b..79a66f0 100644 (file)
@@ -172,13 +172,13 @@ def AddAuthorizedKeys(file_obj, keys):
                            in key_field_list
                            if split_key != line_key]
       nl = line.endswith("\n")
-    else:
-      if not nl:
-        f.write("\n")
-      for (key, _) in key_field_list:
-        f.write(key.rstrip("\r\n"))
-        f.write("\n")
-      f.flush()
+
+    if not nl:
+      f.write("\n")
+    for (key, _) in key_field_list:
+      f.write(key.rstrip("\r\n"))
+      f.write("\n")
+    f.flush()
   finally:
     f.close()
 
@@ -728,7 +728,7 @@ def InitPubKeyFile(master_uuid, key_type, key_file=pathutils.SSH_PUB_KEYS):
   AddPublicKey(master_uuid, key, key_file=key_file)
 
 
-class SshRunner:
+class SshRunner(object):
   """Wrapper for SSH commands.
 
   """
index 81e60d0..138defb 100644 (file)
@@ -1118,9 +1118,8 @@ class RADOSBlockDevice(base.BlockDev):
         base.ThrowError("Cannot parse rbd showmapped output expected %s fields,"
                         " found %s", allfields, field_cnt)
 
-    matched_lines = \
-      filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
-             splitted_lines)
+    matched_lines = [l for l in splitted_lines
+                     if len(l) == allfields and l[volumefield] == volume_name]
 
     if len(matched_lines) > 1:
       base.ThrowError("rbd volume %s mapped more than once", volume_name)
index ef825da..4525268 100644 (file)
@@ -267,7 +267,7 @@ class _LvmBase(_Base): # pylint: disable=W0223
 
         if callable(mapper):
           # we got a function, call it with all the declared fields
-          val = mapper(*values) # pylint: disable=W0142
+          val = mapper(*values)
         elif len(values) == 1:
           assert mapper is None, ("Invalid mapper value (neither callable"
                                   " nor None) for one-element fields")
index c7b8912..4f6cd77 100644 (file)
@@ -102,8 +102,8 @@ class DRBD8(object):
 
     """
     info = DRBD8.GetProcInfo()
-    return filter(lambda m: not info.GetMinorStatus(m).is_unconfigured,
-                  info.GetMinors())
+    return [m for m in info.GetMinors()
+            if not info.GetMinorStatus(m).is_unconfigured]
 
   @staticmethod
   def FindUnusedMinor():
index 99605f1..9f64010 100644 (file)
 """DRBD information parsing utilities"""
 
 import errno
-import pyparsing as pyp
 import re
 
+import pyparsing as pyp
+
 from ganeti import constants
 from ganeti import utils
 from ganeti import errors
@@ -164,7 +165,7 @@ class DRBD8Info(object):
 
   """
 
-  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.(\d+))?"
+  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:([.-])(\d+))?"
                            r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
   _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
 
@@ -180,6 +181,7 @@ class DRBD8Info(object):
       - k_minor
       - k_point
       - k_fix (only on some drbd versions)
+      - k_fix_separator (only when k_fix is present)
       - api
       - proto
       - proto2 (only on drbd > 8.2.X)
@@ -194,8 +196,8 @@ class DRBD8Info(object):
     version = self.GetVersion()
     retval = "%d.%d.%d" % \
              (version["k_major"], version["k_minor"], version["k_point"])
-    if "k_fix" in version:
-      retval += ".%s" % version["k_fix"]
+    if ("k_fix_separator" in version) and ("k_fix" in version):
+      retval += "%s%s" % (version["k_fix_separator"], version["k_fix"])
 
     retval += " (api:%d/proto:%d" % (version["api"], version["proto"])
     if "proto2" in version:
@@ -230,13 +232,14 @@ class DRBD8Info(object):
       "k_major": int(values[0]),
       "k_minor": int(values[1]),
       "k_point": int(values[2]),
-      "api": int(values[4]),
-      "proto": int(values[5]),
+      "api": int(values[5]),
+      "proto": int(values[6]),
       }
-    if values[3] is not None:
-      retval["k_fix"] = values[3]
-    if values[6] is not None:
-      retval["proto2"] = values[6]
+    if (values[3] is not None) and (values[4] is not None):
+      retval["k_fix_separator"] = values[3]
+      retval["k_fix"] = values[4]
+    if values[7] is not None:
+      retval["proto2"] = values[7]
 
     return retval
 
index 75961fc..7dd5266 100644 (file)
@@ -350,8 +350,8 @@ def _GetForbiddenFileStoragePaths():
     ])
 
   for prefix in ["", "/usr", "/usr/local"]:
-    paths.update(["%s/%s" % (prefix, s) for s in
-                     ["bin", "lib", "lib32", "lib64", "sbin"]])
+    paths.update(
+        "%s/%s" % (prefix, s) for s in ["bin", "lib", "lib32", "lib64", "sbin"])
 
   return compat.UniqueFrozenset(map(os.path.normpath, paths))
 
@@ -371,7 +371,7 @@ def _ComputeWrongFileStoragePaths(paths,
   def _Check(path):
     return (not os.path.isabs(path) or
             path in _forbidden or
-            filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
+            [p for p in _forbidden if utils.IsBelowDir(p, path)])
 
   return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
 
index cea8a70..a32e4db 100755 (executable)
@@ -416,7 +416,7 @@ class JobHandler(FeedbackAccumulator):
       cli.SetGenericOpcodeOpts(ops, self.opts)
       self.queued_ops.append((ops, name, post_process))
     else:
-      val = self.ExecOp(self.queue_retry, *ops) # pylint: disable=W0142
+      val = self.ExecOp(self.queue_retry, *ops)
       if post_process is not None:
         post_process()
       return val
@@ -458,7 +458,7 @@ class JobHandler(FeedbackAccumulator):
     self.ClearFeedbackBuf()
     jex = cli.JobExecutor(cl=self.cl, feedback_fn=self.Feedback)
     for ops, name, _ in jobs:
-      jex.QueueJob(name, *ops) # pylint: disable=W0142
+      jex.QueueJob(name, *ops)
     try:
       results = jex.GetResults()
     except Exception, err: # pylint: disable=W0703
@@ -677,8 +677,10 @@ class Burner(JobHandler):
                                     hypervisor=self.hypervisor,
                                     osparams=self.opts.osparams,
                                     )
-      remove_instance = lambda name: lambda: self.to_rem.append(name)
-      self.ExecOrQueue(instance, [op], post_process=remove_instance(instance))
+      # NB the i=instance default param is needed here so the lambda captures
+      # the variable. See https://docs.python.org/2/faq/programming.html#id11
+      rm_inst = lambda i=instance: self.to_rem.append(i) # pylint: disable=C0322
+      self.ExecOrQueue(instance, [op], post_process=rm_inst)
 
   @_DoBatch(False)
   def BurnModifyRuntimeMemory(self):
index d8f1588..8cac3de 100644 (file)
 """
 
 import logging
-import OpenSSL
 import os
 import time
+
 from cStringIO import StringIO
 
+import OpenSSL
+
 from ganeti import constants
 from ganeti import errors
 from ganeti import pathutils
index 1324db8..dc35500 100644 (file)
@@ -93,9 +93,8 @@ def Main():
       (pathutils.CLUSTER_CONF_FILE, True),
       (pathutils.CLUSTER_DOMAIN_SECRET_FILE, True),
       ]
-    clean_files.extend((s, True) for s in pathutils.ALL_CERT_FILES)
-    clean_files.extend((s, False) for s in
-                           ssconf.SimpleStore().GetFileList())
+    clean_files.extend((f, True) for f in pathutils.ALL_CERT_FILES)
+    clean_files.extend((f, False) for f in ssconf.SimpleStore().GetFileList())
 
     if not opts.yes_do_it:
       cli.ToStderr("Cleaning a node is irreversible. If you really want to"
index 778c2f5..c1cd684 100644 (file)
@@ -310,7 +310,7 @@ def RequestUnusedUid(all_uids):
   taken_uids = list(taken_uids)
   random.shuffle(taken_uids)
 
-  for uid in (unused_uids + taken_uids):
+  for uid in unused_uids + taken_uids:
     try:
       # Create the lock file
       # Note: we don't care if it exists. Only the fact that we can
index ce89869..b8a9486 100644 (file)
@@ -533,7 +533,7 @@ def SplitTime(value):
 
   """
   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
-
+  # pylint: disable=C0122
   assert 0 <= seconds, \
     "Seconds must be larger than or equal to 0, but are %s" % seconds
   assert 0 <= microseconds <= 999999, \
@@ -551,7 +551,7 @@ def MergeTime(timetuple):
 
   """
   (seconds, microseconds) = timetuple
-
+  # pylint: disable=C0122
   assert 0 <= seconds, \
     "Seconds must be larger than or equal to 0, but are %s" % seconds
   assert 0 <= microseconds <= 999999, \
index f4f1074..4b8b5ac 100644 (file)
@@ -104,8 +104,6 @@ def FindDuplicates(seq):
   return list(dup)
 
 
-#pylint: disable=W0142
-# (use of *-magic in argument list)
 def GetRepeatedKeys(*dicts):
   """Return the set of keys defined multiple times in the given dicts.
 
index 9e74d68..49bab0d 100644 (file)
@@ -190,7 +190,7 @@ def WriteFile(file_name, fn=None, data=None,
     raise errors.ProgrammerError("Both atime and mtime must be either"
                                  " set or None")
 
-  if not keep_perms in KEEP_PERMS_VALUES:
+  if keep_perms not in KEEP_PERMS_VALUES:
     raise errors.ProgrammerError("Invalid value for keep_perms: %s" %
                                  keep_perms)
   if keep_perms == KP_ALWAYS and (uid != -1 or gid != -1 or mode is not None):
index d20fd0f..5377f29 100644 (file)
@@ -96,8 +96,8 @@ def GuessLockfileFor(name):
       prefix in LIVELOCK_DIR, or the plain name, if none
       exists.
   """
-  lockfiles = filter(lambda n: n.startswith(name),
-                     os.listdir(pathutils.LIVELOCK_DIR))
+  lockfiles = [n for n in os.listdir(pathutils.LIVELOCK_DIR)
+               if n.startswith(name)]
   if len(lockfiles) > 0:
     lockfile = NiceSort(lockfiles)[-1]
   else:
index 5933929..b1f1693 100644 (file)
@@ -361,15 +361,11 @@ def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
           # First fork
           pid = os.fork()
           if pid == 0:
-            try:
-              # Child process, won't return
-              _StartDaemonChild(errpipe_read, errpipe_write,
-                                pidpipe_read, pidpipe_write,
-                                cmd, cmd_env, cwd,
-                                output, output_fd, pidfile)
-            finally:
-              # Well, maybe child process failed
-              os._exit(1) # pylint: disable=W0212
+            # Try to start child process, will either execve or exit on failure.
+            _StartDaemonChild(errpipe_read, errpipe_write,
+                              pidpipe_read, pidpipe_write,
+                              cmd, cmd_env, cwd,
+                              output, output_fd, pidfile)
         finally:
           utils_wrapper.CloseFdNoError(errpipe_write)
 
@@ -536,6 +532,7 @@ def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
   @return: (out, err, status)
 
   """
+  # pylint: disable=R0101
   poller = select.poll()
 
   if interactive:
@@ -936,12 +933,12 @@ def Daemonize(logfile):
 
   # this might fail
   pid = os.fork()
-  if (pid == 0):  # The first child.
+  if pid == 0:  # The first child.
     SetupDaemonEnv()
 
     # this might fail
     pid = os.fork() # Fork a second child.
-    if (pid == 0):  # The second child.
+    if pid == 0:  # The second child.
       utils_wrapper.CloseFdNoError(rpipe)
     else:
       # exit() or _exit()?  See below.
@@ -1099,7 +1096,7 @@ def CloseFDs(noclose_fds=None):
     MAXFD = 1024
 
   maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
-  if (maxfd == resource.RLIM_INFINITY):
+  if maxfd == resource.RLIM_INFINITY:
     maxfd = MAXFD
 
   # Iterate through and close all file descriptors (except the standard ones)
index 895cc0e..6ff45e7 100644 (file)
@@ -170,7 +170,6 @@ def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
   while True:
     retry_args = []
     try:
-      # pylint: disable=W0142
       return fn(*args)
     except RetryAgain, err:
       retry_args = err.args
@@ -181,7 +180,6 @@ def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
     remaining_time = end_time - _time_fn()
 
     if remaining_time <= 0.0:
-      # pylint: disable=W0142
       raise RetryTimeout(*retry_args)
 
     assert remaining_time > 0.0
@@ -218,7 +216,6 @@ def SimpleRetry(expected, fn, delay, timeout, args=None, wait_fn=time.sleep,
   rdict = {}
 
   def helper(*innerargs):
-    # pylint: disable=W0142
     result = rdict["result"] = fn(*innerargs)
     if not ((callable(expected) and expected(result)) or result == expected):
       raise RetryAgain()
index c4fa980..13a9487 100644 (file)
 """
 
 import logging
-import OpenSSL
 import os
-import uuid as uuid_module
 import time
+import uuid as uuid_module
+
+import OpenSSL
 
 from ganeti.utils import io
 from ganeti.utils import x509
index 4bb0f81..8e40983 100644 (file)
@@ -564,8 +564,7 @@ class LineSplitter(object):
 
     if args:
       # Python 2.4 doesn't have functools.partial yet
-      self._line_fn = \
-        lambda line: line_fn(line, *args) # pylint: disable=W0142
+      self._line_fn = lambda line: line_fn(line, *args)
     else:
       self._line_fn = line_fn
 
@@ -687,7 +686,7 @@ def FilterEmptyLinesAndComments(text):
   @rtype: list
 
   """
-  return [line for line in map(lambda s: s.strip(), text.splitlines())
+  return [line for line in [s.strip() for s in text.splitlines()]
           # Ignore empty lines and comments
           if line and not line.startswith("#")]
 
index dde88f9..2e4aa81 100644 (file)
 
 """
 
-import time
-import OpenSSL
-import re
-import datetime
 import calendar
+import datetime
 import errno
 import logging
+import re
+import time
+
+import OpenSSL
 
 from ganeti import errors
 from ganeti import constants
index 5a557c8..b8fecf9 100644 (file)
@@ -964,7 +964,7 @@ def Main():
     logging.error("Job queue is full, can't query cluster state")
   except errors.JobQueueDrainError:
     logging.error("Job queue is drained, can't maintain cluster state")
-  except Exception, err:
+  except Exception, err: # pylint: disable=W0703
     logging.exception(str(err))
     return constants.EXIT_FAILURE
 
index 44b7a47..e0c4e3c 100644 (file)
@@ -201,7 +201,7 @@ class BaseWorker(threading.Thread, object):
           logging.debug("Starting task %r, priority %s", args, priority)
           assert self.getName() == self._worker_id
           try:
-            self.RunTask(*args) # pylint: disable=W0142
+            self.RunTask(*args)
           finally:
             self.SetTaskName(None)
           logging.debug("Done with task %r, priority %s", args, priority)
index 3321cb8..df725e4 100644 (file)
@@ -9,8 +9,9 @@ ganeti-noded - Ganeti node daemon
 Synopsis
 --------
 
-**ganeti-noded** [-f] [-d] [-p *PORT*] [-b *ADDRESS*] [-i *INTERFACE*]
-[--no-mlock] [--syslog] [--no-ssl] [-K *SSL_KEY_FILE*] [-C *SSL_CERT_FILE*]
+| **ganeti-noded** [-f] [-d] [-p *PORT*] [-b *ADDRESS*] [-i *INTERFACE*]
+| [\--max-clients *CLIENTS*] [\--no-mlock] [\--syslog] [\--no-ssl]
+| [-K *SSL_KEY_FILE*] [-C *SSL_CERT_FILE*]
 
 DESCRIPTION
 -----------
@@ -38,6 +39,11 @@ option.  The ``-b`` option can be used to specify the address to bind
 to (defaults to ``0.0.0.0``); alternatively, the ``-i`` option can be
 used to specify the interface to bind do.
 
+The maximum number of simultaneous client connections may be configured
+with the ``--max-clients`` option. This defaults to 20. Connections
+above this count are accepted, but no responses are sent until enough
+connections are closed.
+
 Ganeti noded communication is protected via SSL, with a key
 generated at cluster init time. This can be disabled with the
 ``--no-ssl`` option, or a different SSL key and certificate can be
index 3cded7b..67c1b9e 100644 (file)
@@ -10,8 +10,8 @@ Synopsis
 --------
 
 | **ganeti-rapi** [-d] [-f] [-p *PORT*] [-b *ADDRESS*] [-i *INTERFACE*]
-| [\--no-ssl] [-K *SSL_KEY_FILE*] [-C *SSL_CERT_FILE*]
-| [\--require-authentication]
+| [\--max-clients *CLIENTS*] [\--no-ssl] [-K *SSL_KEY_FILE*]
+| [-C *SSL_CERT_FILE*] | [\--require-authentication]
 
 DESCRIPTION
 -----------
@@ -34,6 +34,11 @@ it will not be able to reach the RAPI interface and will attempt to
 restart it all the time. Alternatively to setting the IP with ``--b``,
 the ``-i`` option can be used to specify the interface to bind do.
 
+The maximum number of simultaneous client connections may be configured
+with the ``--max-clients`` option. This defaults to 20. Connections
+above this count are accepted, but no responses are sent until enough
+connections are closed.
+
 See the *Ganeti remote API* documentation for further information.
 
 Requests are logged to ``@LOCALSTATEDIR@/log/ganeti/rapi-daemon.log``,
index 47be2b8..342eeff 100644 (file)
--- a/pylintrc
+++ b/pylintrc
@@ -20,7 +20,6 @@ evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / state
 comment = yes
 
 [BASIC]
-required-attributes =
 # disabling docstring checks since we have way too many without (complex
 # inheritance hierarchies)
 #no-docstring-rgx = __.*__
@@ -53,7 +52,6 @@ dummy-variables-rgx = _
 additional-builtins =
 
 [CLASSES]
-ignore-iface-methods =
 defining-attr-methods = __init__,__new__,setUp
 valid-classmethod-first-arg = cls,mcs
 
index d61735a..686c5ef 100644 (file)
@@ -19,7 +19,6 @@ evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / state
 comment = yes
 
 [BASIC]
-required-attributes =
 # disabling docstring checks since we have way too many without (complex
 # inheritance hierarchies)
 #no-docstring-rgx = __.*__
@@ -51,7 +50,6 @@ dummy-variables-rgx = _
 additional-builtins =
 
 [CLASSES]
-ignore-iface-methods =
 defining-attr-methods = __init__,__new__,setUp
 
 [DESIGN]
index f84daf8..6035e4f 100644 (file)
@@ -473,7 +473,7 @@ class _QaConfig(object):
     """Deletes a value from the configuration.
 
     """
-    del(self._data[key])
+    del self._data[key]
 
   def __len__(self):
     """Return the number of configuration items.
index 81e9223..859f080 100644 (file)
@@ -183,7 +183,7 @@ genPyUDSRpcStub className constName = liftM (header $+$) .
                                       namesToClass className stubCode
   where
     header = text "# This file is automatically generated, do not edit!" $+$
-             text "# pylint: disable-all"
+             text "# pylint: skip-file"
     stubCode =
       abstrMethod genericInvokeName [ text "method", text "*args"] $+$
       method socketPathName [] (
index ae7bf81..a0bfe99 100644 (file)
@@ -80,8 +80,14 @@ fsyncFileChecked path =
 atomicUpdateFile :: (MonadBaseControl IO m)
                  => FilePath -> (FilePath -> Handle -> m a) -> m a
 atomicUpdateFile path action = do
+  -- Put a separator on the filename pattern to produce temporary filenames
+  -- such as job-1234-NNNNNN.tmp instead of job-1234NNNNNN. The latter can cause
+  -- problems (as well as user confusion) because temporary filenames have the
+  -- same format as real filenames, and anything that scans a directory won't be
+  -- able to tell them apart.
+  let filenameTemplate = takeBaseName path ++ "-.tmp"
   (tmppath, tmphandle) <- liftBase $ openBinaryTempFile (takeDirectory path)
-                                                        (takeBaseName path)
+                                                        filenameTemplate
   r <- L.finally (action tmppath tmphandle)
                  (liftBase (hClose tmphandle >> fsyncFileChecked tmppath))
   -- if all went well, rename the file
index 4e459f3..b630154 100644 (file)
@@ -167,7 +167,6 @@ class CmdlibTestCase(testutils.GanetiTestCase):
     while self._cleanups:
       f, args, kwargs = self._cleanups.pop(-1)
       try:
-        # pylint: disable=W0142
         f(*args, **kwargs)
       except BaseException, e:
         sys.stderr.write('Error in cleanup: %s\n' % e)
@@ -439,7 +438,6 @@ def withLockedLU(func):
       prepare_fn = getattr(test, "PrepareLU")
       assert callable(prepare_fn)
 
-    # pylint: disable=W0142
     def callWithLU(lu):
       if prepare_fn:
         prepare_fn(lu)
index 16b1e0a..7c8cf1e 100755 (executable)
@@ -174,7 +174,7 @@ class TestQmpMessage(testutils.GanetiTestCase):
     message = hv_kvm.QmpMessage(test_data)
 
     oldLen = len(message)
-    del(message[toDelete])
+    del message[toDelete]
     newLen = len(message)
     self.assertEqual(oldLen - 1, newLen)
 
index 902cc1b..f5a5245 100755 (executable)
@@ -50,6 +50,7 @@ class TestDRBD8(testutils.GanetiTestCase):
       "version: 8.0.12 (api:76/proto:86-91)",
       "version: 8.2.7 (api:88/proto:0-100)",
       "version: 8.3.7.49 (api:188/proto:13-191)",
+      "version: 8.4.8-1 (api:1/proto:86-101)",
     ]
     result = [
       {
@@ -79,10 +80,21 @@ class TestDRBD8(testutils.GanetiTestCase):
         "k_major": 8,
         "k_minor": 3,
         "k_point": 7,
+        "k_fix_separator": ".",
         "k_fix": "49",
         "api": 188,
         "proto": 13,
         "proto2": "191",
+      },
+      {
+        "k_major": 8,
+        "k_minor": 4,
+        "k_point": 8,
+        "k_fix_separator": "-",
+        "k_fix": "1",
+        "api": 1,
+        "proto": 86,
+        "proto2": "101",
       }
     ]
     for d, r in zip(data, result):
index 473bede..8267fa0 100644 (file)
@@ -362,7 +362,7 @@ class ConfigMock(config.ConfigWriter):
     return net
 
   def AddOrphanDisk(self, **params):
-    disk = self.CreateDisk(**params)  # pylint: disable=W0142
+    disk = self.CreateDisk(**params)
     self._UnlockedAddDisk(disk)
 
   def ConnectNetworkToGroup(self, net, group, netparams=None):
index 8af20df..b8a9061 100755 (executable)
@@ -448,10 +448,7 @@ class Merger(object):
       check_params_strict.append("shared_file_storage_dir")
     check_params.extend(check_params_strict)
 
-    if self.params == _PARAMS_STRICT:
-      params_strict = True
-    else:
-      params_strict = False
+    params_strict = (self.params == _PARAMS_STRICT)
 
     for param_name in check_params:
       my_param = getattr(my_cluster, param_name)
index 0775583..96c8c4a 100755 (executable)
@@ -252,8 +252,6 @@ class TestClient(object):
       elif kwargs["type"] == constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP:
         kwargs["query"] = {constants.CONFD_REQQ_IPLIST: self.instance_ips}
 
-      # pylint: disable=W0142
-      # used ** magic
       req = confd_client.ConfdClientRequest(**kwargs)
       self.DoConfdRequestReply(req)
 
@@ -276,7 +274,6 @@ class TestClient(object):
     """
     start = time.time()
     for _ in range(self.opts.requests):
-      # pylint: disable=W0142
       req = confd_client.ConfdClientRequest(**kwargs)
       self.DoConfdRequestReply(req)
     stop = time.time()
index 9061fa0..7aebfca 100755 (executable)
@@ -247,7 +247,7 @@ def SetupSshConnection(host, username, password, use_agent, logfile):
         log = logging.getLogger(transport.get_log_channel())
         log.addHandler(handler)
 
-      transport.connect(username=username, **kwargs) # pylint: disable=W0142
+      transport.connect(username=username, **kwargs)
       WriteLog("ssh connection established using %s" % desc, logfile)
       # strange ... when establishing the session and the immediately
       # setting up the channels for sftp & shell from that, it sometimes
@@ -420,7 +420,7 @@ def HostWorker(logdir, username, password, use_agent, hostname,
     print "  %s: received KeyboardInterrupt, aborting" % hostname
     WriteLog("ERROR: ABORT_KEYBOARD_INTERRUPT", logfile)
     result = 1
-  except Exception, err:
+  except Exception, err: # pylint: disable=W0703
     result = 1
     trace = traceback.format_exc()
     msg = "ERROR: UNHANDLED_EXECPTION_ERROR: %s\nTrace: %s" % (err, trace)
index 32474d5..5f33363 100755 (executable)
@@ -51,8 +51,6 @@ from ganeti import compat
 from ganeti import rapi
 from ganeti import errors
 
-import ganeti.rapi.client # pylint: disable=W0611
-import ganeti.rapi.client_utils
 from ganeti.rapi.client import UsesRapiClient
 
 
@@ -416,7 +414,7 @@ class MoveRuntime(object):
       errmsg = None
     except Abort:
       errmsg = "Aborted"
-    except Exception, err:
+    except Exception, err:  # pylint: disable=W0703
       logging.exception("Caught unhandled exception")
       errmsg = str(err)
 
@@ -945,9 +943,9 @@ def _CheckInstanceOptions(parser, options, instance_names):
       options.nics = cli.ParseNicOption(options.nics)
   else:
     # Moving more than one instance
-    if (options.dest_instance_name or options.dest_primary_node or
-        options.dest_secondary_node or options.hvparams or
-        options.beparams or options.osparams or options.nics):
+    if compat.any(options.dest_instance_name, options.dest_primary_node,
+                  options.dest_secondary_node, options.hvparams,
+                  options.beparams, options.osparams, options.nics):
       parser.error("The options --dest-instance-name, --dest-primary-node,"
                    " --dest-secondary-node, --hypervisor-parameters,"
                    " --backend-parameters, --os-parameters and --net can"
index fb6f986..bce60ce 100755 (executable)
@@ -31,17 +31,17 @@ XEN_CMD="$1"
 INSTANCE="$2"
 
 unpause() {
-  ispaused=$(xm list -l "$INSTANCE" 2>/dev/null |
-             sed -n 's/^[[:blank:]]*(state ..\(.\)...)/\1/p')
+  ispaused=$(${XEN_CMD} list $INSTANCE 2>/dev/null | tail -n1 |
+             awk '{print $5}' | sed -n 's/..\(.\).../\1/p')
   [[ "$ispaused" == "p" ]] || return
-  # As there is no way to be sure when xm console has actually connected to the
+  # As there is no way to be sure when xen console has actually connected to the
   # instance, sleep for a few seconds before unpausing the instance. This is a
   # tradeoff between missing some console output if the node is overloaded and
   # making the user wait everytime when the node isn't so busy.
   sleep 3
   # Send \r\n after notice as terminal is in raw mode
   printf "Instance $INSTANCE is paused, unpausing\r\n"
-  xm unpause "$INSTANCE"
+  $(${XEN_CMD} unpause "$INSTANCE")
 }
 
 unpause &