diff --git a/Makefile.am b/Makefile.am index 915638c323169fcf3c4b76bbb84daadd3ab3dd86..fde2b660a64f3318d5c50926cc21f2e44d021c44 100644 --- a/Makefile.am +++ b/Makefile.am @@ -455,6 +455,7 @@ qa_scripts = \ qa/qa_daemon.py \ qa/qa_env.py \ qa/qa_error.py \ + qa/qa_group.py \ qa/qa_instance.py \ qa/qa_node.py \ qa/qa_os.py \ @@ -633,6 +634,8 @@ TEST_FILES = \ test/data/proc_drbd8.txt \ test/data/proc_drbd80-emptyline.txt \ test/data/proc_drbd83.txt \ + test/data/proc_drbd83_sync.txt \ + test/data/proc_drbd83_sync_krnl2.6.39.txt \ test/data/sys_drbd_usermode_helper.txt \ test/import-export_unittest-helper @@ -773,6 +776,17 @@ standalone_python_modules = \ lib/rapi/client.py \ tools/ganeti-listrunner +pep8_python_code = \ + ganeti \ + ganeti/http/server.py \ + $(dist_sbin_SCRIPTS) \ + $(dist_tools_PYTHON) \ + $(pkglib_python_scripts) \ + $(BUILD_BASH_COMPLETION) \ + $(DOCPP) \ + $(PYTHON_BOOTSTRAP) \ + qa + test/daemon-util_unittest.bash: daemons/daemon-util test/ganeti-cleaner_unittest.bash: daemons/ganeti-cleaner @@ -885,7 +899,7 @@ lib/_autoconf.py: Makefile | lib/.dir echo ''; \ echo '"""'; \ echo ''; \ - echo '# pylint: disable-msg=C0301,C0324'; \ + echo '# pylint: disable=C0301,C0324'; \ echo '# because this is autogenerated, we do not want'; \ echo '# style warnings' ; \ echo ''; \ @@ -951,7 +965,7 @@ lib/_vcsversion.py: Makefile vcs-version | lib/.dir echo ''; \ echo '"""'; \ echo ''; \ - echo '# pylint: disable-msg=C0301,C0324'; \ + echo '# pylint: disable=C0301,C0324'; \ echo '# because this is autogenerated, we do not want'; \ echo '# style warnings' ; \ echo ''; \ @@ -1000,7 +1014,7 @@ $(PYTHON_BOOTSTRAP): Makefile | $(all_dirfiles) echo; \ echo '"""Bootstrap script for L{$(MODULE)}"""'; \ echo; \ - echo '# pylint: disable-msg=C0103'; \ + echo '# pylint: disable=C0103'; \ echo '# C0103: Invalid name'; \ echo; \ echo 'import sys'; \ @@ -1009,9 +1023,9 @@ $(PYTHON_BOOTSTRAP): Makefile | $(all_dirfiles) echo '# Temporarily alias commands until bash completion'; \ echo '# generator is changed'; \ echo 'if hasattr(main, "commands"):'; \ - echo ' commands = main.commands # pylint: disable-msg=E1101'; \ + echo ' commands = main.commands # pylint: disable=E1101'; \ echo 'if hasattr(main, "aliases"):'; \ - echo ' aliases = main.aliases # pylint: disable-msg=E1101'; \ + echo ' aliases = main.aliases # pylint: disable=E1101'; \ echo; \ echo 'if __name__ == "__main__":'; \ echo ' sys.exit(main.Main())'; \ @@ -1095,7 +1109,7 @@ lint: $(BUILT_SOURCES) echo '"pep8" not found during configure' >&2; \ else \ $(PEP8) --repeat --ignore='$(PEP8_IGNORE)' --exclude='$(PEP8_EXCLUDE)' \ - $(lint_python_code); \ + $(pep8_python_code); \ fi $(PYLINT) $(LINT_OPTS) $(lint_python_code) cd $(top_srcdir)/qa && \ @@ -1187,7 +1201,7 @@ hs-apidoc: $(HS_BUILT_SRCS) rm -rf $(APIDOC_HS_DIR)/* @mkdir_p@ $(APIDOC_HS_DIR)/Ganeti/HTools/Program $(HSCOLOUR) -print-css > $(APIDOC_HS_DIR)/Ganeti/hscolour.css - ln -s ../hscolour.css $(APIDOC_HS_DIR)/Ganeti/HTools/hscolour.css + $(LN_S) ../hscolour.css $(APIDOC_HS_DIR)/Ganeti/HTools/hscolour.css set -e ; \ cd htools; \ if [ "$(HTOOLS_NOCURL)" ]; \ @@ -1239,7 +1253,7 @@ hs-coverage: $(haskell_tests) @mkdir_p@ $(COVERAGE_HS_DIR) hpc markup --destdir=$(COVERAGE_HS_DIR) htools/test $(HPCEXCL) hpc report htools/test $(HPCEXCL) - ln -sf hpc_index.html $(COVERAGE_HS_DIR)/index.html + $(LN_S) -f hpc_index.html $(COVERAGE_HS_DIR)/index.html # Special "kind-of-QA" target for htools, needs special setup (all # tools compiled with -fhpc) @@ -1247,7 +1261,7 @@ hs-coverage: $(haskell_tests) live-test: all set -e ; \ cd htools; \ - rm -f .hpc; ln -s ../.hpc .hpc; \ + rm -f .hpc; $(LN_S) ../.hpc .hpc; \ rm -f *.tix *.mix; \ ./live-test.sh; \ hpc sum --union $(HPCEXCL) $(addsuffix .tix,$(HS_PROGS:htools/%=%)) \ diff --git a/NEWS b/NEWS index a6689a3ef173d835028c499f5f240bcd4eeb1a56..2e7d18f38fa30f3c8b3a378ed8cd201c1c3b6c24 100644 --- a/NEWS +++ b/NEWS @@ -1,10 +1,10 @@ News ==== -Version 2.5.0 beta2 +Version 2.5.0 beta3 ------------------- -*(Released Mon, 22 Aug 2011)* +*(Released Wed, 31 Aug 2011)* Incompatible/important changes and bugfixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -85,6 +85,7 @@ New features is offline. - Opcodes have a new ``comment`` attribute. - Added basic SPICE support to KVM hypervisor. +- ``tools/ganeti-listrunner`` allows passing of arguments to executable. Node group improvements ~~~~~~~~~~~~~~~~~~~~~~~ @@ -128,6 +129,14 @@ Misc - DRBD metadata volumes are overwritten with zeros during disk creation. +Version 2.5.0 beta2 +------------------- + +*(Released Mon, 22 Aug 2011)* + +This was the second beta release of the 2.5 series. + + Version 2.5.0 beta1 ------------------- @@ -136,6 +145,18 @@ Version 2.5.0 beta1 This was the first beta release of the 2.5 series. +Version 2.4.4 +------------- + +*(Released Tue, 23 Aug 2011)* + +Small bug-fixes: + +- Fixed documentation for importing with ``--src-dir`` option +- Fixed a bug in ``ensure-dirs`` with queue/archive permissions +- Fixed a parsing issue with DRBD 8.3.11 in the Linux kernel + + Version 2.4.3 ------------- @@ -167,7 +188,7 @@ Many bug-fixes and a few small features: - Fixed off-by-one bug in job serial generation - ``gnt-node volumes``: Fix instance names - Fixed aliases in bash completion -- Fixed a bug in reopening log files after beeing sent a SIGHUP +- Fixed a bug in reopening log files after being sent a SIGHUP - Added a flag to burnin to allow specifying VCPU count - Bugfixes to non-root Ganeti configuration diff --git a/autotools/build-bash-completion b/autotools/build-bash-completion index eda56ab52213956b9d05a84c05e5022bf7f866f6..6eb0dbe7544d7a3cff14b43c344d371d45b880ce 100755 --- a/autotools/build-bash-completion +++ b/autotools/build-bash-completion @@ -23,7 +23,7 @@ """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # [C0103] Invalid name build-bash-completion import os @@ -246,7 +246,7 @@ class CompletionWriter: for opt in opts: # While documented, these variables aren't seen as public attributes by - # pylint. pylint: disable-msg=W0212 + # pylint. pylint: disable=W0212 opt.all_names = sorted(opt._short_opts + opt._long_opts) invalid = list(itertools.ifilterfalse(_OPT_NAME_RE.match, opt.all_names)) diff --git a/configure.ac b/configure.ac index cb0a15318f0780a537be4893cbacc0d63e9dd648..dfcd932dc936f1599a924cc44cdf601a7764a2ef 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ m4_define([gnt_version_major], [2]) m4_define([gnt_version_minor], [5]) m4_define([gnt_version_revision], [0]) -m4_define([gnt_version_suffix], [~beta2]) +m4_define([gnt_version_suffix], [~beta3]) m4_define([gnt_version_full], m4_format([%d.%d.%d%s], gnt_version_major, gnt_version_minor, diff --git a/daemons/import-export b/daemons/import-export index a63e4c24c55c93388210adad05ed75f7192a3f83..86034608de94418ff11e526138b92ee08eae0c1f 100755 --- a/daemons/import-export +++ b/daemons/import-export @@ -23,7 +23,7 @@ """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name import-export import errno @@ -353,7 +353,7 @@ def ParseOptions(): @return: Arguments to program """ - global options # pylint: disable-msg=W0603 + global options # pylint: disable=W0603 parser = optparse.OptionParser(usage=("%%prog <status-file> {%s|%s}" % (constants.IEM_IMPORT, @@ -587,7 +587,7 @@ def main(): errmsg = "Exited with status %s" % (child.returncode, ) status_file.SetExitStatus(child.returncode, errmsg) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 logging.exception("Unhandled error occurred") status_file.SetExitStatus(constants.EXIT_FAILURE, "Unhandled error occurred: %s" % (err, )) diff --git a/doc/admin.rst b/doc/admin.rst index b366def1026000222299ec2fe4812113ac21ae8b..665c0e638ccbe82fc4432c5f55c20ab2ec23a568 100644 --- a/doc/admin.rst +++ b/doc/admin.rst @@ -391,16 +391,16 @@ parameters as such: - ``root_path`` to a valid setting (e.g. ``/dev/xvda1``) - ``bootloader_path`` and ``bootloader_args`` to empty -Alternatively, you can delete the kernel management to instances, and +Alternatively, you can delegate the kernel management to instances, and use either ``pvgrub`` or the deprecated ``pygrub``. For this, you must -install the kernels and initrds in the instance, and create a valid grub +install the kernels and initrds in the instance and create a valid GRUB v1 configuration file. For ``pvgrub`` (new in version 2.4.2), you need to set: - ``kernel_path`` to point to the ``pvgrub`` loader present on the node (e.g. ``/usr/lib/xen/boot/pv-grub-x86_32.gz``) -- ``kernel_args`` to the path to the grub config file, relative to the +- ``kernel_args`` to the path to the GRUB config file, relative to the instance (e.g. ``(hd0,0)/grub/menu.lst``) - ``root_path`` **must** be empty - ``bootloader_path`` and ``bootloader_args`` to empty diff --git a/doc/design-cpu-pinning.rst b/doc/design-cpu-pinning.rst index f1b3de140f325c592c3b68062a782b0831507ebb..a4c2eb96ca4bf0634cb2bf77ec6471070cf44a71 100644 --- a/doc/design-cpu-pinning.rst +++ b/doc/design-cpu-pinning.rst @@ -48,7 +48,7 @@ Here are some usage examples:: # Pin vCPU 0 to any CPU, vCPU 1 to CPUs 1, 3, 4 or 5, and CPU 2 to # CPU 0 - gnt-instance modify -H cpu_mask=all:1\\,3-4:0 my-inst + gnt-instance modify -H cpu_mask=all:1\\,3-5:0 my-inst # Pin entire VM to CPU 0 gnt-instance modify -H cpu_mask=0 my-inst @@ -56,13 +56,13 @@ Here are some usage examples:: # Turn off CPU pinning (default setting) gnt-instance modify -H cpu_mask=all my-inst -Assuming an instance has 2 vCPUs, the following commands will fail:: +Assuming an instance has 3 vCPUs, the following commands will fail:: # not enough mappings - gnt-instance modify -H cpu_mask=0 my-inst + gnt-instance modify -H cpu_mask=0:1 my-inst # too many - gnt-instance modify -H cpu_mask=2:1:1 my-inst + gnt-instance modify -H cpu_mask=2:1:1:all my-inst Validation ---------- @@ -102,8 +102,8 @@ indicated by CPU pinning information, instance failover will fail. In case of emergency, to force failover to ignore mismatching CPU information, the following switch can be used: -``gnt-instance failover --ignore-cpu-mismatch my-inst``. -This command will try to fail the instance with the current cpu mask, +``gnt-instance failover --fix-cpu-mismatch my-inst``. +This command will try to failover the instance with the current cpu mask, but if that fails, it will change the mask to be "all". Migration @@ -127,8 +127,7 @@ Configuration file ------------------ The pinning information is kept for each instance's hypervisor -params section of the configuration file as -``cpu_mask: [ [ a ], [ b, c ], [ d ] ]`` +params section of the configuration file as the original string. Xen --- diff --git a/doc/rapi.rst b/doc/rapi.rst index 5ea41ca1ff58d85be802b9382cc6865278d2fe67..efb4a4b5ee44253a443cfc85a5d3a328796da60a 100644 --- a/doc/rapi.rst +++ b/doc/rapi.rst @@ -960,20 +960,23 @@ console. Contained keys: constants.CONS_MESSAGE, constants.CONS_SSH, constants.CONS_VNC, + constants.CONS_SPICE, ]) ``instance`` Instance name. ``kind`` Console type, one of :pyeval:`constants.CONS_SSH`, - :pyeval:`constants.CONS_VNC` or :pyeval:`constants.CONS_MESSAGE`. + :pyeval:`constants.CONS_VNC`, :pyeval:`constants.CONS_SPICE` + or :pyeval:`constants.CONS_MESSAGE`. ``message`` Message to display (:pyeval:`constants.CONS_MESSAGE` type only). ``host`` - Host to connect to (:pyeval:`constants.CONS_SSH` and - :pyeval:`constants.CONS_VNC` only). + Host to connect to (:pyeval:`constants.CONS_SSH`, + :pyeval:`constants.CONS_VNC` or :pyeval:`constants.CONS_SPICE` only). ``port`` - TCP port to connect to (:pyeval:`constants.CONS_VNC` only). + TCP port to connect to (:pyeval:`constants.CONS_VNC` or + :pyeval:`constants.CONS_SPICE` only). ``user`` Username to use (:pyeval:`constants.CONS_SSH` only). ``command`` diff --git a/htools/Ganeti/HTools/Cluster.hs b/htools/Ganeti/HTools/Cluster.hs index 9e946936658352701d4f56753686cb069f62a65f..3d1b9f23fc87ef72e39c4b46cb584efab5161340 100644 --- a/htools/Ganeti/HTools/Cluster.hs +++ b/htools/Ganeti/HTools/Cluster.hs @@ -61,7 +61,6 @@ module Ganeti.HTools.Cluster , tryAlloc , tryMGAlloc , tryReloc - , tryEvac , tryNodeEvac , tryChangeGroup , collapseFailures @@ -811,45 +810,6 @@ tryReloc _ _ _ reqn _ = fail $ "Unsupported number of relocation \ \destinations required (" ++ show reqn ++ "), only one supported" --- | Change an instance's secondary node. -evacInstance :: (Monad m) => - [Ndx] -- ^ Excluded nodes - -> Instance.List -- ^ The current instance list - -> (Node.List, AllocSolution) -- ^ The current state - -> Idx -- ^ The instance to evacuate - -> m (Node.List, AllocSolution) -evacInstance ex_ndx il (nl, old_as) idx = do - -- FIXME: hardcoded one node here - - -- Longer explanation: evacuation is currently hardcoded to DRBD - -- instances (which have one secondary); hence, even if the - -- IAllocator protocol can request N nodes for an instance, and all - -- the message parsing/loading pass this, this implementation only - -- supports one; this situation needs to be revisited if we ever - -- support more than one secondary, or if we change the storage - -- model - new_as <- tryReloc nl il idx 1 ex_ndx - case asSolutions new_as of - -- an individual relocation succeeded, we kind of compose the data - -- from the two solutions - csol@(nl', _, _, _):_ -> - return (nl', new_as { asSolutions = csol:asSolutions old_as }) - -- this relocation failed, so we fail the entire evac - _ -> fail $ "Can't evacuate instance " ++ - Instance.name (Container.find idx il) ++ - ": " ++ describeSolution new_as - --- | Try to evacuate a list of nodes. -tryEvac :: (Monad m) => - Node.List -- ^ The node list - -> Instance.List -- ^ The instance list - -> [Idx] -- ^ Instances to be evacuated - -> [Ndx] -- ^ Restricted nodes (the ones being evacuated) - -> m AllocSolution -- ^ Solution list -tryEvac nl il idxs ex_ndx = do - (_, sol) <- foldM (evacInstance ex_ndx il) (nl, emptyAllocSolution) idxs - return sol - -- | Function which fails if the requested mode is change secondary. -- -- This is useful since except DRBD, no other disk template can @@ -1107,10 +1067,11 @@ tryNodeEvac _ ini_nl ini_il mode idxs = splitCluster ini_nl ini_il (fin_nl, fin_il, esol) = foldl' (\state@(nl, il, _) inst -> - let gdx = instancePriGroup nl inst in + let gdx = instancePriGroup nl inst + pdx = Instance.pNode inst in updateEvacSolution state (Instance.idx inst) $ availableGroupNodes group_ndx - excl_ndx gdx >>= + (IntSet.insert pdx excl_ndx) gdx >>= nodeEvacInstance nl il mode inst gdx ) (ini_nl, ini_il, emptyEvacSolution) diff --git a/htools/Ganeti/HTools/IAlloc.hs b/htools/Ganeti/HTools/IAlloc.hs index 81e6dfdf1be40878d42d3d258b6bf3718f529684..aabdd76f902b2523054454e7780cdeae3c546b43 100644 --- a/htools/Ganeti/HTools/IAlloc.hs +++ b/htools/Ganeti/HTools/IAlloc.hs @@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA module Ganeti.HTools.IAlloc ( readRequest , runIAllocator + , processRelocate ) where import Data.Either () @@ -283,6 +284,8 @@ processRelocate gl nl il idx 1 exndx = do snode = Instance.sNode inst when (snode == sorig) $ fail "Internal error: instance didn't change secondary node?!" + when (snode == pnode) $ + fail "Internal error: selected primary as new secondary?!" nodes' <- if (nodes == [pnode, snode]) then return [snode] -- only the new secondary is needed diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs index 80551ebb74541291f5de2deb05ea2413ec8e53d2..305a2bee7e7114f7a229feb5d132e6ca587b963c 100644 --- a/htools/Ganeti/HTools/QC.hs +++ b/htools/Ganeti/HTools/QC.hs @@ -908,8 +908,9 @@ prop_ClusterAllocEvac node inst = (xnl, xi, _, _):[] -> let sdx = Instance.sNode xi il' = Container.add (Instance.idx xi) xi il - in case Cluster.tryEvac xnl il' [Instance.idx xi] [sdx] of - Just _ -> True + in case IAlloc.processRelocate defGroupList xnl il' + (Instance.idx xi) 1 [sdx] of + Types.Ok _ -> True _ -> False _ -> False diff --git a/lib/asyncnotifier.py b/lib/asyncnotifier.py index 6f74542a989d39244acff5a1b139d50da9b380d2..37a2fcbd8b0f191cd81c2d63d5bd1ddb3fc9c374 100644 --- a/lib/asyncnotifier.py +++ b/lib/asyncnotifier.py @@ -26,7 +26,7 @@ import asyncore import logging try: - # pylint: disable-msg=E0611 + # pylint: disable=E0611 from pyinotify import pyinotify except ImportError: import pyinotify @@ -42,7 +42,7 @@ class AsyncNotifier(asyncore.file_dispatcher): """An asyncore dispatcher for inotify events. """ - # pylint: disable-msg=W0622,W0212 + # pylint: disable=W0622,W0212 def __init__(self, watch_manager, default_proc_fun=None, map=None): """Initializes this class. @@ -90,7 +90,7 @@ class FileEventHandlerBase(pyinotify.ProcessEvent): @param watch_manager: inotify watch manager """ - # pylint: disable-msg=W0231 + # pylint: disable=W0231 # no need to call the parent's constructor self.watch_manager = watch_manager @@ -168,7 +168,7 @@ class SingleFileEventHandler(FileEventHandlerBase): if self._watch_handle is not None and self.RemoveWatch(self._watch_handle): self._watch_handle = None - # pylint: disable-msg=C0103 + # pylint: disable=C0103 # this overrides a method in pyinotify.ProcessEvent def process_IN_IGNORED(self, event): # Since we monitor a single file rather than the directory it resides in, @@ -182,7 +182,7 @@ class SingleFileEventHandler(FileEventHandlerBase): self._watch_handle = None self._callback(False) - # pylint: disable-msg=C0103 + # pylint: disable=C0103 # this overrides a method in pyinotify.ProcessEvent def process_IN_MODIFY(self, event): # This gets called when the monitored file is modified. Note that this diff --git a/lib/backend.py b/lib/backend.py index 84a39d07de6a1e6dff287d54108046e4eafbf3a6..36da855cc33448a052366b2d9e11eb4b20248a1b 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -28,7 +28,7 @@ """ -# pylint: disable-msg=E1103 +# pylint: disable=E1103 # E1103: %s %r has no %r member (but some types could not be # inferred), because the _TryOSFromDisk returns either (True, os_obj) @@ -412,7 +412,7 @@ def LeaveCluster(modify_ssh_setup): utils.RemoveFile(constants.CONFD_HMAC_KEY) utils.RemoveFile(constants.RAPI_CERT_FILE) utils.RemoveFile(constants.NODED_CERT_FILE) - except: # pylint: disable-msg=W0702 + except: # pylint: disable=W0702 logging.exception("Error while removing cluster secrets") result = utils.RunCmd([constants.DAEMON_UTIL, "stop", constants.CONFD]) @@ -668,7 +668,7 @@ def GetBlockDevSizes(devices): blockdevs = {} for devpath in devices: - if os.path.commonprefix([DEV_PREFIX, devpath]) != DEV_PREFIX: + if not utils.IsBelowDir(DEV_PREFIX, devpath): continue try: @@ -1340,7 +1340,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info): """ # TODO: remove the obsolete "size" argument - # pylint: disable-msg=W0613 + # pylint: disable=W0613 clist = [] if disk.children: for child in disk.children: @@ -1352,7 +1352,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info): # we need the children open in case the device itself has to # be assembled try: - # pylint: disable-msg=E1103 + # pylint: disable=E1103 crdev.Open() except errors.BlockDeviceError, err: _Fail("Can't make child '%s' read-write: %s", child, err) @@ -1568,7 +1568,7 @@ def BlockdevAssemble(disk, owner, as_primary, idx): try: result = _RecursiveAssembleBD(disk, owner, as_primary) if isinstance(result, bdev.BlockDev): - # pylint: disable-msg=E1103 + # pylint: disable=E1103 result = result.dev_path if as_primary: _SymlinkBlockDev(owner, result, idx) @@ -2507,8 +2507,8 @@ def _TransformFileStorageDir(fs_dir): fs_dir = os.path.normpath(fs_dir) base_fstore = cfg.GetFileStorageDir() base_shared = cfg.GetSharedFileStorageDir() - if ((os.path.commonprefix([fs_dir, base_fstore]) != base_fstore) and - (os.path.commonprefix([fs_dir, base_shared]) != base_shared)): + if not (utils.IsBelowDir(base_fstore, fs_dir) or + utils.IsBelowDir(base_shared, fs_dir)): _Fail("File storage directory '%s' is not under base file" " storage directory '%s' or shared storage directory '%s'", fs_dir, base_fstore, base_shared) @@ -2875,12 +2875,12 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs): if not utils.IsNormAbsPath(filename): _Fail("Path '%s' is not normalized or absolute", filename) - directory = os.path.normpath(os.path.dirname(filename)) + real_filename = os.path.realpath(filename) + directory = os.path.dirname(real_filename) - if (os.path.commonprefix([constants.EXPORT_DIR, directory]) != - constants.EXPORT_DIR): - _Fail("File '%s' is not under exports directory '%s'", - filename, constants.EXPORT_DIR) + if not utils.IsBelowDir(constants.EXPORT_DIR, real_filename): + _Fail("File '%s' is not under exports directory '%s': %s", + filename, constants.EXPORT_DIR, real_filename) # Create directory utils.Makedirs(directory, mode=0750) @@ -3322,7 +3322,7 @@ def PowercycleNode(hypervisor_type): # ensure the child is running on ram try: utils.Mlockall() - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 pass time.sleep(5) hyper.PowercycleNode() @@ -3347,7 +3347,7 @@ class HooksRunner(object): hooks_base_dir = constants.HOOKS_BASE_DIR # yeah, _BASE_DIR is not valid for attributes, we use it like a # constant - self._BASE_DIR = hooks_base_dir # pylint: disable-msg=C0103 + self._BASE_DIR = hooks_base_dir # pylint: disable=C0103 def RunHooks(self, hpath, phase, env): """Run the scripts in the hooks directory. diff --git a/lib/bdev.py b/lib/bdev.py index 905ad1fc13b7f6490977776a986e6dd5d9ac95e2..d8603df8d13f31e57922423b538e945b12f5be5d 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -791,7 +791,10 @@ class DRBD8Status(object): LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)" "\s+ds:([^/]+)/(\S+)\s+.*$") SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*" - "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$") + # Due to a bug in drbd in the kernel, introduced in + # commit 4b0715f096 (still unfixed as of 2011-08-22) + "(?:\s|M)" + "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$") CS_UNCONFIGURED = "Unconfigured" CS_STANDALONE = "StandAlone" @@ -886,7 +889,7 @@ class DRBD8Status(object): self.est_time = None -class BaseDRBD(BlockDev): # pylint: disable-msg=W0223 +class BaseDRBD(BlockDev): # pylint: disable=W0223 """Base DRBD class. This class contains a few bits of common functionality between the @@ -1762,7 +1765,7 @@ class DRBD8(BaseDRBD): """ # TODO: Rewrite to not use a for loop just because there is 'break' - # pylint: disable-msg=W0631 + # pylint: disable=W0631 net_data = (self._lhost, self._lport, self._rhost, self._rport) for minor in (self._aminor,): info = self._GetDevInfo(self._GetShowData(minor)) diff --git a/lib/bootstrap.py b/lib/bootstrap.py index 3e75dfa124e2b18e4cd92e651e374d4dd8d5711f..733a8ba98a27eab9b88e7062fc41e76629ded3c6 100644 --- a/lib/bootstrap.py +++ b/lib/bootstrap.py @@ -242,7 +242,7 @@ def _InitFileStorage(file_storage_dir): return file_storage_dir -def InitCluster(cluster_name, mac_prefix, # pylint: disable-msg=R0913 +def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913 master_netdev, file_storage_dir, shared_file_storage_dir, candidate_pool_size, secondary_ip=None, vg_name=None, beparams=None, nicparams=None, ndparams=None, hvparams=None, diff --git a/lib/build/sphinx_ext.py b/lib/build/sphinx_ext.py index 474721dccdeec46626972b832f0a9d76c48ce2d5..0d2d2e3cff6ced73f9e8a32ad57787bd51bf01c2 100644 --- a/lib/build/sphinx_ext.py +++ b/lib/build/sphinx_ext.py @@ -42,7 +42,7 @@ from ganeti import opcodes from ganeti import ht from ganeti import rapi -import ganeti.rapi.rlib2 # pylint: disable-msg=W0611 +import ganeti.rapi.rlib2 # pylint: disable=W0611 COMMON_PARAM_NAMES = map(compat.fst, opcodes.OpCode.OP_PARAMS) @@ -213,7 +213,7 @@ def PythonEvalRole(role, rawtext, text, lineno, inliner, The expression's result is included as a literal. """ - # pylint: disable-msg=W0102,W0613,W0142 + # pylint: disable=W0102,W0613,W0142 # W0102: Dangerous default value as argument # W0142: Used * or ** magic # W0613: Unused argument @@ -222,7 +222,7 @@ def PythonEvalRole(role, rawtext, text, lineno, inliner, try: result = eval(code, EVAL_NS) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err), line=lineno) return ([inliner.problematic(rawtext, rawtext, msg)], [msg]) diff --git a/lib/cli.py b/lib/cli.py index f55804d04bd6809e41af4f5079e96d70351a1ea1..d7e497e58b8dee66035564b6201bb83c11be4600 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -259,7 +259,7 @@ _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES) class _Argument: - def __init__(self, min=0, max=None): # pylint: disable-msg=W0622 + def __init__(self, min=0, max=None): # pylint: disable=W0622 self.min = min self.max = max @@ -274,7 +274,7 @@ class ArgSuggest(_Argument): Value can be any of the ones passed to the constructor. """ - # pylint: disable-msg=W0622 + # pylint: disable=W0622 def __init__(self, min=0, max=None, choices=None): _Argument.__init__(self, min=min, max=max) self.choices = choices @@ -462,7 +462,7 @@ def RemoveTags(opts, args): SubmitOpCode(op, opts=opts) -def check_unit(option, opt, value): # pylint: disable-msg=W0613 +def check_unit(option, opt, value): # pylint: disable=W0613 """OptParsers custom converter for units. """ @@ -509,7 +509,7 @@ def _SplitKeyVal(opt, data): return kv_dict -def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613 +def check_ident_key_val(option, opt, value): # pylint: disable=W0613 """Custom parser for ident:key=val,key=val options. This will store the parsed values as a tuple (ident, {key: val}). As such, @@ -537,7 +537,7 @@ def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613 return retval -def check_key_val(option, opt, value): # pylint: disable-msg=W0613 +def check_key_val(option, opt, value): # pylint: disable=W0613 """Custom parser class for key=val,key=val options. This will store the parsed values as a dict {key: val}. @@ -546,7 +546,7 @@ def check_key_val(option, opt, value): # pylint: disable-msg=W0613 return _SplitKeyVal(opt, value) -def check_bool(option, opt, value): # pylint: disable-msg=W0613 +def check_bool(option, opt, value): # pylint: disable=W0613 """Custom parser for yes/no options. This will store the parsed value as either True or False. @@ -2363,8 +2363,8 @@ def GenerateTable(headers, fields, separator, data, if unitfields is None: unitfields = [] - numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142 - unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142 + numfields = utils.FieldSet(*numfields) # pylint: disable=W0142 + unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142 format_fields = [] for field in fields: diff --git a/lib/client/gnt_backup.py b/lib/client/gnt_backup.py index 4b7f81bf68f6c6f12d82d194483668cd40436a11..5999e3267c5ddcdad493517097b2efc0d7825bdb 100644 --- a/lib/client/gnt_backup.py +++ b/lib/client/gnt_backup.py @@ -20,7 +20,7 @@ """Backup related commands""" -# pylint: disable-msg=W0401,W0613,W0614,C0103 +# pylint: disable=W0401,W0613,W0614,C0103 # W0401: Wildcard import ganeti.cli # W0613: Unused argument, since all functions follow the same API # W0614: Unused import %s from wildcard import (since we need cli) diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py index 7eb11e53e1c5d9bf397dbb2a9d3148bdfdad1444..b61e8e3417968616aa28e1c875ebcdaa789eae8e 100644 --- a/lib/client/gnt_cluster.py +++ b/lib/client/gnt_cluster.py @@ -20,7 +20,7 @@ """Cluster related commands""" -# pylint: disable-msg=W0401,W0613,W0614,C0103 +# pylint: disable=W0401,W0613,W0614,C0103 # W0401: Wildcard import ganeti.cli # W0613: Unused argument, since all functions follow the same API # W0614: Unused import %s from wildcard import (since we need cli) @@ -622,7 +622,7 @@ def MasterPing(opts, args): cl = GetClient() cl.QueryClusterInfo() return 0 - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 return 1 @@ -668,7 +668,7 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, """ if new_rapi_cert and rapi_cert_filename: - ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate" + ToStderr("Only one of the --new-rapi-certificate and --rapi-certificate" " options can be specified at the same time.") return 1 @@ -685,14 +685,14 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 ToStderr("Can't load new RAPI certificate from %s: %s" % (rapi_cert_filename, str(err))) return 1 try: OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 ToStderr("Can't load new RAPI private key from %s: %s" % (rapi_cert_filename, str(err))) return 1 @@ -703,7 +703,7 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, if cds_filename: try: cds = utils.ReadFile(cds_filename) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 ToStderr("Can't load new cluster domain secret from %s: %s" % (cds_filename, str(err))) return 1 diff --git a/lib/client/gnt_debug.py b/lib/client/gnt_debug.py index 0a73525cf14f0c68d333cffd8f07df5e4064c8a4..228733b5d27b7ab02d0db59b1dc39d03db7d6bb7 100644 --- a/lib/client/gnt_debug.py +++ b/lib/client/gnt_debug.py @@ -20,7 +20,7 @@ """Debugging commands""" -# pylint: disable-msg=W0401,W0614,C0103 +# pylint: disable=W0401,W0614,C0103 # W0401: Wildcard import ganeti.cli # W0614: Unused import %s from wildcard import (since we need cli) # C0103: Invalid name gnt-backup @@ -90,7 +90,7 @@ def GenericOpCodes(opts, args): ToStdout("Loading...") for job_idx in range(opts.rep_job): for fname in args: - # pylint: disable-msg=W0142 + # 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 @@ -564,7 +564,7 @@ def TestJobqueue(opts, _): return 0 -def ListLocks(opts, args): # pylint: disable-msg=W0613 +def ListLocks(opts, args): # pylint: disable=W0613 """List all locks. @param opts: the command line options selected by the user diff --git a/lib/client/gnt_group.py b/lib/client/gnt_group.py index 2fb73e1b07b62e0194dc6e9da08154f7159bf0b5..096bb9934bce1990e725d221c87606b175d77c94 100644 --- a/lib/client/gnt_group.py +++ b/lib/client/gnt_group.py @@ -20,7 +20,7 @@ """Node group related commands""" -# pylint: disable-msg=W0401,W0614 +# pylint: disable=W0401,W0614 # W0401: Wildcard import ganeti.cli # W0614: Unused import %s from wildcard import (since we need cli) diff --git a/lib/client/gnt_instance.py b/lib/client/gnt_instance.py index 7f51327a57ee370cfbeff0ad017cfa4d50f351d3..84cc869af243e8c9d76bb5521f1b94d345d49d5c 100644 --- a/lib/client/gnt_instance.py +++ b/lib/client/gnt_instance.py @@ -20,7 +20,7 @@ """Instance related commands""" -# pylint: disable-msg=W0401,W0614,C0103 +# pylint: disable=W0401,W0614,C0103 # W0401: Wildcard import ganeti.cli # W0614: Unused import %s from wildcard import (since we need cli) # C0103: Invalid name gnt-instance @@ -87,7 +87,7 @@ def _ExpandMultiNames(mode, names, client=None): @raise errors.OpPrereqError: for invalid input parameters """ - # pylint: disable-msg=W0142 + # pylint: disable=W0142 if client is None: client = GetClient() @@ -322,7 +322,7 @@ def BatchCreate(opts, args): json_filename = args[0] try: instance_data = simplejson.loads(utils.ReadFile(json_filename)) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 ToStderr("Can't parse the instance definition file: %s" % str(err)) return 1 @@ -335,7 +335,7 @@ def BatchCreate(opts, args): # Iterate over the instances and do: # * Populate the specs with default value # * Validate the instance specs - i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103 + i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103 for name in i_names: specs = instance_data[name] specs = _PopulateWithDefaults(specs) @@ -934,6 +934,9 @@ def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout, " URL <vnc://%s:%s/>", console.instance, console.host, console.port, console.display, console.host, console.port) + elif console.kind == constants.CONS_SPICE: + feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance, + console.host, console.port) elif console.kind == constants.CONS_SSH: # Convert to string if not already one if isinstance(console.command, basestring): diff --git a/lib/client/gnt_job.py b/lib/client/gnt_job.py index 4e11eabb73cc69970a31317fea89256ec3f8a9a6..4d888ad835242b9f26469bf511caa47de9d80e74 100644 --- a/lib/client/gnt_job.py +++ b/lib/client/gnt_job.py @@ -20,7 +20,7 @@ """Job related commands""" -# pylint: disable-msg=W0401,W0613,W0614,C0103 +# pylint: disable=W0401,W0613,W0614,C0103 # W0401: Wildcard import ganeti.cli # W0613: Unused argument, since all functions follow the same API # W0614: Unused import %s from wildcard import (since we need cli) diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py index 1847b4162a43e566bc6a32615994b9758afbd429..270aff55eb63016d8c5531e0eba5f889ee0e41ea 100644 --- a/lib/client/gnt_node.py +++ b/lib/client/gnt_node.py @@ -20,7 +20,7 @@ """Node related commands""" -# pylint: disable-msg=W0401,W0613,W0614,C0103 +# pylint: disable=W0401,W0613,W0614,C0103 # W0401: Wildcard import ganeti.cli # W0613: Unused argument, since all functions follow the same API # W0614: Unused import %s from wildcard import (since we need cli) diff --git a/lib/client/gnt_os.py b/lib/client/gnt_os.py index e80aa6513a66d985e4c5893c3fb3c07046f0b15e..3554524ff92c8a34df5b5a75d3166ffb2bbe7baf 100644 --- a/lib/client/gnt_os.py +++ b/lib/client/gnt_os.py @@ -20,7 +20,7 @@ """OS scripts related commands""" -# pylint: disable-msg=W0401,W0613,W0614,C0103 +# pylint: disable=W0401,W0613,W0614,C0103 # W0401: Wildcard import ganeti.cli # W0613: Unused argument, since all functions follow the same API # W0614: Unused import %s from wildcard import (since we need cli) diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 289f6005794e4bdd0a8ab3c4ef2948d7e3fa4074..1dae379d82d813545090f15d4610468dccecf958 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -21,7 +21,7 @@ """Module implementing the master-side code.""" -# pylint: disable-msg=W0201,C0302 +# pylint: disable=W0201,C0302 # W0201 since most LU attributes are defined in CheckPrereq or similar # functions @@ -60,7 +60,7 @@ from ganeti import qlang from ganeti import opcodes from ganeti import ht -import ganeti.masterd.instance # pylint: disable-msg=W0611 +import ganeti.masterd.instance # pylint: disable=W0611 class ResultWithJobs: @@ -131,10 +131,10 @@ class LogicalUnit(object): # Used to force good behavior when calling helper functions self.recalculate_locks = {} # logging - self.Log = processor.Log # pylint: disable-msg=C0103 - self.LogWarning = processor.LogWarning # pylint: disable-msg=C0103 - self.LogInfo = processor.LogInfo # pylint: disable-msg=C0103 - self.LogStep = processor.LogStep # pylint: disable-msg=C0103 + self.Log = processor.Log # pylint: disable=C0103 + self.LogWarning = processor.LogWarning # pylint: disable=C0103 + self.LogInfo = processor.LogInfo # pylint: disable=C0103 + self.LogStep = processor.LogStep # pylint: disable=C0103 # support for dry-run self.dry_run_result = None # support for generic debug attribute @@ -322,7 +322,7 @@ class LogicalUnit(object): """ # API must be kept, thus we ignore the unused argument and could # be a function warnings - # pylint: disable-msg=W0613,R0201 + # pylint: disable=W0613,R0201 return lu_result def _ExpandAndLockInstance(self): @@ -390,7 +390,7 @@ class LogicalUnit(object): del self.recalculate_locks[locking.LEVEL_NODE] -class NoHooksLU(LogicalUnit): # pylint: disable-msg=W0223 +class NoHooksLU(LogicalUnit): # pylint: disable=W0223 """Simple LU which runs no hooks. This LU is intended as a parent for other LogicalUnits which will @@ -757,7 +757,7 @@ def _RunPostHook(lu, node_name): try: hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name]) except: - # pylint: disable-msg=W0702 + # pylint: disable=W0702 lu.LogWarning("Errors occurred running hooks on %s" % node_name) @@ -1087,7 +1087,7 @@ def _BuildInstanceHookEnvByObject(lu, instance, override=None): } if override: args.update(override) - return _BuildInstanceHookEnv(**args) # pylint: disable-msg=W0142 + return _BuildInstanceHookEnv(**args) # pylint: disable=W0142 def _AdjustCandidatePool(lu, exceptions): @@ -1371,7 +1371,7 @@ def _VerifyCertificate(filename): try: cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, utils.ReadFile(filename)) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 return (LUClusterVerifyConfig.ETYPE_ERROR, "Failed to load X509 certificate %s: %s" % (filename, err)) @@ -1486,7 +1486,7 @@ class _VerifyErrors(object): if args: msg = msg % args # then format the whole message - if self.op.error_codes: # This is a mix-in. pylint: disable-msg=E1101 + if self.op.error_codes: # This is a mix-in. pylint: disable=E1101 msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg) else: if item: @@ -1495,14 +1495,14 @@ class _VerifyErrors(object): item = "" msg = "%s: %s%s: %s" % (ltype, itype, item, msg) # and finally report it via the feedback_fn - self._feedback_fn(" - %s" % msg) # Mix-in. pylint: disable-msg=E1101 + self._feedback_fn(" - %s" % msg) # Mix-in. pylint: disable=E1101 def _ErrorIf(self, cond, *args, **kwargs): """Log an error message if the passed condition is True. """ cond = (bool(cond) - or self.op.debug_simulate_errors) # pylint: disable-msg=E1101 + or self.op.debug_simulate_errors) # pylint: disable=E1101 if cond: self._Error(*args, **kwargs) # do not mark the operation as failed for WARN cases only @@ -1539,7 +1539,7 @@ class LUClusterVerify(NoHooksLU): for group in groups) # Fix up all parameters - for op in itertools.chain(*jobs): # pylint: disable-msg=W0142 + for op in itertools.chain(*jobs): # pylint: disable=W0142 op.debug_simulate_errors = self.op.debug_simulate_errors op.verbose = self.op.verbose op.error_codes = self.op.error_codes @@ -1801,7 +1801,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 # main result, nresult should be a non-empty dict test = not nresult or not isinstance(nresult, dict) @@ -1870,7 +1870,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 ntime = nresult.get(constants.NV_TIME, None) try: @@ -1903,7 +1903,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): return node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 # checks vg existence and size > 20G vglist = nresult.get(constants.NV_VGLIST, None) @@ -1940,7 +1940,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): return node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 missing = nresult.get(constants.NV_BRIDGES, None) test = not isinstance(missing, list) @@ -1959,7 +1959,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 test = constants.NV_NODELIST not in nresult _ErrorIf(test, self.ENODESSH, node, @@ -2000,7 +2000,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): available on the instance's node. """ - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 node_current = instanceconfig.primary_node node_vol_should = {} @@ -2200,7 +2200,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 if drbd_helper: helper_result = nresult.get(constants.NV_DRBDHELPER, None) @@ -2259,7 +2259,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 remote_os = nresult.get(constants.NV_OSLIST, None) test = (not isinstance(remote_os, list) or @@ -2300,7 +2300,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?" @@ -2370,7 +2370,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 nimg.lvm_fail = True lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data") @@ -2418,7 +2418,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """ node = ninfo.name - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 # try to read free memory (from the hypervisor) hv_info = nresult.get(constants.NV_HVINFO, None) @@ -2460,7 +2460,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): list of tuples (success, payload) """ - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 node_disks = {} node_disks_devonly = {} @@ -2568,7 +2568,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """Verify integrity of the node group, performing various test on nodes. """ - # This method has too many local variables. pylint: disable-msg=R0914 + # This method has too many local variables. pylint: disable=R0914 feedback_fn("* Verifying group '%s'" % self.group_info.name) if not self.my_node_names: @@ -2577,7 +2577,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): return True self.bad = False - _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103 + _ErrorIf = self._ErrorIf # pylint: disable=C0103 verbose = self.op.verbose self._feedback_fn = feedback_fn @@ -4419,7 +4419,7 @@ class LUNodeQuery(NoHooksLU): """Logical unit for querying nodes. """ - # pylint: disable-msg=W0142 + # pylint: disable=W0142 REQ_BGL = False def CheckArguments(self): @@ -4620,7 +4620,7 @@ class _InstanceQuery(_QueryBase): for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE) for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name)) elif level == locking.LEVEL_NODE: - lu._LockInstancesNodes() # pylint: disable-msg=W0212 + lu._LockInstancesNodes() # pylint: disable=W0212 @staticmethod def _CheckGroupLocks(lu): @@ -4721,7 +4721,7 @@ class LUQuery(NoHooksLU): """Query for resources/items of a certain kind. """ - # pylint: disable-msg=W0142 + # pylint: disable=W0142 REQ_BGL = False def CheckArguments(self): @@ -4743,7 +4743,7 @@ class LUQueryFields(NoHooksLU): """Query for resources/items of a certain kind. """ - # pylint: disable-msg=W0142 + # pylint: disable=W0142 REQ_BGL = False def CheckArguments(self): @@ -4990,7 +4990,7 @@ class LUNodeAdd(LogicalUnit): # later in the procedure; this also means that if the re-add # fails, we are left with a non-offlined, broken node if self.op.readd: - new_node.drained = new_node.offline = False # pylint: disable-msg=W0201 + new_node.drained = new_node.offline = False # pylint: disable=W0201 self.LogInfo("Readding a node, the offline/drained flags were reset") # if we demote the node, we do cleanup later in the procedure new_node.master_candidate = self.master_candidate @@ -6584,7 +6584,7 @@ class LUInstanceQuery(NoHooksLU): """Logical unit for querying instances. """ - # pylint: disable-msg=W0142 + # pylint: disable=W0142 REQ_BGL = False def CheckArguments(self): @@ -8555,7 +8555,7 @@ class LUInstanceCreate(LogicalUnit): joinargs.append(self.op.instance_name) - # pylint: disable-msg=W0142 + # pylint: disable=W0142 self.instance_file_storage_dir = utils.PathJoin(*joinargs) def CheckPrereq(self): @@ -9705,7 +9705,7 @@ class TLReplaceDisks(Tasklet): self.lu.LogWarning("Can't remove old LV: %s" % msg, hint="remove unused LVs manually") - def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable-msg=W0613 + def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613 """Replace a disk on the primary or secondary for DRBD 8. The algorithm for replace is quite complicated: @@ -12497,7 +12497,7 @@ class LUGroupEvacuate(LogicalUnit): return ResultWithJobs(jobs) -class TagsLU(NoHooksLU): # pylint: disable-msg=W0223 +class TagsLU(NoHooksLU): # pylint: disable=W0223 """Generic tags LU. This is an abstract class which is the parent of all the other tags LUs. @@ -12757,7 +12757,7 @@ class LUTestJqueue(NoHooksLU): # Wait for client to close try: try: - # pylint: disable-msg=E1101 + # pylint: disable=E1101 # Instance of '_socketobject' has no ... member conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT) conn.recv(1) @@ -12854,7 +12854,7 @@ class IAllocator(object): easy usage """ - # pylint: disable-msg=R0902 + # pylint: disable=R0902 # lots of instance attributes def __init__(self, cfg, rpc, mode, **kwargs): @@ -13191,7 +13191,7 @@ class IAllocator(object): _STRING_LIST = ht.TListOf(ht.TString) _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, { - # pylint: disable-msg=E1101 + # pylint: disable=E1101 # Class '...' has no 'OP_ID' member "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID, opcodes.OpInstanceMigrate.OP_ID, diff --git a/lib/compat.py b/lib/compat.py index 90799205798aecfd1e1e093ed188c0e0fa14cdf5..53ee1fb801d5e96fbfc1353ada0def0ba7cd0818 100644 --- a/lib/compat.py +++ b/lib/compat.py @@ -27,13 +27,13 @@ import itertools import operator try: - # pylint: disable-msg=F0401 + # pylint: disable=F0401 import functools except ImportError: functools = None try: - # pylint: disable-msg=F0401 + # pylint: disable=F0401 import roman except ImportError: roman = None @@ -45,9 +45,9 @@ except ImportError: # modules (hmac, for example) which have changed their behavior as well from # one version to the other. try: - # pylint: disable-msg=F0401 + # pylint: disable=F0401 # Yes, we're not using the imports in this module. - # pylint: disable-msg=W0611 + # pylint: disable=W0611 from hashlib import md5 as md5_hash from hashlib import sha1 as sha1_hash # this additional version is needed for compatibility with the hmac module @@ -78,21 +78,21 @@ def _any(seq): try: - # pylint: disable-msg=E0601 - # pylint: disable-msg=W0622 + # pylint: disable=E0601 + # pylint: disable=W0622 all = all except NameError: all = _all try: - # pylint: disable-msg=E0601 - # pylint: disable-msg=W0622 + # pylint: disable=E0601 + # pylint: disable=W0622 any = any except NameError: any = _any -def partition(seq, pred=bool): # pylint: disable-msg=W0622 +def partition(seq, pred=bool): # pylint: disable=W0622 """Partition a list in two, based on the given predicate. """ @@ -102,7 +102,7 @@ def partition(seq, pred=bool): # pylint: disable-msg=W0622 # Even though we're using Python's built-in "partial" function if available, # this one is always defined for testing. -def _partial(func, *args, **keywords): # pylint: disable-msg=W0622 +def _partial(func, *args, **keywords): # pylint: disable=W0622 """Decorator with partial application of arguments and keywords. This function was copied from Python's documentation. @@ -111,7 +111,7 @@ def _partial(func, *args, **keywords): # pylint: disable-msg=W0622 def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) - return func(*(args + fargs), **newkeywords) # pylint: disable-msg=W0142 + return func(*(args + fargs), **newkeywords) # pylint: disable=W0142 newfunc.func = func newfunc.args = args diff --git a/lib/confd/client.py b/lib/confd/client.py index aec2a7f5b0f0a61a7d3f77d50a29cace957a7730..11baa4d223ad6cea90d62f03aecddef8f70198fe 100644 --- a/lib/confd/client.py +++ b/lib/confd/client.py @@ -45,7 +45,7 @@ confirming what you already got. """ -# pylint: disable-msg=E0203 +# pylint: disable=E0203 # E0203: Access to member %r before its definition, since we use # objects.py which doesn't explicitely initialise its members @@ -156,7 +156,7 @@ class ConfdClient: """ # we are actually called from init, so: - # pylint: disable-msg=W0201 + # pylint: disable=W0201 if not isinstance(peers, list): raise errors.ProgrammerError("peers must be a list") # make a copy of peers, since we're going to shuffle the list, later diff --git a/lib/confd/querylib.py b/lib/confd/querylib.py index 4ad51e78855e3bffbccd72042182b2744fcc7753..e231a14e49ec94a7032c8c5e6a0feeaef2e0a59e 100644 --- a/lib/confd/querylib.py +++ b/lib/confd/querylib.py @@ -50,7 +50,7 @@ class ConfdQuery(object): """ self.reader = reader - def Exec(self, query): # pylint: disable-msg=R0201,W0613 + def Exec(self, query): # pylint: disable=R0201,W0613 """Process a single UDP request from a client. Different queries should override this function, which by defaults returns diff --git a/lib/config.py b/lib/config.py index df4f84fa6b79b0ec9f7e4ea73da4faf3f08a90d5..fdd77c0d918dcf43ae3628c1326376b5380d5742 100644 --- a/lib/config.py +++ b/lib/config.py @@ -31,7 +31,7 @@ much memory. """ -# pylint: disable-msg=R0904 +# pylint: disable=R0904 # R0904: Too many public methods import os @@ -374,7 +374,7 @@ class ConfigWriter: configuration errors """ - # pylint: disable-msg=R0914 + # pylint: disable=R0914 result = [] seen_macs = [] ports = {} diff --git a/lib/constants.py b/lib/constants.py index 9e48997c66adf5e45060efec164812b1367a78c7..a1c70b2eca90ffb9367a80f35503760931fb2042 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -269,11 +269,14 @@ CONS_SSH = "ssh" #: Console as VNC server CONS_VNC = "vnc" +#: Console as SPICE server +CONS_SPICE = "spice" + #: Display a message for console access CONS_MESSAGE = "msg" #: All console types -CONS_ALL = frozenset([CONS_SSH, CONS_VNC, CONS_MESSAGE]) +CONS_ALL = frozenset([CONS_SSH, CONS_VNC, CONS_SPICE, CONS_MESSAGE]) # For RSA keys more bits are better, but they also make operations more # expensive. NIST SP 800-131 recommends a minimum of 2048 bits from the year @@ -684,6 +687,12 @@ HV_VNC_X509 = "vnc_x509_path" HV_VNC_X509_VERIFY = "vnc_x509_verify" HV_KVM_SPICE_BIND = "spice_bind" HV_KVM_SPICE_IP_VERSION = "spice_ip_version" +HV_KVM_SPICE_PASSWORD_FILE = "spice_password_file" +HV_KVM_SPICE_LOSSLESS_IMG_COMPR = "spice_image_compression" +HV_KVM_SPICE_JPEG_IMG_COMPR = "spice_jpeg_wan_compression" +HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR = "spice_zlib_glz_wan_compression" +HV_KVM_SPICE_STREAMING_VIDEO_DETECTION = "spice_streaming_video" +HV_KVM_SPICE_AUDIO_COMPR = "spice_playback_compression" HV_ACPI = "acpi" HV_PAE = "pae" HV_USE_BOOTLOADER = "use_bootloader" @@ -729,6 +738,12 @@ HVS_PARAMETER_TYPES = { HV_VNC_X509_VERIFY: VTYPE_BOOL, HV_KVM_SPICE_BIND: VTYPE_STRING, HV_KVM_SPICE_IP_VERSION: VTYPE_INT, + HV_KVM_SPICE_PASSWORD_FILE: VTYPE_STRING, + HV_KVM_SPICE_LOSSLESS_IMG_COMPR: VTYPE_STRING, + HV_KVM_SPICE_JPEG_IMG_COMPR: VTYPE_STRING, + HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: VTYPE_STRING, + HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: VTYPE_STRING, + HV_KVM_SPICE_AUDIO_COMPR: VTYPE_BOOL, HV_ACPI: VTYPE_BOOL, HV_PAE: VTYPE_BOOL, HV_USE_BOOTLOADER: VTYPE_BOOL, @@ -962,6 +977,45 @@ HT_KVM_VALID_BO_TYPES = frozenset([ HT_BO_NETWORK ]) +# SPICE lossless image compression options +HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_GLZ = "auto_glz" +HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_LZ = "auto_lz" +HT_KVM_SPICE_LOSSLESS_IMG_COMPR_QUIC = "quic" +HT_KVM_SPICE_LOSSLESS_IMG_COMPR_GLZ = "glz" +HT_KVM_SPICE_LOSSLESS_IMG_COMPR_LZ = "lz" +HT_KVM_SPICE_LOSSLESS_IMG_COMPR_OFF = "off" + +HT_KVM_SPICE_VALID_LOSSLESS_IMG_COMPR_OPTIONS = frozenset([ + HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_GLZ, + HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_LZ, + HT_KVM_SPICE_LOSSLESS_IMG_COMPR_QUIC, + HT_KVM_SPICE_LOSSLESS_IMG_COMPR_GLZ, + HT_KVM_SPICE_LOSSLESS_IMG_COMPR_LZ, + HT_KVM_SPICE_LOSSLESS_IMG_COMPR_OFF, + ]) + +# SPICE lossy image compression options (valid for both jpeg and zlib-glz) +HT_KVM_SPICE_LOSSY_IMG_COMPR_AUTO = "auto" +HT_KVM_SPICE_LOSSY_IMG_COMPR_NEVER = "never" +HT_KVM_SPICE_LOSSY_IMG_COMPR_ALWAYS = "always" + +HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS = frozenset([ + HT_KVM_SPICE_LOSSY_IMG_COMPR_AUTO, + HT_KVM_SPICE_LOSSY_IMG_COMPR_NEVER, + HT_KVM_SPICE_LOSSY_IMG_COMPR_ALWAYS, + ]) + +# SPICE video stream detection +HT_KVM_SPICE_VIDEO_STREAM_DETECTION_OFF = "off" +HT_KVM_SPICE_VIDEO_STREAM_DETECTION_ALL = "all" +HT_KVM_SPICE_VIDEO_STREAM_DETECTION_FILTER = "filter" + +HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS = frozenset([ + HT_KVM_SPICE_VIDEO_STREAM_DETECTION_OFF, + HT_KVM_SPICE_VIDEO_STREAM_DETECTION_ALL, + HT_KVM_SPICE_VIDEO_STREAM_DETECTION_FILTER, + ]) + # Security models HT_SM_NONE = "none" HT_SM_USER = "user" @@ -1311,6 +1365,12 @@ HVC_DEFAULTS = { HV_VNC_PASSWORD_FILE: "", HV_KVM_SPICE_BIND: "", HV_KVM_SPICE_IP_VERSION: IFACE_NO_IP_VERSION_SPECIFIED, + HV_KVM_SPICE_PASSWORD_FILE: "", + HV_KVM_SPICE_LOSSLESS_IMG_COMPR: "", + HV_KVM_SPICE_JPEG_IMG_COMPR: "", + HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: "", + HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: "", + HV_KVM_SPICE_AUDIO_COMPR: True, HV_KVM_FLOPPY_IMAGE_PATH: "", HV_CDROM_IMAGE_PATH: "", HV_KVM_CDROM2_IMAGE_PATH: "", diff --git a/lib/daemon.py b/lib/daemon.py index 7f5e9efcfa7546281f59d63d81c4d6e1d4dfe692..f5e18a25bc0252b6320633f8403580fb23b02793 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -539,12 +539,12 @@ def _BeautifyError(err): err.errno) else: return str(err) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 logging.exception("Error while handling existing error %s", err) return "%s" % str(err) -def _HandleSigHup(reopen_fn, signum, frame): # pylint: disable-msg=W0613 +def _HandleSigHup(reopen_fn, signum, frame): # pylint: disable=W0613 """Handler for SIGHUP. @param reopen_fn: List of callback functions for reopening log files diff --git a/lib/http/__init__.py b/lib/http/__init__.py index 9049829912cef21116b1a7302c17fd5cffe72b21..2719a38f0ff5722f80089f03ec875151f9c491ee 100644 --- a/lib/http/__init__.py +++ b/lib/http/__init__.py @@ -624,7 +624,7 @@ class HttpBase(object): return OpenSSL.SSL.Connection(ctx, sock) - def GetSslCiphers(self): # pylint: disable-msg=R0201 + def GetSslCiphers(self): # pylint: disable=R0201 """Returns the ciphers string for SSL. """ @@ -638,7 +638,7 @@ class HttpBase(object): """ # some parameters are unused, but this is the API - # pylint: disable-msg=W0613 + # pylint: disable=W0613 assert self._ssl_params, "SSL not initialized" return (self._ssl_cert.digest("sha1") == cert.digest("sha1") and diff --git a/lib/http/auth.py b/lib/http/auth.py index 09b0ce72154ddb341adb7d834a14fcb8bdb84ad1..5a7bfac19b2b6d1d217fcc9d62cc3e7e93af3f5d 100644 --- a/lib/http/auth.py +++ b/lib/http/auth.py @@ -92,7 +92,7 @@ class HttpServerRequestAuthentication(object): """ # today we don't have per-request filtering, but we might want to # add it in the future - # pylint: disable-msg=W0613 + # pylint: disable=W0613 return self.AUTH_REALM def AuthenticationRequired(self, req): @@ -106,7 +106,7 @@ class HttpServerRequestAuthentication(object): """ # Unused argument, method could be a function - # pylint: disable-msg=W0613,R0201 + # pylint: disable=W0613,R0201 return False def PreHandleRequest(self, req): diff --git a/lib/http/server.py b/lib/http/server.py index 52b1340648290c7229d87b5d54583356a5271faa..cb76185966add89cadd8e3e21bebab9dc7593efe 100644 --- a/lib/http/server.py +++ b/lib/http/server.py @@ -537,7 +537,7 @@ class HttpServer(http.HttpBase, asyncore.dispatcher): """Called for each incoming connection """ - # pylint: disable-msg=W0212 + # pylint: disable=W0212 (connection, client_addr) = self.socket.accept() self._CollectChildren(False) @@ -560,7 +560,7 @@ class HttpServer(http.HttpBase, asyncore.dispatcher): utils.ResetTempfileModule() self.request_executor(self, connection, client_addr) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 logging.exception("Error while handling request from %s:%s", client_addr[0], client_addr[1]) os._exit(1) diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py index 074017f75940f7943cd372ba60109f252efa2dd7..0886933c3d317cd920bbd2d7eeff236b506a94cd 100644 --- a/lib/hypervisor/hv_base.py +++ b/lib/hypervisor/hv_base.py @@ -263,7 +263,7 @@ class BaseHypervisor(object): """ raise NotImplementedError - def MigrationInfo(self, instance): # pylint: disable-msg=R0201,W0613 + def MigrationInfo(self, instance): # pylint: disable=R0201,W0613 """Get instance information to perform a migration. By default assume no information is needed. diff --git a/lib/hypervisor/hv_chroot.py b/lib/hypervisor/hv_chroot.py index 8505cadc58c60d93c4af3a7326e4a7380cca31e9..3d3caeba7fa8578ff446d8e1c5ec0fa37fc05d44 100644 --- a/lib/hypervisor/hv_chroot.py +++ b/lib/hypervisor/hv_chroot.py @@ -29,7 +29,7 @@ import time import logging from ganeti import constants -from ganeti import errors # pylint: disable-msg=W0611 +from ganeti import errors # pylint: disable=W0611 from ganeti import utils from ganeti import objects from ganeti.hypervisor import hv_base @@ -247,7 +247,7 @@ class ChrootManager(hv_base.BaseHypervisor): return self.GetLinuxNodeInfo() @classmethod - def GetInstanceConsole(cls, instance, # pylint: disable-msg=W0221 + def GetInstanceConsole(cls, instance, # pylint: disable=W0221 hvparams, beparams, root_dir=None): """Return information for connecting to the console of an instance. diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index d5d057fc63fe2a4f4efd988bc089c872dc9e3adf..c32c8abfd6c3dfda2dea2ae381576f6abc620d53 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -34,6 +34,8 @@ import pwd import struct import fcntl import shutil +import socket +import StringIO from ganeti import utils from ganeti import constants @@ -127,6 +129,251 @@ def _OpenTap(vnet_hdr=True): return (ifname, tapfd) +class QmpMessage: + """QEMU Messaging Protocol (QMP) message. + + """ + + def __init__(self, data): + """Creates a new QMP message based on the passed data. + + """ + if not isinstance(data, dict): + raise TypeError("QmpMessage must be initialized with a dict") + + self.data = data + + def __getitem__(self, field_name): + """Get the value of the required field if present, or None. + + Overrides the [] operator to provide access to the message data, + returning None if the required item is not in the message + @return: the value of the field_name field, or None if field_name + is not contained in the message + + """ + + if field_name in self.data: + return self.data[field_name] + + return None + + def __setitem__(self, field_name, field_value): + """Set the value of the required field_name to field_value. + + """ + self.data[field_name] = field_value + + @staticmethod + def BuildFromJsonString(json_string): + """Build a QmpMessage from a JSON encoded string. + + @type json_string: str + @param json_string: JSON string representing the message + @rtype: L{QmpMessage} + @return: a L{QmpMessage} built from json_string + + """ + # Parse the string + data = serializer.LoadJson(json_string) + return QmpMessage(data) + + def __str__(self): + # The protocol expects the JSON object to be sent as a single + # line, hence the need for indent=False. + return serializer.DumpJson(self.data, indent=False) + + def __eq__(self, other): + # When comparing two QmpMessages, we are interested in comparing + # their internal representation of the message data + return self.data == other.data + + +class QmpConnection: + """Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP). + + """ + _FIRST_MESSAGE_KEY = "QMP" + _EVENT_KEY = "event" + _ERROR_KEY = "error" + _ERROR_CLASS_KEY = "class" + _ERROR_DATA_KEY = "data" + _ERROR_DESC_KEY = "desc" + _EXECUTE_KEY = "execute" + _ARGUMENTS_KEY = "arguments" + _CAPABILITIES_COMMAND = "qmp_capabilities" + _MESSAGE_END_TOKEN = "\r\n" + _SOCKET_TIMEOUT = 5 + + def __init__(self, monitor_filename): + """Instantiates the QmpConnection object. + + @type monitor_filename: string + @param monitor_filename: the filename of the UNIX raw socket on which the + QMP monitor is listening + + """ + self.monitor_filename = monitor_filename + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + # We want to fail if the server doesn't send a complete message + # in a reasonable amount of time + self.sock.settimeout(self._SOCKET_TIMEOUT) + self._connected = False + self._buf = "" + + def _check_connection(self): + """Make sure that the connection is established. + + """ + if not self._connected: + raise errors.ProgrammerError("To use a QmpConnection you need to first" + " invoke connect() on it") + + def connect(self): + """Connects to the QMP monitor. + + Connects to the UNIX socket and makes sure that we can actually send and + receive data to the kvm instance via QMP. + + @raise errors.HypervisorError: when there are communication errors + @raise errors.ProgrammerError: when there are data serialization errors + + """ + self.sock.connect(self.monitor_filename) + self._connected = True + + # Check if we receive a correct greeting message from the server + # (As per the QEMU Protocol Specification 0.1 - section 2.2) + greeting = self._Recv() + if not greeting[self._FIRST_MESSAGE_KEY]: + self._connected = False + raise errors.HypervisorError("kvm: qmp communication error (wrong" + " server greeting") + + # Let's put the monitor in command mode using the qmp_capabilities + # command, or else no command will be executable. + # (As per the QEMU Protocol Specification 0.1 - section 4) + self.Execute(self._CAPABILITIES_COMMAND) + + def _ParseMessage(self, buf): + """Extract and parse a QMP message from the given buffer. + + Seeks for a QMP message in the given buf. If found, it parses it and + returns it together with the rest of the characters in the buf. + If no message is found, returns None and the whole buffer. + + @raise errors.ProgrammerError: when there are data serialization errors + + """ + message = None + # Check if we got the message end token (CRLF, as per the QEMU Protocol + # Specification 0.1 - Section 2.1.1) + pos = buf.find(self._MESSAGE_END_TOKEN) + if pos >= 0: + try: + message = QmpMessage.BuildFromJsonString(buf[:pos + 1]) + except Exception, err: + raise errors.ProgrammerError("QMP data serialization error: %s" % err) + buf = buf[pos + 1:] + + return (message, buf) + + def _Recv(self): + """Receives a message from QMP and decodes the received JSON object. + + @rtype: QmpMessage + @return: the received message + @raise errors.HypervisorError: when there are communication errors + @raise errors.ProgrammerError: when there are data serialization errors + + """ + self._check_connection() + + # Check if there is already a message in the buffer + (message, self._buf) = self._ParseMessage(self._buf) + if message: + return message + + recv_buffer = StringIO.StringIO(self._buf) + recv_buffer.seek(len(self._buf)) + try: + while True: + data = self.sock.recv(4096) + if not data: + break + recv_buffer.write(data) + + (message, self._buf) = self._ParseMessage(recv_buffer.getvalue()) + if message: + return message + + except socket.timeout, err: + raise errors.HypervisorError("Timeout while receiving a QMP message: " + "%s" % (err)) + except socket.error, err: + raise errors.HypervisorError("Unable to receive data from KVM using the" + " QMP protocol: %s" % err) + + def _Send(self, message): + """Encodes and sends a message to KVM using QMP. + + @type message: QmpMessage + @param message: message to send to KVM + @raise errors.HypervisorError: when there are communication errors + @raise errors.ProgrammerError: when there are data serialization errors + + """ + self._check_connection() + try: + message_str = str(message) + except Exception, err: + raise errors.ProgrammerError("QMP data deserialization error: %s" % err) + + try: + self.sock.sendall(message_str) + except socket.timeout, err: + raise errors.HypervisorError("Timeout while sending a QMP message: " + "%s (%s)" % (err.string, err.errno)) + except socket.error, err: + raise errors.HypervisorError("Unable to send data from KVM using the" + " QMP protocol: %s" % err) + + def Execute(self, command, arguments=None): + """Executes a QMP command and returns the response of the server. + + @type command: str + @param command: the command to execute + @type arguments: dict + @param arguments: dictionary of arguments to be passed to the command + @rtype: dict + @return: dictionary representing the received JSON object + @raise errors.HypervisorError: when there are communication errors + @raise errors.ProgrammerError: when there are data serialization errors + + """ + self._check_connection() + message = QmpMessage({self._EXECUTE_KEY: command}) + if arguments: + message[self._ARGUMENTS_KEY] = arguments + self._Send(message) + + # Events can occur between the sending of the command and the reception + # of the response, so we need to filter out messages with the event key. + while True: + response = self._Recv() + err = response[self._ERROR_KEY] + if err: + raise errors.HypervisorError("kvm: error executing the %s" + " command: %s (%s, %s):" % + (command, + err[self._ERROR_DESC_KEY], + err[self._ERROR_CLASS_KEY], + err[self._ERROR_DATA_KEY])) + + elif not response[self._EVENT_KEY]: + return response + + class KVMHypervisor(hv_base.BaseHypervisor): """KVM hypervisor interface""" CAN_MIGRATE = True @@ -171,6 +418,20 @@ class KVMHypervisor(hv_base.BaseHypervisor): x in constants.VALID_IP_VERSIONS), "the SPICE IP version should be 4 or 6", None, None), + constants.HV_KVM_SPICE_PASSWORD_FILE: hv_base.OPT_FILE_CHECK, + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR: + hv_base.ParamInSet(False, + constants.HT_KVM_SPICE_VALID_LOSSLESS_IMG_COMPR_OPTIONS), + constants.HV_KVM_SPICE_JPEG_IMG_COMPR: + hv_base.ParamInSet(False, + constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS), + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: + hv_base.ParamInSet(False, + constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS), + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: + hv_base.ParamInSet(False, + constants.HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS), + constants.HV_KVM_SPICE_AUDIO_COMPR: hv_base.NO_CHECK, constants.HV_KVM_FLOPPY_IMAGE_PATH: hv_base.OPT_FILE_CHECK, constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK, constants.HV_KVM_CDROM2_IMAGE_PATH: hv_base.OPT_FILE_CHECK, @@ -326,6 +587,13 @@ class KVMHypervisor(hv_base.BaseHypervisor): """ return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name) + @classmethod + def _InstanceQmpMonitor(cls, instance_name): + """Returns the instance serial QMP socket name + + """ + return utils.PathJoin(cls._CTRL_DIR, "%s.qmp" % instance_name) + @staticmethod def _SocatUnixConsoleParams(): """Returns the correct parameters for socat @@ -397,6 +665,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): utils.RemoveFile(pidfile) utils.RemoveFile(cls._InstanceMonitor(instance_name)) utils.RemoveFile(cls._InstanceSerial(instance_name)) + utils.RemoveFile(cls._InstanceQmpMonitor(instance_name)) utils.RemoveFile(cls._InstanceKVMRuntime(instance_name)) utils.RemoveFile(cls._InstanceKeymapFile(instance_name)) uid_file = cls._InstanceUidFile(instance_name) @@ -525,6 +794,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): """Generate KVM information to start an instance. """ + # pylint: disable=R0914 _, v_major, v_min, _ = self._GetKVMVersion() pidfile = self._InstancePidFile(instance.name) @@ -761,14 +1031,39 @@ class KVMHypervisor(hv_base.BaseHypervisor): # ValidateParameters checked it. spice_address = spice_bind - spice_arg = "addr=%s,port=%s,disable-ticketing" % (spice_address, - instance.network_port) + spice_arg = "addr=%s,port=%s" % (spice_address, instance.network_port) + if not hvp[constants.HV_KVM_SPICE_PASSWORD_FILE]: + spice_arg = "%s,disable-ticketing" % spice_arg + if spice_ip_version: spice_arg = "%s,ipv%s" % (spice_arg, spice_ip_version) + # Image compression options + img_lossless = hvp[constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR] + img_jpeg = hvp[constants.HV_KVM_SPICE_JPEG_IMG_COMPR] + img_zlib_glz = hvp[constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR] + if img_lossless: + spice_arg = "%s,image-compression=%s" % (spice_arg, img_lossless) + if img_jpeg: + spice_arg = "%s,jpeg-wan-compression=%s" % (spice_arg, img_jpeg) + if img_zlib_glz: + spice_arg = "%s,zlib-glz-wan-compression=%s" % (spice_arg, img_zlib_glz) + + # Video stream detection + video_streaming = hvp[constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION] + if video_streaming: + spice_arg = "%s,streaming-video=%s" % (spice_arg, video_streaming) + + # Audio compression, by default in qemu-kvm it is on + if not hvp[constants.HV_KVM_SPICE_AUDIO_COMPR]: + spice_arg = "%s,playback-compression=off" % spice_arg + logging.info("KVM: SPICE will listen on port %s", instance.network_port) kvm_cmd.extend(["-spice", spice_arg]) + # Tell kvm to use the paravirtualized graphic card, optimized for SPICE + kvm_cmd.extend(["-vga", "qxl"]) + if hvp[constants.HV_USE_LOCALTIME]: kvm_cmd.extend(["-localtime"]) @@ -940,6 +1235,12 @@ class KVMHypervisor(hv_base.BaseHypervisor): utils.EnsureDirs([(self._InstanceChrootDir(name), constants.SECURE_DIR_MODE)]) + # Automatically enable QMP if version is >= 0.14 + if (v_major, v_min) >= (0, 14): + logging.debug("Enabling QMP") + kvm_cmd.extend(["-qmp", "unix:%s,server,nowait" % + self._InstanceQmpMonitor(instance.name)]) + # Configure the network now for starting instances and bridged interfaces, # during FinalizeMigration for incoming instances' routed interfaces for nic_seq, nic in enumerate(kvm_nics): @@ -976,6 +1277,26 @@ class KVMHypervisor(hv_base.BaseHypervisor): change_cmd = "change vnc password %s" % vnc_pwd self._CallMonitorCommand(instance.name, change_cmd) + # Setting SPICE password. We are not vulnerable to malicious passwordless + # connection attempts because SPICE by default does not allow connections + # if neither a password nor the "disable_ticketing" options are specified. + # As soon as we send the password via QMP, that password is a valid ticket + # for connection. + spice_password_file = conf_hvp[constants.HV_KVM_SPICE_PASSWORD_FILE] + if spice_password_file: + try: + spice_pwd = utils.ReadOneLineFile(spice_password_file, strict=True) + qmp = QmpConnection(self._InstanceQmpMonitor(instance.name)) + qmp.connect() + arguments = { + "protocol": "spice", + "password": spice_pwd, + } + qmp.Execute("set_password", arguments) + except EnvironmentError, err: + raise errors.HypervisorError("Failed to open SPICE password file %s: %s" + % (spice_password_file, err)) + for filename in temp_files: utils.RemoveFile(filename) @@ -1241,6 +1562,13 @@ class KVMHypervisor(hv_base.BaseHypervisor): port=instance.network_port, display=display) + spice_bind = hvparams[constants.HV_KVM_SPICE_BIND] + if spice_bind: + return objects.InstanceConsole(instance=instance.name, + kind=constants.CONS_SPICE, + host=spice_bind, + port=instance.network_port) + return objects.InstanceConsole(instance=instance.name, kind=constants.CONS_MESSAGE, message=("No serial shell for instance %s" % @@ -1298,8 +1626,8 @@ class KVMHypervisor(hv_base.BaseHypervisor): " security model is 'none' or 'pool'") spice_bind = hvparams[constants.HV_KVM_SPICE_BIND] + spice_ip_version = hvparams[constants.HV_KVM_SPICE_IP_VERSION] if spice_bind: - spice_ip_version = hvparams[constants.HV_KVM_SPICE_IP_VERSION] if spice_ip_version != constants.IFACE_NO_IP_VERSION_SPECIFIED: # if an IP version is specified, the spice_bind parameter must be an # IP of that family @@ -1314,6 +1642,21 @@ class KVMHypervisor(hv_base.BaseHypervisor): raise errors.HypervisorError("spice: got an IPv6 address (%s), but" " the specified IP version is %s" % (spice_bind, spice_ip_version)) + else: + # All the other SPICE parameters depend on spice_bind being set. Raise an + # error if any of them is set without it. + spice_additional_params = frozenset([ + constants.HV_KVM_SPICE_IP_VERSION, + constants.HV_KVM_SPICE_PASSWORD_FILE, + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR, + constants.HV_KVM_SPICE_JPEG_IMG_COMPR, + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR, + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION, + ]) + for param in spice_additional_params: + if hvparams[param]: + raise errors.HypervisorError("spice: %s requires %s to be set" % + (param, constants.HV_KVM_SPICE_BIND)) @classmethod def ValidateParameters(cls, hvparams): diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py index 9d56bd2fc77b820d56010bc3c028df83c72fdb30..8fdc52f1a6e3935dd6307e6804f9a8ced51ae8bd 100644 --- a/lib/hypervisor/hv_lxc.py +++ b/lib/hypervisor/hv_lxc.py @@ -29,7 +29,7 @@ import time import logging from ganeti import constants -from ganeti import errors # pylint: disable-msg=W0611 +from ganeti import errors # pylint: disable=W0611 from ganeti import utils from ganeti import objects from ganeti.hypervisor import hv_base diff --git a/lib/jqueue.py b/lib/jqueue.py index bf65d5bc71fe6c4ce870ef8d8617ece5173bbaec..3cf3b428762be1b26b4e509fc63fb34c2e819d33 100644 --- a/lib/jqueue.py +++ b/lib/jqueue.py @@ -37,7 +37,7 @@ import threading import itertools try: - # pylint: disable-msg=E0611 + # pylint: disable=E0611 from pyinotify import pyinotify except ImportError: import pyinotify @@ -177,7 +177,7 @@ class _QueuedJob(object): @ivar writable: Whether the job is allowed to be modified """ - # pylint: disable-msg=W0212 + # pylint: disable=W0212 __slots__ = ["queue", "id", "ops", "log_serial", "ops_iter", "cur_opctx", "received_timestamp", "start_timestamp", "end_timestamp", "__weakref__", "processor_lock", "writable"] @@ -1048,7 +1048,7 @@ class _JobProcessor(object): logging.exception("%s: Canceling job", opctx.log_prefix) assert op.status == constants.OP_STATUS_CANCELING return (constants.OP_STATUS_CANCELING, None) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 logging.exception("%s: Caught exception in %s", opctx.log_prefix, opctx.summary) return (constants.OP_STATUS_ERROR, _EncodeOpError(err)) @@ -1241,7 +1241,7 @@ class _JobQueueWorker(workerpool.BaseWorker): """The actual job workers. """ - def RunTask(self, job): # pylint: disable-msg=W0221 + def RunTask(self, job): # pylint: disable=W0221 """Job executor. @type job: L{_QueuedJob} @@ -1358,7 +1358,7 @@ class _JobDependencyManager: self._lock = locking.SharedLock("JobDepMgr") @locking.ssynchronized(_LOCK, shared=1) - def GetLockInfo(self, requested): # pylint: disable-msg=W0613 + def GetLockInfo(self, requested): # pylint: disable=W0613 """Retrieves information about waiting jobs. @type requested: set @@ -1481,7 +1481,7 @@ def _RequireOpenQueue(fn): """ def wrapper(self, *args, **kwargs): - # pylint: disable-msg=W0212 + # pylint: disable=W0212 assert self._queue_filelock is not None, "Queue should be open" return fn(self, *args, **kwargs) return wrapper @@ -1945,7 +1945,7 @@ class JobQueue(object): try: data = serializer.LoadJson(raw_data) job = _QueuedJob.Restore(self, data, writable) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 raise errors.JobFileCorrupted(err) return job @@ -2187,7 +2187,7 @@ class JobQueue(object): # Try to load from disk job = self.SafeLoadJobFromDisk(job_id, True, writable=False) - assert not job.writable, "Got writable job" + assert not job.writable, "Got writable job" # pylint: disable=E1101 if job: return job.CalcStatus() diff --git a/lib/locking.py b/lib/locking.py index 9f24771500db9135724ae4f53ebe658ec24a00a8..26345632e400df71a04978ec7271cec4d6eac420 100644 --- a/lib/locking.py +++ b/lib/locking.py @@ -20,7 +20,7 @@ """Module implementing the Ganeti locking code.""" -# pylint: disable-msg=W0212 +# pylint: disable=W0212 # W0212 since e.g. LockSet methods use (a lot) the internals of # SharedLock @@ -276,7 +276,7 @@ class SingleNotifyPipeCondition(_BaseCondition): if self._nwaiters == 0: self._Cleanup() - def notifyAll(self): # pylint: disable-msg=C0103 + def notifyAll(self): # pylint: disable=C0103 """Close the writing side of the pipe to notify all waiters. """ @@ -333,7 +333,7 @@ class PipeCondition(_BaseCondition): self._check_owned() self._waiters.remove(threading.currentThread()) - def notifyAll(self): # pylint: disable-msg=C0103 + def notifyAll(self): # pylint: disable=C0103 """Notify all currently waiting threads. """ @@ -1507,7 +1507,7 @@ class GanetiLockManager: # the test cases. return compat.any((self._is_owned(l) for l in LEVELS[level + 1:])) - def _BGL_owned(self): # pylint: disable-msg=C0103 + def _BGL_owned(self): # pylint: disable=C0103 """Check if the current thread owns the BGL. Both an exclusive or a shared acquisition work. @@ -1516,7 +1516,7 @@ class GanetiLockManager: return BGL in self.__keyring[LEVEL_CLUSTER]._list_owned() @staticmethod - def _contains_BGL(level, names): # pylint: disable-msg=C0103 + def _contains_BGL(level, names): # pylint: disable=C0103 """Check if the level contains the BGL. Check if acting on the given level and set of names will change diff --git a/lib/luxi.py b/lib/luxi.py index cfde21e4ec372c68a4ec7aa30c597f5b57675a5d..4371613a1193254ae3e74f717f723799ec2bbd60 100644 --- a/lib/luxi.py +++ b/lib/luxi.py @@ -278,9 +278,9 @@ def ParseRequest(msg): logging.error("LUXI request not a dict: %r", msg) raise ProtocolError("Invalid LUXI request (not a dict)") - method = request.get(KEY_METHOD, None) # pylint: disable-msg=E1103 - args = request.get(KEY_ARGS, None) # pylint: disable-msg=E1103 - version = request.get(KEY_VERSION, None) # pylint: disable-msg=E1103 + method = request.get(KEY_METHOD, None) # pylint: disable=E1103 + args = request.get(KEY_ARGS, None) # pylint: disable=E1103 + version = request.get(KEY_VERSION, None) # pylint: disable=E1103 if method is None or args is None: logging.error("LUXI request missing method or arguments: %r", msg) @@ -309,7 +309,7 @@ def ParseResponse(msg): raise ProtocolError("Invalid response from server: %r" % data) return (data[KEY_SUCCESS], data[KEY_RESULT], - data.get(KEY_VERSION, None)) # pylint: disable-msg=E1103 + data.get(KEY_VERSION, None)) # pylint: disable=E1103 def FormatResponse(success, result, version=None): @@ -417,7 +417,7 @@ class Client(object): old_transp = self.transport self.transport = None old_transp.Close() - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 pass def _SendMethodCall(self, data): diff --git a/lib/mcpu.py b/lib/mcpu.py index 4fcd6fdcb8b93bc25a9223da482709d691d205dd..89046c60cdf64e575cce43b592969530b790e0ea 100644 --- a/lib/mcpu.py +++ b/lib/mcpu.py @@ -122,7 +122,7 @@ class LockAttemptTimeoutStrategy(object): return timeout -class OpExecCbBase: # pylint: disable-msg=W0232 +class OpExecCbBase: # pylint: disable=W0232 """Base class for OpCode execution callbacks. """ diff --git a/lib/netutils.py b/lib/netutils.py index 7a1b6d032013409eb417592f56dfc5195c2ccff7..80c0219bc13b9a853a4520e41d90717f89b25821 100644 --- a/lib/netutils.py +++ b/lib/netutils.py @@ -583,7 +583,7 @@ class IP6Address(IPAddress): twoparts = address.split("::") sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":")) parts = twoparts[0].split(":") - [parts.append("0") for _ in range(8 - sep)] + parts.extend(["0"] * (8 - sep)) parts += twoparts[1].split(":") else: parts = address.split(":") diff --git a/lib/objects.py b/lib/objects.py index 4e298c6a1761f97b5b1fcc04a293e7ec4da6e2f3..8cc7ac36b29276b46e4536d88fe2878a57540b5a 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -26,7 +26,7 @@ pass to and from external parties. """ -# pylint: disable-msg=E0203,W0201 +# pylint: disable=E0203,W0201 # E0203: Access to member %r before its definition, since we use # objects.py which doesn't explicitely initialise its members @@ -170,7 +170,7 @@ class ConfigObject(object): 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-msg=W0142 + obj = cls(**val_str) # pylint: disable=W0142 return obj @staticmethod @@ -966,7 +966,7 @@ class Node(TaggableObject): """Fill defaults for missing configuration values. """ - # pylint: disable-msg=E0203 + # pylint: disable=E0203 # because these are "defined" via slots, not manually if self.master_capable is None: self.master_capable = True @@ -1096,7 +1096,7 @@ class Cluster(TaggableObject): """Fill defaults for missing configuration values. """ - # pylint: disable-msg=E0203 + # pylint: disable=E0203 # because these are "defined" via slots, not manually if self.hvparams is None: self.hvparams = constants.HVC_DEFAULTS @@ -1524,15 +1524,20 @@ class InstanceConsole(ConfigObject): """ assert self.kind in constants.CONS_ALL, "Unknown console type" assert self.instance, "Missing instance name" - assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC] + assert self.message or self.kind in [constants.CONS_SSH, + constants.CONS_SPICE, + constants.CONS_VNC] assert self.host or self.kind == constants.CONS_MESSAGE assert self.port or self.kind in [constants.CONS_MESSAGE, constants.CONS_SSH] assert self.user or self.kind in [constants.CONS_MESSAGE, + constants.CONS_SPICE, constants.CONS_VNC] assert self.command or self.kind in [constants.CONS_MESSAGE, + constants.CONS_SPICE, constants.CONS_VNC] assert self.display or self.kind in [constants.CONS_MESSAGE, + constants.CONS_SPICE, constants.CONS_SSH] return True diff --git a/lib/opcodes.py b/lib/opcodes.py index 14a132b1aefb2afb4506805c416c8099fb85ac62..36104f7c7147792a8d6bc7f919ea4ed67f93b5f3 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -31,7 +31,7 @@ opcodes. # this are practically structures, so disable the message about too # few public methods: -# pylint: disable-msg=R0903 +# pylint: disable=R0903 import logging import re @@ -311,7 +311,7 @@ class BaseOpCode(object): field handling. """ - # pylint: disable-msg=E1101 + # pylint: disable=E1101 # as OP_ID is dynamically defined __metaclass__ = _AutoOpParamSlots @@ -485,7 +485,7 @@ class OpCode(BaseOpCode): @ivar priority: Opcode priority for queue """ - # pylint: disable-msg=E1101 + # pylint: disable=E1101 # as OP_ID is dynamically defined WITH_LU = True OP_PARAMS = [ diff --git a/lib/qlang.py b/lib/qlang.py index db0a59d357cd52115b555a2b6664f324bdc68444..bd2a3fd5f75046ec9c42852bf35132f5033f9d91 100644 --- a/lib/qlang.py +++ b/lib/qlang.py @@ -32,7 +32,7 @@ converted to callable functions by L{query._CompileFilter}. """ import re -import string # pylint: disable-msg=W0402 +import string # pylint: disable=W0402 import logging import pyparsing as pyp diff --git a/lib/query.py b/lib/query.py index ae7da5aa6195a66d534c31cf6f29c088bca4c709..7a9360c8726f9aaa1305d61cd2815d08153e66a7 100644 --- a/lib/query.py +++ b/lib/query.py @@ -137,7 +137,7 @@ _VTToQFT = { _SERIAL_NO_DOC = "%s object serial number, incremented on each modification" -def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613 +def _GetUnknownField(ctx, item): # pylint: disable=W0613 """Gets the contents of an unknown field. """ @@ -260,7 +260,7 @@ class _FilterHints: if op != qlang.OP_OR: self._NeedAllNames() - def NoteUnaryOp(self, op): # pylint: disable-msg=W0613 + def NoteUnaryOp(self, op): # pylint: disable=W0613 """Called when handling an unary operation. @type op: string @@ -337,7 +337,7 @@ class _FilterCompilerHelper: """Converts a query filter to a callable usable for filtering. """ - # String statement has no effect, pylint: disable-msg=W0105 + # String statement has no effect, pylint: disable=W0105 #: How deep filters can be nested _LEVELS_MAX = 10 @@ -546,7 +546,7 @@ class _FilterCompilerHelper: @param operands: List of operands """ - # Unused arguments, pylint: disable-msg=W0613 + # Unused arguments, pylint: disable=W0613 try: (name, value) = operands except (ValueError, TypeError): @@ -1062,7 +1062,7 @@ def _GetGroup(cb): return fn -def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613 +def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613 """Returns the name of a node's group. @type ctx: L{NodeQueryData} @@ -1408,7 +1408,7 @@ def _GetInstNic(index, cb): return fn -def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613 +def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613 """Get a NIC's IP address. @type ctx: L{InstanceQueryData} diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index af799c4684b0edfbae2bc6f3c41cd020240d0119..3e467394f0c45e727a541a55c8884af62d754a35 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -23,7 +23,7 @@ """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name, since the R_* names are not conforming @@ -198,7 +198,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-msg=W0142 + op = opcls(**params) # pylint: disable=W0142 op.Validate(False) except (errors.OpPrereqError, TypeError), err: raise http.HttpBadRequest("Invalid body parameters: %s" % err) diff --git a/lib/rapi/client.py b/lib/rapi/client.py index a4be072dbb23af782528c208b10179ff85521249..dac670b1b187818531d51bfdd720d3fe2c16fe22 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -261,7 +261,7 @@ def GenericCurlConfig(verbose=False, use_signal=False, return _ConfigCurl -class GanetiRapiClient(object): # pylint: disable-msg=R0904 +class GanetiRapiClient(object): # pylint: disable=R0904 """Ganeti RAPI client. """ diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py index fad46e46e9bcca7f8b59e6906b49b7d302bf46a0..d8524efad75201675dd98ab476053ac52a2d331f 100644 --- a/lib/rapi/connector.py +++ b/lib/rapi/connector.py @@ -22,7 +22,7 @@ """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name, since the R_* names are not conforming diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 049b97eb87c14de41d67d65bb4c757dbeab09c95..76d640b12b8a19b13bceddde163ab4f3bb4a06a1 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -51,7 +51,7 @@ PUT should be prefered over POST. """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name, since the R_* names are not conforming diff --git a/lib/rpc.py b/lib/rpc.py index c93c16d2b6045f1564d9b1ee8712596aa3706672..60a30ab0e45f2541a10f82f10796f65f019dd040 100644 --- a/lib/rpc.py +++ b/lib/rpc.py @@ -23,7 +23,7 @@ """ -# pylint: disable-msg=C0103,R0201,R0904 +# pylint: disable=C0103,R0201,R0904 # C0103: Invalid name, since call_ are not valid # R0201: Method could be a function, we keep all rpcs instance methods # as not to change them back and forth between static/instance methods @@ -48,7 +48,7 @@ from ganeti import ssconf from ganeti import runtime # pylint has a bug here, doesn't see this import -import ganeti.http.client # pylint: disable-msg=W0611 +import ganeti.http.client # pylint: disable=W0611 # Timeout for connecting to nodes (seconds) @@ -262,7 +262,7 @@ class RpcResult(object): args = (msg, ecode) else: args = (msg, ) - raise ec(*args) # pylint: disable-msg=W0142 + raise ec(*args) # pylint: disable=W0142 def _AddressLookup(node_list, diff --git a/lib/runtime.py b/lib/runtime.py index 6108e2671b6ab574e3770bd127452ef239fae81a..9e478ec5e5731cf62432fd06988840a287b49576 100644 --- a/lib/runtime.py +++ b/lib/runtime.py @@ -175,14 +175,14 @@ def GetEnts(resolver=GetentResolver): """ # We need to use the global keyword here - global _priv # pylint: disable-msg=W0603 + global _priv # pylint: disable=W0603 if not _priv: _priv_lock.acquire() try: if not _priv: # W0621: Redefine '_priv' from outer scope (used for singleton) - _priv = resolver() # pylint: disable-msg=W0621 + _priv = resolver() # pylint: disable=W0621 finally: _priv_lock.release() diff --git a/lib/serializer.py b/lib/serializer.py index 81f1add970f237a69ffcf0a156af131884547891..ff27261aab11c1ee4a04a48c6587ba45107b68da 100644 --- a/lib/serializer.py +++ b/lib/serializer.py @@ -24,7 +24,7 @@ This module introduces a simple abstraction over the serialization backend (currently json). """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name, since pylint doesn't see that Dump points to a # function and not a constant @@ -147,7 +147,7 @@ def LoadSignedJson(txt, key): raise errors.SignatureError("Invalid external message") if callable(key): - # pylint: disable-msg=E1103 + # pylint: disable=E1103 key_selector = signed_dict.get("key_selector", None) hmac_key = key(key_selector) if not hmac_key: diff --git a/lib/server/confd.py b/lib/server/confd.py index fe5f7e11b62f4559cc509611d65cf837e7529f35..d25a67715f44f6ce6f786b3afd2457d581e197e6 100644 --- a/lib/server/confd.py +++ b/lib/server/confd.py @@ -26,7 +26,7 @@ It uses UDP+HMAC for authentication with a global cluster key. """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name ganeti-confd import os @@ -35,7 +35,7 @@ import logging import time try: - # pylint: disable-msg=E0611 + # pylint: disable=E0611 from pyinotify import pyinotify except ImportError: import pyinotify @@ -265,7 +265,7 @@ def PrepConfd(options, _): # TODO: clarify how the server and reloader variables work (they are # not used) - # pylint: disable-msg=W0612 + # pylint: disable=W0612 mainloop = daemon.Mainloop() # Asyncronous confd UDP server @@ -285,7 +285,7 @@ def PrepConfd(options, _): return mainloop -def ExecConfd(options, args, prep_data): # pylint: disable-msg=W0613 +def ExecConfd(options, args, prep_data): # pylint: disable=W0613 """Main confd function, executed with PID file held """ diff --git a/lib/server/masterd.py b/lib/server/masterd.py index 17eabf85fefde7bbe7a717eb2a1a10903f5f6268..27bf561c4bb681cd287110594c0db1a025bfc692 100644 --- a/lib/server/masterd.py +++ b/lib/server/masterd.py @@ -26,7 +26,7 @@ inheritance from parent classes requires it. """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name ganeti-masterd import grp @@ -66,7 +66,7 @@ EXIT_NODESETUP_ERROR = constants.EXIT_NODESETUP_ERROR class ClientRequestWorker(workerpool.BaseWorker): - # pylint: disable-msg=W0221 + # pylint: disable=W0221 def RunTask(self, server, message, client): """Process the request. @@ -103,7 +103,7 @@ class ClientRequestWorker(workerpool.BaseWorker): client.send_message(reply) # awake the main thread so that it can write out the data. server.awaker.signal() - except: # pylint: disable-msg=W0702 + except: # pylint: disable=W0702 logging.exception("Send error") client.close_log() @@ -189,7 +189,7 @@ class ClientOps: def __init__(self, server): self.server = server - def handle_request(self, method, args): # pylint: disable-msg=R0911 + def handle_request(self, method, args): # pylint: disable=R0911 queue = self.server.context.jobqueue # TODO: Parameter validation @@ -374,7 +374,7 @@ class GanetiContext(object): This class creates and holds common objects shared by all threads. """ - # pylint: disable-msg=W0212 + # pylint: disable=W0212 # we do want to ensure a singleton here _instance = None @@ -613,7 +613,7 @@ def PrepMasterd(options, _): return (mainloop, master) -def ExecMasterd(options, args, prep_data): # pylint: disable-msg=W0613 +def ExecMasterd(options, args, prep_data): # pylint: disable=W0613 """Main master daemon function, executed with the PID file held. """ diff --git a/lib/server/noded.py b/lib/server/noded.py index 580467ae975c3b009369fa02fff00c4d15f09bd4..e852ca90d3eb9eb3aa3a0226627e3448d75d35d4 100644 --- a/lib/server/noded.py +++ b/lib/server/noded.py @@ -21,7 +21,7 @@ """Ganeti node daemon""" -# pylint: disable-msg=C0103,W0142 +# pylint: disable=C0103,W0142 # C0103: Functions in this module need to have a given name structure, # and the name of the daemon doesn't match @@ -49,7 +49,7 @@ from ganeti import storage from ganeti import serializer from ganeti import netutils -import ganeti.http.server # pylint: disable-msg=W0611 +import ganeti.http.server # pylint: disable=W0611 queue_lock = None @@ -61,7 +61,7 @@ def _PrepareQueueLock(): @return: None for success, otherwise an exception object """ - global queue_lock # pylint: disable-msg=W0603 + global queue_lock # pylint: disable=W0603 if queue_lock is not None: return None @@ -129,7 +129,7 @@ class NodeHttpServer(http.server.HttpServer): """ # too many public methods, and unused args - all methods get params # due to the API - # pylint: disable-msg=R0904,W0613 + # pylint: disable=R0904,W0613 def __init__(self, *args, **kwargs): http.server.HttpServer.__init__(self, *args, **kwargs) self.noded_pid = os.getpid() @@ -1019,7 +1019,7 @@ def PrepNoded(options, _): return (mainloop, server) -def ExecNoded(options, args, prep_data): # pylint: disable-msg=W0613 +def ExecNoded(options, args, prep_data): # pylint: disable=W0613 """Main node daemon function, executed with the PID file held. """ diff --git a/lib/server/rapi.py b/lib/server/rapi.py index b3a71aa08d96435b4f388a954af9df721c284f09..6a8a76aee19fd1ad3ced1c09cd1013278aca08eb 100644 --- a/lib/server/rapi.py +++ b/lib/server/rapi.py @@ -22,7 +22,7 @@ """ -# pylint: disable-msg=C0103,W0142 +# pylint: disable=C0103,W0142 # C0103: Invalid name ganeti-watcher @@ -34,7 +34,7 @@ import os.path import errno try: - from pyinotify import pyinotify # pylint: disable-msg=E0611 + from pyinotify import pyinotify # pylint: disable=E0611 except ImportError: import pyinotify @@ -49,7 +49,7 @@ from ganeti import compat from ganeti import utils from ganeti.rapi import connector -import ganeti.http.auth # pylint: disable-msg=W0611 +import ganeti.http.auth # pylint: disable=W0611 import ganeti.http.server @@ -90,7 +90,7 @@ class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication, AUTH_REALM = "Ganeti Remote API" def __init__(self, *args, **kwargs): - # pylint: disable-msg=W0233 + # pylint: disable=W0233 # it seems pylint doesn't see the second parent class there http.server.HttpServer.__init__(self, *args, **kwargs) http.auth.HttpServerRequestAuthentication.__init__(self) @@ -118,7 +118,7 @@ class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication, users = http.auth.ParsePasswordFile(contents) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 # We don't care about the type of exception logging.error("Error while parsing %s: %s", filename, err) return False @@ -321,14 +321,14 @@ def PrepRapi(options, _): server.LoadUsers(constants.RAPI_USERS_FILE) - # pylint: disable-msg=E1101 + # pylint: disable=E1101 # it seems pylint doesn't see the second parent class there server.Start() return (mainloop, server) -def ExecRapi(options, args, prep_data): # pylint: disable-msg=W0613 +def ExecRapi(options, args, prep_data): # pylint: disable=W0613 """Main remote API function, executed with the PID file held. """ diff --git a/lib/storage.py b/lib/storage.py index cf8768636c911023c30793608a1a8d25aeef4650..d382be53c8e249e1d186226a23133f1dff3632f5 100644 --- a/lib/storage.py +++ b/lib/storage.py @@ -23,7 +23,7 @@ """ -# pylint: disable-msg=W0232,R0201 +# pylint: disable=W0232,R0201 # W0232, since we use these as singletons rather than object holding # data @@ -58,7 +58,7 @@ class _Base: """ raise NotImplementedError() - def Modify(self, name, changes): # pylint: disable-msg=W0613 + def Modify(self, name, changes): # pylint: disable=W0613 """Modifies an entity within the storage unit. @type name: string @@ -84,7 +84,7 @@ class _Base: raise NotImplementedError() -class FileStorage(_Base): # pylint: disable-msg=W0223 +class FileStorage(_Base): # pylint: disable=W0223 """File storage unit. """ @@ -161,7 +161,7 @@ class FileStorage(_Base): # pylint: disable-msg=W0223 return values -class _LvmBase(_Base): # pylint: disable-msg=W0223 +class _LvmBase(_Base): # pylint: disable=W0223 """Base class for LVM storage containers. @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_* @@ -256,7 +256,7 @@ class _LvmBase(_Base): # pylint: disable-msg=W0223 if callable(mapper): # we got a function, call it with all the declared fields - val = mapper(*values) # pylint: disable-msg=W0142 + val = mapper(*values) # pylint: disable=W0142 elif len(values) == 1: assert mapper is None, ("Invalid mapper value (neither callable" " nor None) for one-element fields") @@ -347,7 +347,7 @@ def _LvmPvGetAllocatable(attr): return False -class LvmPvStorage(_LvmBase): # pylint: disable-msg=W0223 +class LvmPvStorage(_LvmBase): # pylint: disable=W0223 """LVM Physical Volume storage unit. """ diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py index 2f2da51e23dd33de47400ff02b879af7457c63c4..fd7ab70fc4d19da57a0787d179c097f1a6588b02 100644 --- a/lib/utils/__init__.py +++ b/lib/utils/__init__.py @@ -26,7 +26,7 @@ the command line scripts. """ -# Allow wildcard import in pylint: disable-msg=W0401 +# Allow wildcard import in pylint: disable=W0401 import os import re @@ -599,7 +599,7 @@ class SignalWakeupFd(object): _set_wakeup_fd_fn = signal.set_wakeup_fd except AttributeError: # Not supported - def _SetWakeupFd(self, _): # pylint: disable-msg=R0201 + def _SetWakeupFd(self, _): # pylint: disable=R0201 return -1 else: def _SetWakeupFd(self, fd): diff --git a/lib/utils/io.py b/lib/utils/io.py index c64c125d1de97f65dbaf168b9116c7d8133bb69f..91899a210f4d3a70607f51d1549066af8acbb319 100644 --- a/lib/utils/io.py +++ b/lib/utils/io.py @@ -465,6 +465,20 @@ def IsNormAbsPath(path): return os.path.normpath(path) == path and os.path.isabs(path) +def IsBelowDir(root, other_path): + """Check whether a path is below a root dir. + + This works around the nasty byte-byte comparisation of commonprefix. + + """ + if not (os.path.isabs(root) and os.path.isabs(other_path)): + raise ValueError("Provided paths '%s' and '%s' are not absolute" % + (root, other_path)) + prepared_root = "%s%s" % (os.path.normpath(root), os.sep) + return os.path.commonprefix([prepared_root, + os.path.normpath(other_path)]) == prepared_root + + def PathJoin(*args): """Safe-join a list of path components. @@ -488,10 +502,9 @@ def PathJoin(*args): if not IsNormAbsPath(result): raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args)) # check that we're still under the original prefix - prefix = os.path.commonprefix([root, result]) - if prefix != root: + if not IsBelowDir(root, result): raise ValueError("Error: path joining resulted in different prefix" - " (%s != %s)" % (prefix, root)) + " (%s != %s)" % (result, root)) return result diff --git a/lib/utils/log.py b/lib/utils/log.py index 1cc6d82c75a44248aa3b2f30cafffe4aa211809c..281f59045ac8e7e7ae2505e154d55f6fdf4f1d84 100644 --- a/lib/utils/log.py +++ b/lib/utils/log.py @@ -51,13 +51,13 @@ class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler): self._reopen = False - def shouldRollover(self, _): # pylint: disable-msg=C0103 + def shouldRollover(self, _): # pylint: disable=C0103 """Determine whether log file should be reopened. """ return self._reopen or not self.stream - def doRollover(self): # pylint: disable-msg=C0103 + def doRollover(self): # pylint: disable=C0103 """Reopens the log file. """ @@ -88,7 +88,7 @@ def _LogErrorsToConsole(base): This needs to be in a function for unittesting. """ - class wrapped(base): # pylint: disable-msg=C0103 + class wrapped(base): # pylint: disable=C0103 """Log handler that doesn't fallback to stderr. When an error occurs while writing on the logfile, logging.FileHandler @@ -108,7 +108,7 @@ def _LogErrorsToConsole(base): assert not hasattr(self, "_console") self._console = console - def handleError(self, record): # pylint: disable-msg=C0103 + def handleError(self, record): # pylint: disable=C0103 """Handle errors which occur during an emit() call. Try to handle errors with FileHandler method, if it fails write to @@ -117,13 +117,13 @@ def _LogErrorsToConsole(base): """ try: base.handleError(record) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 if self._console: try: - # Ignore warning about "self.format", pylint: disable-msg=E1101 + # Ignore warning about "self.format", pylint: disable=E1101 self._console.write("Cannot log message:\n%s\n" % self.format(record)) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 # Log handler tried everything it could, now just give up pass diff --git a/lib/utils/mlock.py b/lib/utils/mlock.py index a8fe5b70e1c92d34e9816033e5e2bb7c57541db3..ea14d684800c8a39810f9ef127acbc3b4aa39715 100644 --- a/lib/utils/mlock.py +++ b/lib/utils/mlock.py @@ -28,7 +28,7 @@ import logging from ganeti import errors try: - # pylint: disable-msg=F0401 + # pylint: disable=F0401 import ctypes except ImportError: ctypes = None @@ -65,11 +65,11 @@ def Mlockall(_ctypes=ctypes): # By declaring this variable as a pointer to an integer we can then access # its value correctly, should the mlockall call fail, in order to see what # the actual error code was. - # pylint: disable-msg=W0212 + # pylint: disable=W0212 libc.__errno_location.restype = _ctypes.POINTER(_ctypes.c_int) if libc.mlockall(_MCL_CURRENT | _MCL_FUTURE): - # pylint: disable-msg=W0212 + # pylint: disable=W0212 logging.error("Cannot set memory lock: %s", os.strerror(libc.__errno_location().contents.value)) return diff --git a/lib/utils/process.py b/lib/utils/process.py index 78937610127153058cbc773a202a63666401a417..a4587553239121a4dd1970591cfca97b1c3cf8ca 100644 --- a/lib/utils/process.py +++ b/lib/utils/process.py @@ -57,7 +57,7 @@ def DisableFork(): """Disables the use of fork(2). """ - global _no_fork # pylint: disable-msg=W0603 + global _no_fork # pylint: disable=W0603 _no_fork = True @@ -342,7 +342,7 @@ def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None, output, output_fd, pidfile) finally: # Well, maybe child process failed - os._exit(1) # pylint: disable-msg=W0212 + os._exit(1) # pylint: disable=W0212 finally: utils_wrapper.CloseFdNoError(errpipe_write) @@ -396,7 +396,7 @@ def _StartDaemonChild(errpipe_read, errpipe_write, pid = os.fork() if pid != 0: # Exit first child process - os._exit(0) # pylint: disable-msg=W0212 + os._exit(0) # pylint: disable=W0212 # Make sure pipe is closed on execv* (and thereby notifies # original process) @@ -431,15 +431,15 @@ def _StartDaemonChild(errpipe_read, errpipe_write, os.execvp(args[0], args) else: os.execvpe(args[0], args, env) - except: # pylint: disable-msg=W0702 + except: # pylint: disable=W0702 try: # Report errors to original process WriteErrorToFD(errpipe_write, str(sys.exc_info()[1])) - except: # pylint: disable-msg=W0702 + except: # pylint: disable=W0702 # Ignore errors in error handling pass - os._exit(1) # pylint: disable-msg=W0212 + os._exit(1) # pylint: disable=W0212 def WriteErrorToFD(fd, err): @@ -699,7 +699,7 @@ def RunParts(dir_name, env=None, reset_env=False): else: try: result = RunCmd([fname], env=env, reset_env=reset_env) - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 rr.append((relname, constants.RUNPARTS_ERR, str(err))) else: rr.append((relname, constants.RUNPARTS_RUN, result)) @@ -850,7 +850,7 @@ def Daemonize(logfile): process and a callable to reopen log files """ - # pylint: disable-msg=W0212 + # pylint: disable=W0212 # yes, we really want os._exit # TODO: do another attempt to merge Daemonize and StartDaemon, or at @@ -975,12 +975,12 @@ def RunInSeparateProcess(fn, *args): # Call function result = int(bool(fn(*args))) assert result in (0, 1) - except: # pylint: disable-msg=W0702 + except: # pylint: disable=W0702 logging.exception("Error while calling function in separate process") # 0 and 1 are reserved for the return value result = 33 - os._exit(result) # pylint: disable-msg=W0212 + os._exit(result) # pylint: disable=W0212 # Parent process diff --git a/lib/utils/retry.py b/lib/utils/retry.py index 4e36332e089c2e338be697eb3f37e6baa8afe943..cc7541cffbb85dbbed20a249a99e7041e9bf471f 100644 --- a/lib/utils/retry.py +++ b/lib/utils/retry.py @@ -160,7 +160,7 @@ def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep, while True: retry_args = [] try: - # pylint: disable-msg=W0142 + # pylint: disable=W0142 return fn(*args) except RetryAgain, err: retry_args = err.args @@ -171,7 +171,7 @@ def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep, remaining_time = end_time - _time_fn() if remaining_time < 0.0: - # pylint: disable-msg=W0142 + # pylint: disable=W0142 raise RetryTimeout(*retry_args) assert remaining_time >= 0.0 @@ -208,7 +208,7 @@ def SimpleRetry(expected, fn, delay, timeout, args=None, wait_fn=time.sleep, rdict = {} def helper(*innerargs): - # pylint: disable-msg=W0142 + # pylint: disable=W0142 result = rdict["result"] = fn(*innerargs) if not ((callable(expected) and expected(result)) or result == expected): raise RetryAgain() diff --git a/lib/utils/text.py b/lib/utils/text.py index a4c2777ecda452f022cd63551be8a89b36f1846d..7ec049e951a21b7717bcec91d5b3aa680fc5025d 100644 --- a/lib/utils/text.py +++ b/lib/utils/text.py @@ -381,7 +381,7 @@ def UnescapeAndSplit(text, sep=","): e1 = slist.pop(0) if e1.endswith("\\"): num_b = len(e1) - len(e1.rstrip("\\")) - if num_b % 2 == 1: + if num_b % 2 == 1 and slist: e2 = slist.pop(0) # here the backslashes remain (all), and will be reduced in # the next step @@ -463,7 +463,7 @@ class LineSplitter: if args: # Python 2.4 doesn't have functools.partial yet self._line_fn = \ - lambda line: line_fn(line, *args) # pylint: disable-msg=W0142 + lambda line: line_fn(line, *args) # pylint: disable=W0142 else: self._line_fn = line_fn diff --git a/lib/utils/wrapper.py b/lib/utils/wrapper.py index 6cf16541f4edbe45747a00ea8f169b39a8cd2b66..256d2f1d6d1f1ad686bf75ae7dd3739526d4aca1 100644 --- a/lib/utils/wrapper.py +++ b/lib/utils/wrapper.py @@ -181,7 +181,7 @@ def ResetTempfileModule(): a temporary name. """ - # pylint: disable-msg=W0212 + # pylint: disable=W0212 if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"): tempfile._once_lock.acquire() try: diff --git a/lib/watcher/__init__.py b/lib/watcher/__init__.py index 323283b5b23a648a9ce9db3694aafeed3f48237d..c84f3e802d3166ef1674881976d63438e4bf29f7 100644 --- a/lib/watcher/__init__.py +++ b/lib/watcher/__init__.py @@ -50,7 +50,7 @@ from ganeti import objects from ganeti import ssconf from ganeti import ht -import ganeti.rapi.client # pylint: disable-msg=W0611 +import ganeti.rapi.client # pylint: disable=W0611 from ganeti.watcher import nodemaint from ganeti.watcher import state @@ -106,8 +106,8 @@ def RunWatcherHooks(): try: results = utils.RunParts(hooks_dir) - except Exception: # pylint: disable-msg=W0703 - logging.exception("RunParts %s failed: %s", hooks_dir) + except Exception, err: # pylint: disable=W0703 + logging.exception("RunParts %s failed: %s", hooks_dir, err) return for (relname, status, runresult) in results: @@ -193,7 +193,7 @@ def _CheckInstances(cl, notepad, instances): logging.info("Restarting instance '%s' (attempt #%s)", inst.name, n + 1) inst.Restart(cl) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 logging.exception("Error while restarting instance '%s'", inst.name) else: started.add(inst.name) @@ -255,7 +255,7 @@ def _CheckDisks(cl, notepad, nodes, instances, started): try: logging.info("Activating disks for instance '%s'", inst.name) inst.ActivateDisks(cl) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 logging.exception("Error while activating disks for instance '%s'", inst.name) @@ -313,7 +313,7 @@ def _VerifyDisks(cl, uuid, nodes, instances): try: cli.PollJob(job_id, cl=cl, feedback_fn=logging.debug) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 logging.exception("Error while activating disks") @@ -549,7 +549,7 @@ def _StartGroupChildren(cl, wait): try: # TODO: Should utils.StartDaemon be used instead? pid = os.spawnv(os.P_NOWAIT, args[0], args) - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 logging.exception("Failed to start child for group '%s' (%s)", name, uuid) else: @@ -596,8 +596,8 @@ def _GlobalWatcher(opts): # Run node maintenance in all cases, even if master, so that old masters can # be properly cleaned up - if nodemaint.NodeMaintenance.ShouldRun(): # pylint: disable-msg=E0602 - nodemaint.NodeMaintenance().Exec() # pylint: disable-msg=E0602 + if nodemaint.NodeMaintenance.ShouldRun(): # pylint: disable=E0602 + nodemaint.NodeMaintenance().Exec() # pylint: disable=E0602 try: client = GetLuxiClient(True) @@ -729,11 +729,11 @@ def _GroupWatcher(opts): logging.debug("Using state file %s", state_path) # Global watcher - statefile = state.OpenStateFile(state_path) # pylint: disable-msg=E0602 + statefile = state.OpenStateFile(state_path) # pylint: disable=E0602 if not statefile: return constants.EXIT_FAILURE - notepad = state.WatcherState(statefile) # pylint: disable-msg=E0602 + notepad = state.WatcherState(statefile) # pylint: disable=E0602 try: # Connect to master daemon client = GetLuxiClient(False) diff --git a/lib/watcher/nodemaint.py b/lib/watcher/nodemaint.py index a00fc9b19fd07d457c4572604569ce06e6e2b78b..33be4f13a7dc383fd578b7fe739783b5fbd94d54 100644 --- a/lib/watcher/nodemaint.py +++ b/lib/watcher/nodemaint.py @@ -34,7 +34,7 @@ from ganeti import ssconf from ganeti import utils from ganeti import confd -import ganeti.confd.client # pylint: disable-msg=W0611 +import ganeti.confd.client # pylint: disable=W0611 class NodeMaintenance(object): @@ -70,7 +70,7 @@ class NodeMaintenance(object): hv = hypervisor.GetHypervisor(hv_name) ilist = hv.ListInstances() results.extend([(iname, hv_name) for iname in ilist]) - except: # pylint: disable-msg=W0702 + except: # pylint: disable=W0702 logging.error("Error while listing instances for hypervisor %s", hv_name, exc_info=True) return results @@ -121,7 +121,7 @@ class NodeMaintenance(object): logging.info("Following DRBD minors should not be active," " shutting them down: %s", utils.CommaJoin(drbd_running)) for minor in drbd_running: - # pylint: disable-msg=W0212 + # pylint: disable=W0212 # using the private method as is, pending enhancements to the DRBD # interface bdev.DRBD8._ShutdownAll(minor) diff --git a/lib/watcher/state.py b/lib/watcher/state.py index 34630f49921f45751eed42ac00b9cc2161d63cba..be52c65b587b7c2530a8a6a026f9c5d561fc19f2 100644 --- a/lib/watcher/state.py +++ b/lib/watcher/state.py @@ -86,7 +86,7 @@ class WatcherState(object): self._data = {} else: self._data = serializer.Load(state_data) - except Exception, msg: # pylint: disable-msg=W0703 + except Exception, msg: # pylint: disable=W0703 # Ignore errors while loading the file and treat it as empty self._data = {} logging.warning(("Invalid state file. Using defaults." diff --git a/lib/workerpool.py b/lib/workerpool.py index d276b1837b8ca81265799ca40cbf5258f3e6bc39..4736be5e7256240d270bc8a9a37f13226719982d 100644 --- a/lib/workerpool.py +++ b/lib/workerpool.py @@ -59,7 +59,7 @@ class BaseWorker(threading.Thread, object): Users of a worker pool must override RunTask in a subclass. """ - # pylint: disable-msg=W0212 + # pylint: disable=W0212 def __init__(self, pool, worker_id): """Constructor for BaseWorker thread. @@ -169,7 +169,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-msg=W0142 + self.RunTask(*args) # pylint: disable=W0142 finally: self.SetTaskName(None) logging.debug("Done with task %r, priority %s", args, priority) @@ -184,7 +184,7 @@ class BaseWorker(threading.Thread, object): args, defer.priority) assert self._HasRunningTaskUnlocked() - except: # pylint: disable-msg=W0702 + except: # pylint: disable=W0702 logging.exception("Caught unhandled exception") assert self._HasRunningTaskUnlocked() @@ -384,7 +384,7 @@ class WorkerPool(object): """ for worker in self._workers + self._termworkers: - if worker._HasRunningTaskUnlocked(): # pylint: disable-msg=W0212 + if worker._HasRunningTaskUnlocked(): # pylint: disable=W0212 return True return False diff --git a/man/ganeti-listrunner.rst b/man/ganeti-listrunner.rst index 91a9c124a438323dd5239fbf1ca78f61a349d6e0..b6e024268d047750da45d4e7ae5be45648d5fa8e 100644 --- a/man/ganeti-listrunner.rst +++ b/man/ganeti-listrunner.rst @@ -72,6 +72,9 @@ The options that can be passed to the program are as follows: ``-A`` Use an existing ssh-agent instead of password authentication. +``--args`` + Arguments to pass to executable (``-x``). + EXIT STATUS ----------- diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst index 5d906a40399a1bec58936ba33251b34faa492bd3..9ae83c21b568c2239c5bc982cf3d8ba2668c8f63 100644 --- a/man/gnt-instance.rst +++ b/man/gnt-instance.rst @@ -300,6 +300,59 @@ spice\_ip\_version this case, if the ``spice_ip_version`` parameter is not used, the default IP version of the cluster will be used. +spice\_password\_file + Valid for the KVM hypervisor. + + Specifies a file containing the password that must be used when + connecting via the SPICE protocol. If the option is not specified, + passwordless connections are allowed. + +spice\_image\_compression + Valid for the KVM hypervisor. + + Configures the SPICE lossless image compression. Valid values are: + + - auto_glz + - auto_lz + - quic + - glz + - lz + - off + +spice\_jpeg\_wan\_compression + Valid for the KVM hypervisor. + + Configures how SPICE should use the jpeg algorithm for lossy image + compression on slow links. Valid values are: + + - auto + - never + - always + +spice\_zlib\_glz\_wan\_compression + Valid for the KVM hypervisor. + + Configures how SPICE should use the zlib-glz algorithm for lossy image + compression on slow links. Valid values are: + + - auto + - never + - always + +spice\_streaming\_video + Valid for the KVM hypervisor. + + Configures how SPICE should detect video streams. Valid values are: + + - off + - all + - filter + +spice\_playback\_compression + Valid for the KVM hypervisor. + + Configures whether SPICE should compress audio streams or not. + acpi Valid for the Xen HVM and KVM hypervisors. diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index 6fa83c72727bdc0da2e4c0c89f6e18c0326d1f42..fc7abbcfa9b3acc202059f2ab2bb30dd46983d55 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -23,7 +23,7 @@ """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # due to invalid name import sys @@ -46,7 +46,7 @@ from ganeti import utils from ganeti import rapi from ganeti import constants -import ganeti.rapi.client # pylint: disable-msg=W0611 +import ganeti.rapi.client # pylint: disable=W0611 def _FormatHeader(line, end=72): @@ -54,7 +54,7 @@ def _FormatHeader(line, end=72): """ line = "---- " + line + " " - line += "-" * (end-len(line)) + line += "-" * (end - len(line)) line = line.rstrip() return line diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py index bfda23674505602eb7cec7da713ac736b23d9e3b..bd3d737d5d6abc2dd5edad52c227a00d480eab96 100644 --- a/qa/qa_cluster.py +++ b/qa/qa_cluster.py @@ -40,6 +40,7 @@ from qa_utils import AssertEqual, AssertCommand, GetCommandOutput #: cluster verify command _CLUSTER_VERIFY = ["gnt-cluster", "verify"] + def _RemoveFileFromAllNodes(filename): """Removes a file from all nodes. @@ -231,14 +232,14 @@ def TestClusterReservedLvs(): (False, _CLUSTER_VERIFY), (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]), (False, ["lvcreate", "-L1G", "-nqa-test", "xenvg"]), - (True, _CLUSTER_VERIFY), + (True, _CLUSTER_VERIFY), (False, ["gnt-cluster", "modify", "--reserved-lvs", "xenvg/qa-test,.*/other-test"]), (False, _CLUSTER_VERIFY), (False, ["gnt-cluster", "modify", "--reserved-lvs", ".*/qa-.*"]), (False, _CLUSTER_VERIFY), (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]), - (True, _CLUSTER_VERIFY), + (True, _CLUSTER_VERIFY), (False, ["lvremove", "-f", "xenvg/qa-test"]), (False, _CLUSTER_VERIFY), ]: @@ -251,19 +252,19 @@ def TestClusterModifyBe(): # mem (False, ["gnt-cluster", "modify", "-B", "memory=256"]), (False, ["sh", "-c", "gnt-cluster info|grep '^ *memory: 256$'"]), - (True, ["gnt-cluster", "modify", "-B", "memory=a"]), + (True, ["gnt-cluster", "modify", "-B", "memory=a"]), (False, ["gnt-cluster", "modify", "-B", "memory=128"]), (False, ["sh", "-c", "gnt-cluster info|grep '^ *memory: 128$'"]), # vcpus (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]), (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]), - (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]), + (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]), (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]), (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]), # auto_balance (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]), (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]), - (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]), + (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]), (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]), (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]), ]: @@ -274,6 +275,7 @@ def TestClusterModifyBe(): if bep: AssertCommand(["gnt-cluster", "modify", "-B", bep]) + def TestClusterInfo(): """gnt-cluster info""" AssertCommand(["gnt-cluster", "info"]) @@ -306,7 +308,7 @@ def TestClusterRenewCrypto(): ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"], ] for i in conflicting: - AssertCommand(cmd+i, fail=True) + AssertCommand(cmd + i, fail=True) # Invalid RAPI certificate cmd = ["gnt-cluster", "renew-crypto", "--force", diff --git a/qa/qa_config.py b/qa/qa_config.py index 29c6e6955729a02130f5a798d215d700511d4bb0..b4cff14f9c115bdf4d09f873525a56da1b0a29d1 100644 --- a/qa/qa_config.py +++ b/qa/qa_config.py @@ -39,7 +39,7 @@ def Load(path): """Loads the passed configuration file. """ - global cfg # pylint: disable-msg=W0603 + global cfg # pylint: disable=W0603 cfg = serializer.LoadJson(utils.ReadFile(path)) diff --git a/qa/qa_env.py b/qa/qa_env.py index e9293b0765c11cbe8aa16c27ec2e17e327436813..986128b2fe143b0d7061895d3403a62c523449cb 100644 --- a/qa/qa_env.py +++ b/qa/qa_env.py @@ -42,7 +42,7 @@ def TestGanetiCommands(): """Test availibility of Ganeti commands. """ - cmds = ( ["gnt-backup", "--version"], + cmds = (["gnt-backup", "--version"], ["gnt-cluster", "--version"], ["gnt-debug", "--version"], ["gnt-instance", "--version"], @@ -76,7 +76,7 @@ def TestIcmpPing(): seccmd = [pingsecondary, "-e"] for i in nodes: pricmd.append(i["primary"]) - if i.has_key("secondary"): + if "secondary" in i: seccmd.append(i["secondary"]) pristr = utils.ShellQuoteArgs(pricmd) diff --git a/qa/qa_error.py b/qa/qa_error.py index 131b69bc8d4e6d25f30bfc35fd7408857bfe0ebe..8b5d4b90308ffa40f53e951377a3963e51563203 100644 --- a/qa/qa_error.py +++ b/qa/qa_error.py @@ -23,6 +23,7 @@ """ + class Error(Exception): """An error occurred during Q&A testing. diff --git a/qa/qa_instance.py b/qa/qa_instance.py index 4747839eb0ba9d00803496d6e49152e4d4922d0e..45ee521fd1a913acecd7f8735a51061cb54d9784 100644 --- a/qa/qa_instance.py +++ b/qa/qa_instance.py @@ -251,7 +251,7 @@ def TestInstanceConsole(instance): def TestReplaceDisks(instance, pnode, snode, othernode): """gnt-instance replace-disks""" - # pylint: disable-msg=W0613 + # pylint: disable=W0613 # due to unused pnode arg # FIXME: should be removed from the function completely def buildcmd(args): @@ -429,7 +429,7 @@ def _TestInstanceDiskFailure(instance, node, node2, onmaster): def TestInstanceMasterDiskFailure(instance, node, node2): """Testing disk failure on master node.""" - # pylint: disable-msg=W0613 + # pylint: disable=W0613 # due to unused args print qa_utils.FormatError("Disk failure on primary node cannot be" " tested due to potential crashes.") diff --git a/qa/qa_node.py b/qa/qa_node.py index 434de3832386f58766eb8a96844a043a72d92e67..8b2d406f3ee30e6c0e6864aa51ed51be82eb5199 100644 --- a/qa/qa_node.py +++ b/qa/qa_node.py @@ -275,7 +275,7 @@ def TestOutOfBand(): # Power off on master without options should fail AssertCommand(["gnt-node", "power", "-f", "off", master_name], fail=True) # With force master it should still fail - AssertCommand(["gnt-node", "power", "-f", "--ignore-status", "off", + AssertCommand(["gnt-node", "power", "-f", "--ignore-status", "off", master_name], fail=True) @@ -302,7 +302,7 @@ def TestOutOfBand(): AssertCommand(["gnt-node", "health"], fail=True) # Correct Data, exit 0 - _UpdateOobFile(data_path, serializer.DumpJson({ "powered": True })) + _UpdateOobFile(data_path, serializer.DumpJson({"powered": True})) AssertCommand(["gnt-node", "power", "status", node_name]) _AssertOobCall(verify_path, "power-status %s" % full_node_name) @@ -315,7 +315,6 @@ def TestOutOfBand(): AssertCommand(["gnt-node", "health"]) - # Those commands should fail as they expect no data regardless of exit 0 AssertCommand(["gnt-node", "power", "on", node_name], fail=True) _AssertOobCall(verify_path, "power-on %s" % full_node_name) diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py index 407279efcce15321864d637e03d2c2f912d7d8ed..ece6e645277af5d0d2c2d8f272ba01d84543d1ed 100644 --- a/qa/qa_rapi.py +++ b/qa/qa_rapi.py @@ -35,7 +35,7 @@ from ganeti import query from ganeti import compat from ganeti import qlang -import ganeti.rapi.client # pylint: disable-msg=W0611 +import ganeti.rapi.client # pylint: disable=W0611 import ganeti.rapi.client_utils import qa_config @@ -55,7 +55,7 @@ def Setup(username, password): """Configures the RAPI client. """ - # pylint: disable-msg=W0603 + # pylint: disable=W0603 # due to global usage global _rapi_ca global _rapi_client @@ -122,7 +122,7 @@ def Enabled(): def _DoTests(uris): - # pylint: disable-msg=W0212 + # pylint: disable=W0212 # due to _SendRequest usage results = [] diff --git a/qa/qa_utils.py b/qa/qa_utils.py index 441bc8768ea0e9f34ac7deac5ffb2ffb642c6ab8..671a6f21abf2ecb976a572c61a1371f590060564 100644 --- a/qa/qa_utils.py +++ b/qa/qa_utils.py @@ -50,7 +50,7 @@ def _SetupColours(): """Initializes the colour constants. """ - # pylint: disable-msg=W0603 + # pylint: disable=W0603 # due to global usage global _INFO_SEQ, _WARNING_SEQ, _ERROR_SEQ, _RESET_SEQ diff --git a/test/data/proc_drbd83_sync.txt b/test/data/proc_drbd83_sync.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae94186ccab349653a265a26a79ad25693609db9 Binary files /dev/null and b/test/data/proc_drbd83_sync.txt differ diff --git a/test/data/proc_drbd83_sync_krnl2.6.39.txt b/test/data/proc_drbd83_sync_krnl2.6.39.txt new file mode 100644 index 0000000000000000000000000000000000000000..cf3f541d3c52a7c26bf77657133ac27d15a389fd Binary files /dev/null and b/test/data/proc_drbd83_sync_krnl2.6.39.txt differ diff --git a/test/ganeti.bdev_unittest.py b/test/ganeti.bdev_unittest.py index fa1433ec169ab648a08033c2c22834a50da53c54..6f8e3d40adc1616146f36fedba55cd01daee2abc 100755 --- a/test/ganeti.bdev_unittest.py +++ b/test/ganeti.bdev_unittest.py @@ -166,12 +166,21 @@ class TestDRBD8Status(testutils.GanetiTestCase): proc_data = self._TestDataFilename("proc_drbd8.txt") proc80e_data = self._TestDataFilename("proc_drbd80-emptyline.txt") proc83_data = self._TestDataFilename("proc_drbd83.txt") + proc83_sync_data = self._TestDataFilename("proc_drbd83_sync.txt") + proc83_sync_krnl_data = \ + self._TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt") self.proc_data = bdev.DRBD8._GetProcData(filename=proc_data) self.proc80e_data = bdev.DRBD8._GetProcData(filename=proc80e_data) self.proc83_data = bdev.DRBD8._GetProcData(filename=proc83_data) + self.proc83_sync_data = bdev.DRBD8._GetProcData(filename=proc83_sync_data) + self.proc83_sync_krnl_data = \ + bdev.DRBD8._GetProcData(filename=proc83_sync_krnl_data) self.mass_data = bdev.DRBD8._MassageProcData(self.proc_data) self.mass80e_data = bdev.DRBD8._MassageProcData(self.proc80e_data) self.mass83_data = bdev.DRBD8._MassageProcData(self.proc83_data) + self.mass83_sync_data = bdev.DRBD8._MassageProcData(self.proc83_sync_data) + self.mass83_sync_krnl_data = \ + bdev.DRBD8._MassageProcData(self.proc83_sync_krnl_data) def testIOErrors(self): """Test handling of errors while reading the proc file.""" @@ -251,5 +260,15 @@ class TestDRBD8Status(testutils.GanetiTestCase): stats.rrole == 'Unknown' and stats.is_disk_uptodate) + def testDRBD83SyncFine(self): + stats = bdev.DRBD8Status(self.mass83_sync_data[3]) + self.failUnless(stats.is_in_resync) + self.failUnless(stats.sync_percent is not None) + + def testDRBD83SyncBroken(self): + stats = bdev.DRBD8Status(self.mass83_sync_krnl_data[3]) + self.failUnless(stats.is_in_resync) + self.failUnless(stats.sync_percent is not None) + if __name__ == '__main__': testutils.GanetiTestProgram() diff --git a/test/ganeti.hypervisor.hv_kvm_unittest.py b/test/ganeti.hypervisor.hv_kvm_unittest.py index d9e5e7377db1cb7955dedd6fcdbe729749c79bd2..14ab34546910d5a76350a4c9ca9c06aecff68d1c 100755 --- a/test/ganeti.hypervisor.hv_kvm_unittest.py +++ b/test/ganeti.hypervisor.hv_kvm_unittest.py @@ -21,8 +21,13 @@ """Script for testing the hypervisor.hv_kvm module""" +import threading +import tempfile import unittest +import socket +import os +from ganeti import serializer from ganeti import constants from ganeti import compat from ganeti import objects @@ -33,6 +38,129 @@ from ganeti.hypervisor import hv_kvm import testutils +class QmpStub(threading.Thread): + """Stub for a QMP endpoint for a KVM instance + + """ + _QMP_BANNER_DATA = {"QMP": {"version": { + "package": "", + "qemu": {"micro": 50, "minor": 13, "major": 0}, + "capabilities": [], + }}} + _EMPTY_RESPONSE = {"return": []} + + def __init__(self, socket_filename, server_responses): + """Creates a QMP stub + + @type socket_filename: string + @param socket_filename: filename of the UNIX socket that will be created + this class and used for the communication + @type server_responses: list + @param server_responses: list of responses that the server sends in response + to whatever it receives + """ + threading.Thread.__init__(self) + self.socket_filename = socket_filename + self.script = server_responses + + self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.socket.bind(self.socket_filename) + self.socket.listen(1) + + def run(self): + # Hypothesis: the messages we receive contain only a complete QMP message + # encoded in JSON. + conn, addr = self.socket.accept() + + # Send the banner as the first thing + conn.send(self.encode_string(self._QMP_BANNER_DATA)) + + # Expect qmp_capabilities and return an empty response + conn.recv(4096) + conn.send(self.encode_string(self._EMPTY_RESPONSE)) + + while True: + # We ignore the expected message, as the purpose of this object is not + # to verify the correctness of the communication but to act as a + # partner for the SUT (System Under Test, that is QmpConnection) + msg = conn.recv(4096) + if not msg: + break + + if not self.script: + break + response = self.script.pop(0) + if isinstance(response, str): + conn.send(response) + elif isinstance(response, list): + for chunk in response: + conn.send(chunk) + else: + raise errors.ProgrammerError("Unknown response type for %s" % response) + + conn.close() + + def encode_string(self, message): + return (serializer.DumpJson(message, indent=False) + + hv_kvm.QmpConnection._MESSAGE_END_TOKEN) + + +class TestQmpMessage(testutils.GanetiTestCase): + def testSerialization(self): + test_data = {"execute": "command", "arguments": ["a", "b", "c"]} + message = hv_kvm.QmpMessage(test_data) + + for k, v in test_data.items(): + self.failUnless(message[k] == v) + + rebuilt_message = hv_kvm.QmpMessage.BuildFromJsonString(str(message)) + self.failUnless(rebuilt_message == message) + + +class TestQmp(testutils.GanetiTestCase): + def testQmp(self): + requests = [ + {"execute": "query-kvm", "arguments": []}, + {"execute": "eject", "arguments": {"device": "ide1-cd0"}}, + {"execute": "query-status", "arguments": []}, + {"execute": "query-name", "arguments": []}, + ] + + server_responses = [ + # One message, one send() + '{"return": {"enabled": true, "present": true}}\r\n', + + # Message sent using multiple send() + ['{"retur', 'n": {}}\r\n'], + + # Multiple messages sent using one send() + '{"return": [{"name": "quit"}, {"name": "eject"}]}\r\n' + '{"return": {"running": true, "singlestep": false}}\r\n', + ] + + expected_responses = [ + {"return": {"enabled": True, "present": True}}, + {"return": {}}, + {"return": [{"name": "quit"}, {"name": "eject"}]}, + {"return": {"running": True, "singlestep": False}}, + ] + + # Set up the stub + socket_file = tempfile.NamedTemporaryFile() + os.remove(socket_file.name) + qmp_stub = QmpStub(socket_file.name, server_responses) + qmp_stub.start() + + # Set up the QMP connection + qmp_connection = hv_kvm.QmpConnection(socket_file.name) + qmp_connection.connect() + + # Format the script + for request, expected_response in zip(requests, expected_responses): + response = qmp_connection.Execute(request) + self.failUnless(response == hv_kvm.QmpMessage(expected_response)) + + class TestConsole(unittest.TestCase): def _Test(self, instance, hvparams): cons = hv_kvm.KVMHypervisor.GetInstanceConsole(instance, hvparams, {}) @@ -45,6 +173,7 @@ class TestConsole(unittest.TestCase): hvparams = { constants.HV_SERIAL_CONSOLE: True, constants.HV_VNC_BIND_ADDRESS: None, + constants.HV_KVM_SPICE_BIND: None, } cons = self._Test(instance, hvparams) self.assertEqual(cons.kind, constants.CONS_SSH) @@ -59,6 +188,7 @@ class TestConsole(unittest.TestCase): hvparams = { constants.HV_SERIAL_CONSOLE: False, constants.HV_VNC_BIND_ADDRESS: "192.0.2.1", + constants.HV_KVM_SPICE_BIND: None, } cons = self._Test(instance, hvparams) self.assertEqual(cons.kind, constants.CONS_VNC) @@ -66,6 +196,20 @@ class TestConsole(unittest.TestCase): self.assertEqual(cons.port, constants.VNC_BASE_PORT + 10) self.assertEqual(cons.display, 10) + def testSpice(self): + instance = objects.Instance(name="kvm.example.com", + primary_node="node7235", + network_port=11000) + hvparams = { + constants.HV_SERIAL_CONSOLE: False, + constants.HV_VNC_BIND_ADDRESS: None, + constants.HV_KVM_SPICE_BIND: "192.0.2.1", + } + cons = self._Test(instance, hvparams) + self.assertEqual(cons.kind, constants.CONS_SPICE) + self.assertEqual(cons.host, "192.0.2.1") + self.assertEqual(cons.port, 11000) + def testNoConsole(self): instance = objects.Instance(name="kvm.example.com", primary_node="node24325", @@ -73,6 +217,7 @@ class TestConsole(unittest.TestCase): hvparams = { constants.HV_SERIAL_CONSOLE: False, constants.HV_VNC_BIND_ADDRESS: None, + constants.HV_KVM_SPICE_BIND: None, } cons = self._Test(instance, hvparams) self.assertEqual(cons.kind, constants.CONS_MESSAGE) diff --git a/test/ganeti.utils.io_unittest.py b/test/ganeti.utils.io_unittest.py index be1b4abb65c1460f8b62332616502c1bcf167e51..8b2cc8ba80cc6d867ec4fe22a688683b729cb8ef 100755 --- a/test/ganeti.utils.io_unittest.py +++ b/test/ganeti.utils.io_unittest.py @@ -586,6 +586,32 @@ class TestIsNormAbsPath(unittest.TestCase): self._pathTestHelper("/etc/", False) +class TestIsBelowDir(unittest.TestCase): + """Testing case for IsBelowDir""" + + def testSamePrefix(self): + self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/c")) + self.assertTrue(utils.IsBelowDir("/a/b/", "/a/b/e")) + + def testSamePrefixButDifferentDir(self): + self.assertFalse(utils.IsBelowDir("/a/b", "/a/bc/d")) + self.assertFalse(utils.IsBelowDir("/a/b/", "/a/bc/e")) + + def testSamePrefixButDirTraversal(self): + self.assertFalse(utils.IsBelowDir("/a/b", "/a/b/../c")) + self.assertFalse(utils.IsBelowDir("/a/b/", "/a/b/../d")) + + def testSamePrefixAndTraversal(self): + self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/c/../d")) + self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/c/./e")) + self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/../b/./e")) + + def testBothAbsPath(self): + self.assertRaises(ValueError, utils.IsBelowDir, "/a/b/c", "d") + self.assertRaises(ValueError, utils.IsBelowDir, "a/b/c", "/d") + self.assertRaises(ValueError, utils.IsBelowDir, "a/b/c", "d") + + class TestPathJoin(unittest.TestCase): """Testing case for PathJoin""" diff --git a/test/ganeti.utils.text_unittest.py b/test/ganeti.utils.text_unittest.py index 5181db4d36aa963d1f552d59650039d186d0a696..91039e173a765a1bbb6eddc09edda95b4d651d7c 100755 --- a/test/ganeti.utils.text_unittest.py +++ b/test/ganeti.utils.text_unittest.py @@ -350,7 +350,7 @@ class TestUnescapeAndSplit(unittest.TestCase): def setUp(self): # testing more that one separator for regexp safety - self._seps = [",", "+", "."] + self._seps = [",", "+", ".", ":"] def testSimple(self): a = ["a", "b", "c", "d"] @@ -375,6 +375,18 @@ class TestUnescapeAndSplit(unittest.TestCase): b = ["a", "b\\" + sep + "c", "d"] self.failUnlessEqual(utils.UnescapeAndSplit(sep.join(a), sep=sep), b) + def testEscapeAtEnd(self): + for sep in self._seps: + self.assertEqual(utils.UnescapeAndSplit("\\", sep=sep), ["\\"]) + + a = ["a", "b\\", "c"] + b = ["a", "b" + sep + "c\\"] + self.assertEqual(utils.UnescapeAndSplit("%s\\" % sep.join(a), sep=sep), b) + + a = ["\\" + sep, "\\" + sep, "c", "d\\.moo"] + b = [sep, sep, "c", "d.moo\\"] + self.assertEqual(utils.UnescapeAndSplit("%s\\" % sep.join(a), sep=sep), b) + class TestCommaJoin(unittest.TestCase): def test(self): diff --git a/tools/burnin b/tools/burnin index 8ab7a15f4a0a33f27c5b6e0e138c91ce0a7e4cc4..c5d1612ffa36df856f6fcc413759638626156c53 100755 --- a/tools/burnin +++ b/tools/burnin @@ -91,12 +91,12 @@ def Err(msg, exit_code=1): class SimpleOpener(urllib.FancyURLopener): """A simple url opener""" - # pylint: disable-msg=W0221 + # pylint: disable=W0221 def prompt_user_passwd(self, host, realm, clear_cache=0): """No-interaction version of prompt_user_passwd.""" # we follow parent class' API - # pylint: disable-msg=W0613 + # pylint: disable=W0613 return None, None def http_error_default(self, url, fp, errcode, errmsg, headers): @@ -232,7 +232,7 @@ def _DoCheckInstances(fn): def wrapper(self, *args, **kwargs): val = fn(self, *args, **kwargs) for instance in self.instances: - self._CheckInstanceAlive(instance) # pylint: disable-msg=W0212 + self._CheckInstanceAlive(instance) # pylint: disable=W0212 return val return wrapper @@ -312,7 +312,7 @@ class Burner(object): Log("Idempotent %s succeeded after %d retries", msg, MAX_RETRIES - retry_count) return val - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 if retry_count == 0: Log("Non-idempotent %s failed, aborting", msg) raise @@ -358,7 +358,7 @@ class Burner(object): cli.SetGenericOpcodeOpts(ops, self.opts) self.queued_ops.append((ops, name, post_process)) else: - val = self.ExecOp(self.queue_retry, *ops) # pylint: disable-msg=W0142 + val = self.ExecOp(self.queue_retry, *ops) # pylint: disable=W0142 if post_process is not None: post_process() return val @@ -400,10 +400,10 @@ class Burner(object): self.ClearFeedbackBuf() jex = cli.JobExecutor(cl=self.cl, feedback_fn=self.Feedback) for ops, name, _ in jobs: - jex.QueueJob(name, *ops) # pylint: disable-msg=W0142 + jex.QueueJob(name, *ops) # pylint: disable=W0142 try: results = jex.GetResults() - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 Log("Jobs failed: %s", err) raise BurninFailure() @@ -414,7 +414,7 @@ class Burner(object): if post_process: try: post_process() - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 Log("Post process call for job %s failed: %s", name, err) fail = True val.append(result) @@ -1066,7 +1066,7 @@ class Burner(object): if not self.opts.keep_instances: try: self.BurnRemove() - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 if has_err: # already detected errors, so errors in removal # are quite expected Log("Note: error detected during instance remove: %s", err) diff --git a/tools/cfgshell b/tools/cfgshell index 77afab9dcdd4e2167c9c9eecf5ec63c810557856..3e79561b109d986d6204891e822fcbccb2eacc9c 100755 --- a/tools/cfgshell +++ b/tools/cfgshell @@ -24,7 +24,7 @@ """ # functions in this module need to have a given name structure, so: -# pylint: disable-msg=C0103 +# pylint: disable=C0103 import optparse @@ -54,7 +54,7 @@ class ConfigShell(cmd.Cmd): """ # all do_/complete_* functions follow the same API - # pylint: disable-msg=W0613 + # pylint: disable=W0613 prompt = "(/) " def __init__(self, cfg_file=None): @@ -93,7 +93,7 @@ class ConfigShell(cmd.Cmd): dirs = [] entries = [] if isinstance(obj, objects.ConfigObject): - # pylint: disable-msg=W0212 + # pylint: disable=W0212 # yes, we're using a protected member for name in obj._all_slots(): child = getattr(obj, name, None) @@ -156,7 +156,7 @@ class ConfigShell(cmd.Cmd): arg = None try: self.cfg = config.ConfigWriter(cfg_file=arg, offline=True) - self.parents = [self.cfg._config_data] # pylint: disable-msg=W0212 + self.parents = [self.cfg._config_data] # pylint: disable=W0212 self.path = [] except errors.ConfigurationError, err: print "Error: %s" % str(err) @@ -288,7 +288,7 @@ class ConfigShell(cmd.Cmd): if self.cfg.VerifyConfig(): print "Config data does not validate, refusing to save." return False - self.cfg._WriteConfig() # pylint: disable-msg=W0212 + self.cfg._WriteConfig() # pylint: disable=W0212 def do_rm(self, line): """Removes an instance or a node. @@ -298,7 +298,7 @@ class ConfigShell(cmd.Cmd): """ pointer = self.parents[-1] - data = self.cfg._config_data # pylint: disable-msg=W0212 + data = self.cfg._config_data # pylint: disable=W0212 if pointer not in (data.instances, data.nodes): print "Can only delete instances and nodes" return False diff --git a/tools/cfgupgrade b/tools/cfgupgrade index 7197d24840b6ed1be71718e60d4119c2de648e93..b44ea6c1fbc5845bb762faf68bf5ad03b42e095d 100755 --- a/tools/cfgupgrade +++ b/tools/cfgupgrade @@ -93,7 +93,7 @@ def main(): """Main program. """ - global options, args # pylint: disable-msg=W0603 + global options, args # pylint: disable=W0603 # Option parsing parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]") diff --git a/tools/cfgupgrade12 b/tools/cfgupgrade12 index 7ff5daa6bbf6809f895b96aa2d1007189e3be215..83187c3d703ea32e5940a853e30ab14696fe0637 100755 --- a/tools/cfgupgrade12 +++ b/tools/cfgupgrade12 @@ -18,7 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. -# pylint: disable-msg=C0103,E1103 +# pylint: disable=C0103,E1103 # C0103: invalid name NoDefault # E1103: Instance of 'foor' has no 'bar' member (but some types could @@ -281,7 +281,7 @@ def main(): """Main program. """ - # pylint: disable-msg=W0603 + # pylint: disable=W0603 global options, args program = os.path.basename(sys.argv[0]) diff --git a/tools/check-cert-expired b/tools/check-cert-expired index f47ca984905d37766da6dee9c4dd2de39b000545..32580bd5017a822f87044dc7e931e11004aee13a 100755 --- a/tools/check-cert-expired +++ b/tools/check-cert-expired @@ -22,7 +22,7 @@ """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name check-cert-expired import os.path @@ -66,7 +66,7 @@ def main(): except (KeyboardInterrupt, SystemExit): raise - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 cli.ToStderr("Unable to check %s: %s", filename, err) sys.exit(constants.EXIT_FAILURE) diff --git a/tools/cluster-merge b/tools/cluster-merge index e2fc8485b302510ed094972d880e19284f623e36..40bc2596b68fbc97f1ef82c49e6aad96b17f9136 100755 --- a/tools/cluster-merge +++ b/tools/cluster-merge @@ -24,7 +24,7 @@ The clusters have to run the same version of Ganeti! """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name cluster-merge import logging @@ -374,7 +374,7 @@ class Merger(object): utils.WriteFile(data.config_path, data=result.stdout) # R0201: Method could be a function - def _KillMasterDaemon(self): # pylint: disable-msg=R0201 + def _KillMasterDaemon(self): # pylint: disable=R0201 """Kills the local master daemon. @raise errors.CommandError: If unable to kill @@ -574,7 +574,7 @@ class Merger(object): other_cluster.cluster_name) # R0201: Method could be a function - def _GetOsHypervisor(self, cluster, os_name, hyp): # pylint: disable-msg=R0201 + def _GetOsHypervisor(self, cluster, os_name, hyp): # pylint: disable=R0201 if os_name in cluster.os_hvp: return cluster.os_hvp[os_name].get(hyp, None) else: @@ -586,7 +586,7 @@ class Merger(object): ConfigWriter.AddNodeGroup takes care of making sure there are no conflicts. """ - # pylint: disable-msg=R0201 + # pylint: disable=R0201 logging.info("Node group conflict strategy: %s", self.groups) my_grps = my_config.GetAllNodeGroupsInfo().values() @@ -626,15 +626,15 @@ class Merger(object): for node_name in other_grp.members[:]: node = other_config.GetNodeInfo(node_name) # Access to a protected member of a client class - # pylint: disable-msg=W0212 + # pylint: disable=W0212 other_config._UnlockedRemoveNodeFromGroup(node) # Access to a protected member of a client class - # pylint: disable-msg=W0212 + # pylint: disable=W0212 my_grp_uuid = my_config._UnlockedLookupNodeGroup(other_grp.name) # Access to a protected member of a client class - # pylint: disable-msg=W0212 + # pylint: disable=W0212 my_config._UnlockedAddNodeToGroup(node, my_grp_uuid) node.group = my_grp_uuid # Remove from list of groups to add @@ -645,7 +645,7 @@ class Merger(object): my_config.AddNodeGroup(grp, _CLUSTERMERGE_ECID) # R0201: Method could be a function - def _StartMasterDaemon(self, no_vote=False): # pylint: disable-msg=R0201 + def _StartMasterDaemon(self, no_vote=False): # pylint: disable=R0201 """Starts the local master daemon. @param no_vote: Should the masterd started without voting? default: False @@ -683,7 +683,7 @@ class Merger(object): result.output)) # R0201: Method could be a function - def _StartupAllInstances(self): # pylint: disable-msg=R0201 + def _StartupAllInstances(self): # pylint: disable=R0201 """Starts up all instances (locally). @raise errors.CommandError: If unable to start clusters @@ -698,7 +698,7 @@ class Merger(object): # R0201: Method could be a function # TODO: make this overridable, for some verify errors - def _VerifyCluster(self): # pylint: disable-msg=R0201 + def _VerifyCluster(self): # pylint: disable=R0201 """Runs gnt-cluster verify to verify the health. @raise errors.ProgrammError: If cluster fails on verification diff --git a/tools/ganeti-listrunner b/tools/ganeti-listrunner index 2ab63cef23637fcf4addce17f18fa19fec143780..13ab024271934431ffecc7c8c2b175d84855be36 100755 --- a/tools/ganeti-listrunner +++ b/tools/ganeti-listrunner @@ -49,7 +49,7 @@ Security considerations: """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name ganeti-listrunner import errno @@ -231,7 +231,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-msg=W0142 + transport.connect(username=username, **kwargs) # pylint: disable=W0142 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 @@ -267,7 +267,7 @@ def UploadFiles(connection, executable, filelist, logfile): sftp = paramiko.SFTPClient.from_transport(connection) sftp.mkdir(remote_dir, mode=0700) for item in filelist: - remote_file = "%s/%s" % (remote_dir, item.split("/").pop()) + remote_file = "%s/%s" % (remote_dir, os.path.basename(item)) WriteLog("uploading %s to remote %s" % (item, remote_file), logfile) sftp.put(item, remote_file) if item == executable: @@ -287,7 +287,7 @@ def CleanupRemoteDir(connection, upload_dir, filelist, logfile): try: sftp = paramiko.SFTPClient.from_transport(connection) for item in filelist: - fullpath = "%s/%s" % (upload_dir, item.split("/").pop()) + fullpath = "%s/%s" % (upload_dir, os.path.basename(item)) WriteLog("removing remote %s" % fullpath, logfile) sftp.remove(fullpath) sftp.rmdir(upload_dir) @@ -337,7 +337,7 @@ def RunRemoteCommand(connection, command, logfile): select.select([], [], [], .1) WriteLog("SUCCESS: command output follows", logfile) - for line in output.split("\n"): + for line in output.splitlines(): WriteLog("output = %s" % line, logfile) WriteLog("command execution completed", logfile) session.close() @@ -346,7 +346,7 @@ def RunRemoteCommand(connection, command, logfile): def HostWorker(logdir, username, password, use_agent, hostname, - executable, command, filelist): + executable, exec_args, command, filelist): """Per-host worker. This function does not return - it's the main code of the childs, @@ -359,6 +359,7 @@ def HostWorker(logdir, username, password, use_agent, hostname, @param use_agent: whether we should instead use an agent @param hostname: the hostname to connect to @param executable: the executable to upload, if not None + @param exec_args: Additional arguments for executable @param command: the command to run @param filelist: auxiliary files to upload @@ -375,8 +376,8 @@ def HostWorker(logdir, username, password, use_agent, hostname, print " %s: uploading files" % hostname upload_dir = UploadFiles(connection, executable, filelist, logfile) - command = "cd %s && ./%s" % (upload_dir, - executable.split("/").pop()) + command = ("cd %s && ./%s %s" % + (upload_dir, os.path.basename(executable), exec_args)) print " %s: executing remote command" % hostname cmd_result = RunRemoteCommand(connection, command, logfile) if cmd_result is True: @@ -412,7 +413,7 @@ def HostWorker(logdir, username, password, use_agent, hostname, def LaunchWorker(child_pids, logdir, username, password, use_agent, hostname, - executable, command, filelist): + executable, exec_args, command, filelist): """Launch the per-host worker. Arguments are the same as for HostWorker, except for child_pids, @@ -426,7 +427,7 @@ def LaunchWorker(child_pids, logdir, username, password, use_agent, hostname, child_pids[pid] = hostname else: HostWorker(logdir, username, password, use_agent, hostname, - executable, command, filelist) + executable, exec_args, command, filelist) def ParseOptions(): @@ -453,7 +454,7 @@ def ParseOptions(): help="comma-separated list of hosts or single hostname",) parser.add_option("-a", dest="auxfiles", action="append", default=[], help="optional auxiliary file to upload" - " (can be given multiple times", + " (can be given multiple times)", metavar="FILE") parser.add_option("-c", dest="command", default=None, help="shell command to run on remote host(s)") @@ -467,6 +468,8 @@ def ParseOptions(): " using an agent)") parser.add_option("-A", dest="use_agent", default=False, action="store_true", help="instead of password, use keys from an SSH agent") + parser.add_option("--args", dest="exec_args", default=None, + help="Arguments to be passed to executable (-x)") opts, args = parser.parse_args() @@ -474,6 +477,8 @@ def ParseOptions(): parser.error("Options -x and -c conflict with each other") if not (opts.executable or opts.command): parser.error("One of -x and -c must be given") + if opts.command and opts.exec_args: + parser.error("Can't specify arguments when using custom command") if not opts.logdir: parser.error("Option -l is required") if opts.hostfile and opts.hostlist: @@ -484,14 +489,15 @@ def ParseOptions(): parser.error("This program doesn't take any arguments, passed in: %s" % ", ".join(args)) - return (opts.logdir, opts.executable, opts.hostfile, opts.hostlist, + return (opts.logdir, opts.executable, opts.exec_args, + opts.hostfile, opts.hostlist, opts.command, opts.use_agent, opts.auxfiles, opts.username, opts.password, opts.batch_size) def main(): """main.""" - (logdir, executable, hostfile, hostlist, + (logdir, executable, exec_args, hostfile, hostlist, command, use_agent, auxfiles, username, password, batch_size) = ParseOptions() @@ -536,7 +542,7 @@ def main(): child_pids = {} for hostname in batch: LaunchWorker(child_pids, logdir, username, password, use_agent, hostname, - executable, command, filelist) + executable, exec_args, command, filelist) while child_pids: pid, status = os.wait() @@ -548,7 +554,7 @@ def main(): failures += 1 if hosts: LaunchWorker(child_pids, logdir, username, password, use_agent, - hosts.pop(0), executable, command, filelist) + hosts.pop(0), executable, exec_args, command, filelist) print print "All done, %s successful and %s failed hosts" % (successes, failures) diff --git a/tools/lvmstrap b/tools/lvmstrap index e24b2a6daeacc5a268ec002aa0f278115e2806a4..eb87cd2325b919d3c0c8f7a02be0048803bb53b9 100755 --- a/tools/lvmstrap +++ b/tools/lvmstrap @@ -170,7 +170,7 @@ def ParseOptions(): OptionParser.parse_args """ - global verbose_flag # pylint: disable-msg=W0603 + global verbose_flag # pylint: disable=W0603 parser = optparse.OptionParser(usage="\n%s" % USAGE, version="%%prog (ganeti) %s" % diff --git a/tools/move-instance b/tools/move-instance index cdf99ec971eb85ea25b2baa204b528d8028e3e06..168ad811fb7789c05cca285bbc612eb9ad38f82d 100755 --- a/tools/move-instance +++ b/tools/move-instance @@ -22,7 +22,7 @@ """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name move-instance import os @@ -40,7 +40,7 @@ from ganeti import objects from ganeti import compat from ganeti import rapi -import ganeti.rapi.client # pylint: disable-msg=W0611 +import ganeti.rapi.client # pylint: disable=W0611 import ganeti.rapi.client_utils @@ -662,7 +662,7 @@ class MoveSourceExecutor(object): class MoveSourceWorker(workerpool.BaseWorker): - def RunTask(self, rapi_factory, move): # pylint: disable-msg=W0221 + def RunTask(self, rapi_factory, move): # pylint: disable=W0221 """Executes an instance move. @type rapi_factory: L{RapiClientFactory} @@ -696,7 +696,7 @@ class MoveSourceWorker(workerpool.BaseWorker): (mrt.src_error_message, mrt.dest_error_message)) else: move.error_message = None - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 logging.exception("Caught unhandled exception") move.error_message = str(err) diff --git a/tools/sanitize-config b/tools/sanitize-config index ca9c95539f3ba93b390bf04a53006cba43ac087f..f94e67084c48082b08b8dd5ba0a2ecd0092c6251 100755 --- a/tools/sanitize-config +++ b/tools/sanitize-config @@ -19,7 +19,7 @@ # 02110-1301, USA. -# pylint: disable-msg=C0103 +# pylint: disable=C0103 """Tool to sanitize/randomize the configuration file. @@ -79,7 +79,7 @@ def GenerateNameMap(opts, names, base): return name_map -def SanitizeSecrets(opts, cfg): # pylint: disable-msg=W0613 +def SanitizeSecrets(opts, cfg): # pylint: disable=W0613 """Cleanup configuration secrets. """ @@ -126,7 +126,7 @@ def SanitizeInstances(opts, cfg): RenameDictKeys(cfg["instances"], old_map, True) -def SanitizeIps(opts, cfg): # pylint: disable-msg=W0613 +def SanitizeIps(opts, cfg): # pylint: disable=W0613 """Sanitize the IP names. @note: we're interested in obscuring the old IPs, not in generating @@ -161,7 +161,7 @@ def SanitizeIps(opts, cfg): # pylint: disable-msg=W0613 nic["ip"] = _Get(nic["ip"]) -def SanitizeOsNames(opts, cfg): # pylint: disable-msg=W0613 +def SanitizeOsNames(opts, cfg): # pylint: disable=W0613 """Sanitize the OS names. """ @@ -182,7 +182,7 @@ def SanitizeOsNames(opts, cfg): # pylint: disable-msg=W0613 RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False) -def SanitizeDisks(opts, cfg): # pylint: disable-msg=W0613 +def SanitizeDisks(opts, cfg): # pylint: disable=W0613 """Cleanup disks disks. """ diff --git a/tools/setup-ssh b/tools/setup-ssh index 112caf61bec9ee8670a3a0a6e46085160c72cf6c..8c406ce93cf7b796175e1fe76d1c8f37b3c72cd2 100755 --- a/tools/setup-ssh +++ b/tools/setup-ssh @@ -24,7 +24,7 @@ This is needed before we can join the node into the cluster. """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name setup-ssh import getpass