diff --git a/Makefile.am b/Makefile.am index 7a2d35952a3cca12c287214c7e4dc5ab05ff3527..14d2345621e088dc0ee38bac845cbc81f64a2939 100644 --- a/Makefile.am +++ b/Makefile.am @@ -160,8 +160,8 @@ CLEANFILES = \ .hpc/*.mix htools/*.tix \ doc/hs-lint.html -# BUILT_SOURCES should only be used as a dependency on phony targets. Otherwise -# it'll cause the target to rebuild every time. +# BUILT_SOURCES should only be used as a dependency on phony +# targets. Otherwise it'll cause the target to rebuild every time. BUILT_SOURCES = \ $(built_base_sources) \ $(BUILT_PYTHON_SOURCES) \ @@ -999,13 +999,17 @@ man/footer.html: man/footer.rst $(PANDOC) -f rst -t html -o $@ $< man/%.gen: man/%.rst lib/query.py lib/build/sphinx_ext.py \ - lib/build/shell_example_lexer.py + lib/build/shell_example_lexer.py \ + | $(RUN_IN_TEMPDIR) $(BUILT_PYTHON_SOURCES) @echo "Checking $< for hardcoded paths..." @if grep -nEf autotools/wrong-hardcoded-paths $<; then \ echo "Man page $< has harcoded paths (see above)!" 1>&2 ; \ exit 1; \ fi - PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(DOCPP) < $< > $@ + set -e ; \ + trap 'echo auto-removing $@; rm $@' EXIT; \ + PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(DOCPP) < $< > $@ ;\ + trap - EXIT man/%.7.in man/%.8.in man/%.1.in: man/%.gen man/footer.man @test -n "$(PANDOC)" || \ @@ -1048,12 +1052,16 @@ vcs-version: echo "Cannot auto-generate $@ file"; exit 1; \ fi +.PHONY: clean-vcs-version +clean-vcs-version: + rm -f vcs-version + .PHONY: regen-vcs-version regen-vcs-version: set -e; \ cd $(srcdir); \ if test -d .git; then \ - rm -f vcs-version; \ + $(MAKE) clean-vcs-version; \ $(MAKE) vcs-version; \ fi @@ -1138,6 +1146,7 @@ lib/_autoconf.py: Makefile | lib/.dir echo "ENABLE_CONFD = $(ENABLE_CONFD)"; \ echo "PY_CONFD = $(PY_CONFD)"; \ echo "HS_CONFD = $(HS_CONFD)"; \ + echo "XEN_CMD = '$(XEN_CMD)'"; \ } > $@ lib/_vcsversion.py: Makefile vcs-version | lib/.dir @@ -1309,28 +1318,43 @@ PEP8_IGNORE = E111,E261,E501 # For excluding pep8 expects filenames only, not whole paths PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(BUILT_PYTHON_SOURCES)))) +LINT_TARGETS = pylint pylint-qa +if HAS_PEP8 +LINT_TARGETS += pep8 +endif +if HAS_HLINT +LINT_TARGETS += hlint +endif + .PHONY: lint -lint: $(BUILT_SOURCES) +lint: $(LINT_TARGETS) + +.PHONY: pylint +pylint: $(BUILT_SOURCES) @test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; } - if test -z "$(PEP8)"; then \ - echo '"pep8" not found during configure' >&2; \ - else \ - $(PEP8) --repeat --ignore='$(PEP8_IGNORE)' --exclude='$(PEP8_EXCLUDE)' \ - $(pep8_python_code); \ - fi $(PYLINT) $(LINT_OPTS) $(lint_python_code) + +.PHONY: pylint-qa +pylint-qa: $(BUILT_SOURCES) + @test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; } cd $(top_srcdir)/qa && \ PYTHONPATH=$(abs_top_srcdir) $(PYLINT) $(LINT_OPTS) \ --rcfile ../pylintrc $(patsubst qa/%.py,%,$(qa_scripts)) +.PHONY: pep8 +pep8: $(BUILT_SOURCES) + @test -n "$(PEP8)" || { echo 'pep8' not found during configure; exit 1; } + $(PEP8) --ignore='$(PEP8_IGNORE)' --exclude='$(PEP8_EXCLUDE)' \ + --repeat $(pep8_python_code) + .PHONY: hlint hlint: $(HS_BUILT_SRCS) htools/lint-hints.hs + @test -n "$(HLINT)" || { echo 'hlint' not found during configure; exit 1; } if tty -s; then C="-c"; else C=""; fi; \ - hlint --report=doc/hs-lint.html --cross $$C \ + $(HLINT) --report=doc/hs-lint.html --cross $$C \ --ignore "Use first" \ --ignore "Use comparing" \ --ignore "Use on" \ - --ignore "Use Control.Exception.catch" \ --ignore "Reduce duplication" \ --hint htools/lint-hints \ $(filter-out htools/Ganeti/THH.hs,$(HS_LIB_SRCS)) @@ -1338,8 +1362,8 @@ hlint: $(HS_BUILT_SRCS) htools/lint-hints.hs # a dist hook rule for updating the vcs-version file; this is # hardcoded due to where it needs to build the file... dist-hook: - $(MAKE) regen-vcs-version && \ - rm -f $(top_distdir)/vcs-version && \ + $(MAKE) regen-vcs-version + rm -f $(top_distdir)/vcs-version cp -p $(srcdir)/vcs-version $(top_distdir) # a distcheck hook rule for catching revision control directories @@ -1465,7 +1489,8 @@ py-coverage: $(BUILT_SOURCES) $(python_tests) .PHONY: hs-coverage hs-coverage: $(haskell_tests) htools/hpc-htools - rm -f *.tix && $(MAKE) hs-check + rm -f *.tix + $(MAKE) hs-check @mkdir_p@ $(COVERAGE_HS_DIR) hpc combine $(HPCEXCL) test.tix hpc-htools.tix > coverage-htools.tix hpc markup --destdir=$(COVERAGE_HS_DIR) coverage-htools.tix diff --git a/NEWS b/NEWS index 57b070466ba93f0d45f5448996bb2a7f7cd015b1..5925f4a93404ea0541e7cdf32aaa8b84d62bb823 100644 --- a/NEWS +++ b/NEWS @@ -2,10 +2,10 @@ News ==== -Version 2.6.0 beta1 +Version 2.6.0 beta2 ------------------- -*(Released Wed, 23 Mar 2011)* +*(Released Mon, 11 Jun 2012)* New features ~~~~~~~~~~~~ @@ -256,6 +256,10 @@ Python 2.7 is better supported, and after Ganeti 2.6 we will investigate whether to still support Python 2.4 or move to Python 2.6 as minimum required version. +Support for Fedora has been slightly improved; the provided example +init.d script should work better on it and the INSTALL file should +document the needed dependencies. + Internal changes ~~~~~~~~~~~~~~~~ @@ -291,6 +295,30 @@ verification. This will remove some rare but hard to diagnose errors in import-export. +Version 2.6.0 beta1 +------------------- + +*(Released Wed, 23 May 2012)* + +First beta release of 2.6. The following changes were done from beta1 to +beta2: + +- integrated patch for distributions without ``start-stop-daemon`` +- adapted example init.d script to work on Fedora +- fixed log handling in Haskell daemons +- adapted checks in the watcher for pycurl linked against libnss +- add partial support for ``xl`` instead of ``xm`` for Xen +- fixed a type issue in cluster verification +- fixed ssconf handling in the Haskell code (was breaking confd in IPv6 + clusters) + +Plus integrated fixes from the 2.5 branch: + +- fixed ``kvm-ifup`` to use ``/bin/bash`` +- fixed parallel build failures +- KVM live migration when using a custom keymap + + Version 2.5.1 ------------- diff --git a/configure.ac b/configure.ac index f93920437f1df04211ae2d59d148392a9522164a..7732351ab2b159e95f7ac4e66ca257b1f5298281 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ m4_define([gnt_version_major], [2]) m4_define([gnt_version_minor], [6]) m4_define([gnt_version_revision], [0]) -m4_define([gnt_version_suffix], [~beta1]) +m4_define([gnt_version_suffix], [~beta2]) m4_define([gnt_version_full], m4_format([%d.%d.%d%s], gnt_version_major, gnt_version_minor, @@ -98,6 +98,19 @@ AC_ARG_WITH([xen-initrd], [xen_initrd="/boot/initrd-3-xenU"]) AC_SUBST(XEN_INITRD, $xen_initrd) +# --with-xen-cmd=... +AC_ARG_WITH([xen-cmd], + [AS_HELP_STRING([--with-xen-cmd=CMD], + [Sets the xen cli interface command (default is xm)] + )], + [xen_cmd="$withval"], + [xen_cmd="xm"]) +AC_SUBST(XEN_CMD, $xen_cmd) + +if ! test "$XEN_CMD" = xl -o "$XEN_CMD" = xm; then + AC_MSG_ERROR([Unsupported xen command specified]) +fi + # --with-kvm-kernel=... AC_ARG_WITH([kvm-kernel], [AS_HELP_STRING([--with-kvm-kernel=PATH], @@ -374,6 +387,7 @@ if test -z "$PEP8" then AC_MSG_WARN([pep8 not found, checking code will not be complete]) fi +AM_CONDITIONAL([HAS_PEP8], [test "$PEP8"]) # Check for socat AC_ARG_VAR(SOCAT, [socat path]) @@ -505,6 +519,14 @@ if test "$HADDOCK" && test "$HSCOLOUR"; then fi AC_SUBST(HTOOLS_APIDOC) +# Check for hlint +HLINT=no +AC_ARG_VAR(HLINT, [hlint path]) +AC_PATH_PROG(HLINT, [hlint], []) +if test -z "$HLINT"; then + AC_MSG_WARN([hlint not found, checking code will not be possible]) +fi + fi # end if enable_htools, define automake conditions if test "$HTOOLS" != "yes" && test "$HS_CONFD" = "True"; then @@ -515,6 +537,7 @@ fi AM_CONDITIONAL([WANT_HTOOLS], [test x$HTOOLS = xyes]) AM_CONDITIONAL([WANT_HTOOLSTESTS], [test "x$GHC_PKG_QUICKCHECK" != x]) AM_CONDITIONAL([WANT_HTOOLSAPIDOC], [test x$HTOOLS_APIDOC = xyes]) +AM_CONDITIONAL([HAS_HLINT], [test "$HLINT"]) # Check for fakeroot AC_ARG_VAR(FAKEROOT_PATH, [fakeroot path]) diff --git a/doc/design-shared-storage.rst b/doc/design-shared-storage.rst index 9f3a0ea1c317726e64d79a7706e7e7370d5bdc17..c1754764cda81d8c8c79bc8394b65d18d42759bc 100644 --- a/doc/design-shared-storage.rst +++ b/doc/design-shared-storage.rst @@ -47,7 +47,7 @@ Use cases We consider the following use cases: - A virtualization cluster with FibreChannel shared storage, mapping at - leaste one LUN per instance, accessible by the whole cluster. + least one LUN per instance, accessible by the whole cluster. - A virtualization cluster with instance images stored as files on an NFS server. - A virtualization cluster storing instance images on a Ceph volume. diff --git a/doc/hooks.rst b/doc/hooks.rst index 50e290d64a3c912fb715ff75968452077d8f4440..ca384d685cf45837d5056a07a1912001e6ef1cf1 100644 --- a/doc/hooks.rst +++ b/doc/hooks.rst @@ -8,10 +8,11 @@ Documents Ganeti version 2.6 Introduction ------------ - -In order to allow customisation of operations, ganeti runs scripts -under ``/etc/ganeti/hooks`` based on certain rules. - +In order to allow customisation of operations, Ganeti runs scripts in +sub-directories of ``@SYSCONFDIR@/ganeti/hooks``. These sub-directories +are named ``$hook-$phase.d``, where ``$phase`` is either ``pre`` or +``post`` and ``$hook`` matches the directory name given for a hook (e.g. +``cluster-verify-post.d`` or ``node-add-pre.d``). This is similar to the ``/etc/network/`` structure present in Debian for network interface handling. diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs index 0c1ff5adbaccf4be6d56e4eea5e07685d6352511..78b88a638c2c671c8ded6f8fae48f2ada36e274c 100644 --- a/htools/Ganeti/HTools/QC.hs +++ b/htools/Ganeti/HTools/QC.hs @@ -41,6 +41,7 @@ module Ganeti.HTools.QC , testCLI , testJSON , testLUXI + , testSsconf ) where import Test.QuickCheck @@ -58,6 +59,7 @@ import qualified Data.IntMap as IntMap import qualified Ganeti.OpCodes as OpCodes import qualified Ganeti.Jobs as Jobs import qualified Ganeti.Luxi as Luxi +import qualified Ganeti.Ssconf as Ssconf import qualified Ganeti.HTools.CLI as CLI import qualified Ganeti.HTools.Cluster as Cluster import qualified Ganeti.HTools.Container as Container @@ -1731,3 +1733,16 @@ prop_Luxi_CallEncoding op = testSuite "LUXI" [ 'prop_Luxi_CallEncoding ] + +-- * Ssconf tests + +instance Arbitrary Ssconf.SSKey where + arbitrary = elements [minBound..maxBound] + +prop_Ssconf_filename key = + printTestCase "Key doesn't start with correct prefix" $ + Ssconf.sSFilePrefix `isPrefixOf` Ssconf.keyToFilename (Just "") key + +testSuite "Ssconf" + [ 'prop_Ssconf_filename + ] diff --git a/htools/Ganeti/Ssconf.hs b/htools/Ganeti/Ssconf.hs index 5910cd1a3d00c3331af70c19845e722fdcca0e29..39a3d95dfaac6badae2dfdb5148ee17bb731fc3c 100644 --- a/htools/Ganeti/Ssconf.hs +++ b/htools/Ganeti/Ssconf.hs @@ -30,6 +30,8 @@ module Ganeti.Ssconf , sSKeyToRaw , sSKeyFromRaw , getPrimaryIPFamily + , keyToFilename + , sSFilePrefix ) where import Ganeti.THH @@ -51,6 +53,10 @@ import Ganeti.HTools.Utils maxFileSize :: Int maxFileSize = 131072 +-- | ssconf file prefix, re-exported from Constants. +sSFilePrefix :: FilePath +sSFilePrefix = C.ssconfFileprefix + $(declareSADT "SSKey" [ ("SSClusterName", 'C.ssClusterName) , ("SSClusterTags", 'C.ssClusterTags) @@ -80,7 +86,8 @@ $(declareSADT "SSKey" keyToFilename :: Maybe FilePath -- ^ Optional config path override -> SSKey -- ^ ssconf key -> FilePath -keyToFilename optpath key = fromMaybe C.dataDir optpath </> sSKeyToRaw key +keyToFilename optpath key = fromMaybe C.dataDir optpath </> + sSFilePrefix ++ sSKeyToRaw key -- | Runs an IO action while transforming any error into 'Bad' -- values. It also accepts an optional value to use in case the error diff --git a/htools/test.hs b/htools/test.hs index eedefd35ba6a021be43a6bfc62b9db9608241d86..6e434274bc11ab5b9af918395897fbba307400b4 100644 --- a/htools/test.hs +++ b/htools/test.hs @@ -125,6 +125,7 @@ allTests = , (fast, testCLI) , (fast, testJSON) , (fast, testLUXI) + , (fast, testSsconf) , (slow, testCluster) ] diff --git a/lib/cmdlib.py b/lib/cmdlib.py index b9cd7ad3dcacb7063cc712e4e39c81f8b775d117..34902d1270e914c3c08b4d2749e8458c0092dc3c 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -3152,6 +3152,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): for instance in self.my_inst_names: inst_config = self.my_inst_info[instance] + if inst_config.admin_state == constants.ADMINST_OFFLINE: + i_offline += 1 for nname in inst_config.all_nodes: if nname not in node_image: @@ -3291,12 +3293,6 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): non_primary_inst = set(nimg.instances).difference(nimg.pinst) for inst in non_primary_inst: - # FIXME: investigate best way to handle offline insts - if inst.admin_state == constants.ADMINST_OFFLINE: - if verbose: - feedback_fn("* Skipping offline instance %s" % inst.name) - i_offline += 1 - continue test = inst in self.all_inst_info _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst, "instance should not run on node %s", node_i.name) @@ -5929,7 +5925,8 @@ class LUNodeSetParams(LogicalUnit): if mc_remaining < mc_should: raise errors.OpPrereqError("Not enough master candidates, please" " pass auto promote option to allow" - " promotion", errors.ECODE_STATE) + " promotion (--auto-promote or RAPI" + " auto_promote=True)", errors.ECODE_STATE) self.old_flags = old_flags = (node.master_candidate, node.drained, node.offline) diff --git a/lib/constants.py b/lib/constants.py index 5cd8be503bbc95a46239d15829f52581abfbacf1..b76863e9bf4030420282adcad027c60d7dded1e8 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -267,7 +267,10 @@ EXPORT_CONF_FILE = "config.ini" XEN_BOOTLOADER = _autoconf.XEN_BOOTLOADER XEN_KERNEL = _autoconf.XEN_KERNEL XEN_INITRD = _autoconf.XEN_INITRD -XEN_CMD = "xm" +XEN_CMD_XM = "xm" +XEN_CMD_XL = "xl" +# FIXME: This will be made configurable using hvparams in Ganeti 2.7 +XEN_CMD = _autoconf.XEN_CMD KVM_PATH = _autoconf.KVM_PATH KVM_KERNEL = _autoconf.KVM_KERNEL @@ -1695,6 +1698,8 @@ RSS_DESCRIPTION = { MAX_NICS = 8 MAX_DISKS = 16 +# SSCONF file prefix +SSCONF_FILEPREFIX = "ssconf_" # SSCONF keys SS_CLUSTER_NAME = "cluster_name" SS_CLUSTER_TAGS = "cluster_tags" diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index 8327f98be7783cf33c801787ffffb162fdbb779d..2d6e23b7bbee04d0dd47a466908a59fe027ca99a 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2008, 2009, 2010, 2011 Google Inc. +# Copyright (C) 2008, 2009, 2010, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -420,7 +420,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): # a separate directory, called 'chroot-quarantine'. _CHROOT_QUARANTINE_DIR = _ROOT_DIR + "/chroot-quarantine" _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR, _NICS_DIR, - _CHROOT_DIR, _CHROOT_QUARANTINE_DIR] + _CHROOT_DIR, _CHROOT_QUARANTINE_DIR, _KEYMAP_DIR] PARAMETERS = { constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK, @@ -944,6 +944,13 @@ class KVMHypervisor(hv_base.BaseHypervisor): def _GenerateKVMRuntime(self, instance, block_devices, startup_paused): """Generate KVM information to start an instance. + @attention: this function must not have any side-effects; for + example, it must not write to the filesystem, or read values + from the current system the are expected to differ between + nodes, since it is only run once at instance startup; + actions/kvm arguments that can vary between systems should be + done in L{_ExecuteKVMRuntime} + """ # pylint: disable=R0914,R0915 _, v_major, v_min, _ = self._GetKVMVersion() @@ -1099,16 +1106,6 @@ class KVMHypervisor(hv_base.BaseHypervisor): elif vnc_bind_address: kvm_cmd.extend(["-usbdevice", constants.HT_MOUSE_TABLET]) - keymap = hvp[constants.HV_KEYMAP] - if keymap: - keymap_path = self._InstanceKeymapFile(instance.name) - # If a keymap file is specified, KVM won't use its internal defaults. By - # first including the "en-us" layout, an error on loading the actual - # layout (e.g. because it can't be found) won't lead to a non-functional - # keyboard. A keyboard with incorrect keys is still better than none. - utils.WriteFile(keymap_path, data="include en-us\ninclude %s\n" % keymap) - kvm_cmd.extend(["-k", keymap_path]) - if vnc_bind_address: if netutils.IP4Address.IsValid(vnc_bind_address): if instance.network_port > constants.VNC_BASE_PORT: @@ -1144,6 +1141,8 @@ class KVMHypervisor(hv_base.BaseHypervisor): kvm_cmd.extend(["-vnc", vnc_arg]) elif spice_bind: + # FIXME: this is wrong here; the iface ip address differs + # between systems, so it should be done in _ExecuteKVMRuntime if netutils.IsValidInterface(spice_bind): # The user specified a network interface, we have to figure out the IP # address. @@ -1314,7 +1313,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): raise errors.HypervisorError("Failed to start instance %s" % name) def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None): - """Execute a KVM cmd, after completing it with some last minute data + """Execute a KVM cmd, after completing it with some last minute data. @type incoming: tuple of strings @param incoming: (target_host_ip, port) @@ -1345,6 +1344,16 @@ class KVMHypervisor(hv_base.BaseHypervisor): if security_model == constants.HT_SM_USER: kvm_cmd.extend(["-runas", conf_hvp[constants.HV_SECURITY_DOMAIN]]) + keymap = conf_hvp[constants.HV_KEYMAP] + if keymap: + keymap_path = self._InstanceKeymapFile(name) + # If a keymap file is specified, KVM won't use its internal defaults. By + # first including the "en-us" layout, an error on loading the actual + # layout (e.g. because it can't be found) won't lead to a non-functional + # keyboard. A keyboard with incorrect keys is still better than none. + utils.WriteFile(keymap_path, data="include en-us\ninclude %s\n" % keymap) + kvm_cmd.extend(["-k", keymap_path]) + # We have reasons to believe changing something like the nic driver/type # upon migration won't exactly fly with the instance kernel, so for nic # related parameters we'll use up_hvp @@ -1538,7 +1547,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): @type text: string @param text: output of kvm --help @return: (version, v_maj, v_min, v_rev) - @raise L{errors.HypervisorError}: when the KVM version cannot be retrieved + @raise errors.HypervisorError: when the KVM version cannot be retrieved """ match = cls._VERSION_RE.search(text.splitlines()[0]) @@ -1559,7 +1568,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): """Return the installed KVM version. @return: (version, v_maj, v_min, v_rev) - @raise L{errors.HypervisorError}: when the KVM version cannot be retrieved + @raise errors.HypervisorError: when the KVM version cannot be retrieved """ result = utils.RunCmd([constants.KVM_PATH, "--help"]) diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py index 50abe1a0f41ae639916aeac0eadf064447485bac..a757617e15902ae986764a29bbf9c48cd39b8291 100644 --- a/lib/hypervisor/hv_xen.py +++ b/lib/hypervisor/hv_xen.py @@ -537,13 +537,19 @@ class XenHypervisor(hv_base.BaseHypervisor): " %s, cannot migrate" % (target, port)) # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1). - # -l doesn't exist anymore - # -p doesn't exist anymore - # -C config_file must be passed + # This should be reworked in Ganeti 2.7 # ssh must recognize the key of the target host for the migration - args = [constants.XEN_CMD, "migrate", "-p", "%d" % port] - if live: - args.append("-l") + args = [constants.XEN_CMD, "migrate"] + if constants.XEN_CMD == constants.XEN_CMD_XM: + args.extend(["-p", "%d" % port]) + if live: + args.append("-l") + elif constants.XEN_CMD == constants.XEN_CMD_XL: + args.extend(["-C", self._ConfigFileName(instance.name)]) + else: + raise errors.HypervisorError("Unsupported xen command: %s" % + constants.XEN_CMD) + args.extend([instance.name, target]) result = utils.RunCmd(args) if result.failed: diff --git a/lib/rapi/client.py b/lib/rapi/client.py index 15e61f4a896ae57eeb52620515187ae983d97a56..b5a4df53013912f5e5feca36fbb2920d26e2eb0c 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -256,6 +256,9 @@ def GenericCurlConfig(verbose=False, use_signal=False, lcsslver = sslver.lower() if lcsslver.startswith("openssl/"): pass + elif lcsslver.startswith("nss/"): + # TODO: investigate compatibility beyond a simple test + pass elif lcsslver.startswith("gnutls/"): if capath: raise Error("cURL linked against GnuTLS has no support for a" diff --git a/lib/ssconf.py b/lib/ssconf.py index babd148e675650a482a1b8649505b6cccf3e6873..2399d81065325a25a6524a45a082f075881f73e1 100644 --- a/lib/ssconf.py +++ b/lib/ssconf.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008, 2010 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -273,7 +273,6 @@ class SimpleStore(object): - keys are restricted to predefined values """ - _SS_FILEPREFIX = "ssconf_" _VALID_KEYS = ( constants.SS_CLUSTER_NAME, constants.SS_CLUSTER_TAGS, @@ -314,7 +313,7 @@ class SimpleStore(object): raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'" % str(key)) - filename = self._cfg_dir + "/" + self._SS_FILEPREFIX + key + filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key return filename def _ReadFile(self, key, default=None): diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index 3440122aacd4e0e3f3bf0d66ab0ca2b1ce6b32bb..e7ad3b81f7813d2a94d35f882d3c869632b4aadf 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -464,7 +464,8 @@ def RunQa(): for use_client in [True, False]: rapi_instance = RunTest(qa_rapi.TestRapiInstanceAdd, pnode, use_client) - RunCommonInstanceTests(rapi_instance) + if qa_config.TestEnabled("instance-plain-rapi-common-tests"): + RunCommonInstanceTests(rapi_instance) RunTest(qa_rapi.TestRapiInstanceRemove, rapi_instance, use_client) del rapi_instance @@ -553,7 +554,12 @@ def main(): qa_config.Load(config_file) - qa_utils.StartMultiplexer(qa_config.GetMasterNode()["primary"]) + primary = qa_config.GetMasterNode()["primary"] + qa_utils.StartMultiplexer(primary) + print ("SSH command for primary node: %s" % + utils.ShellQuoteArgs(qa_utils.GetSSHCommand(primary, ""))) + print ("SSH command for other nodes: %s" % + utils.ShellQuoteArgs(qa_utils.GetSSHCommand("NODE", ""))) try: RunQa() finally: diff --git a/qa/qa-sample.json b/qa/qa-sample.json index d2c5f1a9a33bec100404f210b0498d88db085316..0769d0848e2a0e47470927f3f2c2db37378d9c5c 100644 --- a/qa/qa-sample.json +++ b/qa/qa-sample.json @@ -27,6 +27,9 @@ "# Script to check instance status": null, "instance-check": null, + "# Regular expression to ignore existing tags": null, + "ignore-tags-re": null, + "nodes": [ { "# Master node": null, @@ -42,10 +45,16 @@ "instances": [ { - "name": "xen-test-inst1" + "name": "xen-test-inst1", + + "# Static MAC address": null, + "#nic.mac/0": "AA:00:00:11:11:11" }, { - "name": "xen-test-inst2" + "name": "xen-test-inst2", + + "# Static MAC address": null, + "#nic.mac/0": "AA:00:00:22:22:22" } ], @@ -106,6 +115,7 @@ "instance-add-plain-disk": true, "instance-add-drbd-disk": true, "instance-convert-disk": true, + "instance-plain-rapi-common-tests": true, "instance-export": true, "instance-failover": true, diff --git a/qa/qa_config.py b/qa/qa_config.py index 48a8a11fa4ccf6128ace5d88a5b141e28b50d501..2cf8d500a6e51c672af8949d1a321a8bf0e844b6 100644 --- a/qa/qa_config.py +++ b/qa/qa_config.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2007, 2011 Google Inc. +# Copyright (C) 2007, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -154,6 +154,13 @@ def GetInstanceCheckScript(): return cfg.get(_INSTANCE_CHECK_KEY, None) +def GetInstanceNicMac(inst, default=None): + """Returns MAC address for instance's network interface. + + """ + return inst.get("nic.mac/0", default) + + def GetMasterNode(): return cfg["nodes"][0] diff --git a/qa/qa_instance.py b/qa/qa_instance.py index e6fd4f4cb99da85657eea538640724e22c370624..65af80e1c45eb9c6dd0ce3b46c6327914b78a594 100644 --- a/qa/qa_instance.py +++ b/qa/qa_instance.py @@ -42,7 +42,7 @@ def _GetDiskStatePath(disk): return "/sys/block/%s/device/state" % disk -def _GetGenericAddParameters(): +def _GetGenericAddParameters(inst, force_mac=None): params = ["-B"] params.append("%s=%s,%s=%s" % (constants.BE_MINMEM, qa_config.get(constants.BE_MINMEM), @@ -50,6 +50,15 @@ def _GetGenericAddParameters(): qa_config.get(constants.BE_MAXMEM))) for idx, size in enumerate(qa_config.get("disk")): params.extend(["--disk", "%s:size=%s" % (idx, size)]) + + # Set static MAC address if configured + if force_mac: + nic0_mac = force_mac + else: + nic0_mac = qa_config.GetInstanceNicMac(inst) + if nic0_mac: + params.extend(["--net", "0:mac=%s" % nic0_mac]) + return params @@ -60,7 +69,7 @@ def _DiskTest(node, disk_template): "--os-type=%s" % qa_config.get("os"), "--disk-template=%s" % disk_template, "--node=%s" % node] + - _GetGenericAddParameters()) + _GetGenericAddParameters(instance)) cmd.append(instance["name"]) AssertCommand(cmd) @@ -412,11 +421,10 @@ def TestInstanceImport(newinst, node, expnode, name): cmd = (["gnt-backup", "import", "--disk-template=plain", "--no-ip-check", - "--net", "0:mac=generate", "--src-node=%s" % expnode["primary"], "--src-dir=%s/%s" % (constants.EXPORT_DIR, name), "--node=%s" % node["primary"]] + - _GetGenericAddParameters()) + _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE)) cmd.append(newinst["name"]) AssertCommand(cmd) diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py index e265764595c9f421f484835721c3c5a07d344b64..2a3ea1ec16aac1dc90c10044f26496b9ecd79d8b 100644 --- a/qa/qa_rapi.py +++ b/qa/qa_rapi.py @@ -25,6 +25,8 @@ import tempfile import random +import re +import itertools from ganeti import utils from ganeti import constants @@ -416,6 +418,18 @@ def TestNode(node): ]) +def _FilterTags(seq): + """Removes unwanted tags from a sequence. + + """ + ignore_re = qa_config.get("ignore-tags-re", None) + + if ignore_re: + return itertools.ifilterfalse(re.compile(ignore_re).match, seq) + else: + return seq + + def TestTags(kind, name, tags): """Tests .../tags resources. @@ -432,7 +446,7 @@ def TestTags(kind, name, tags): raise errors.ProgrammerError("Unknown tag kind") def _VerifyTags(data): - AssertEqual(sorted(tags), sorted(data)) + AssertEqual(sorted(tags), sorted(_FilterTags(data))) queryargs = "&".join("tag=%s" % i for i in tags) @@ -539,7 +553,11 @@ def TestRapiInstanceAdd(node, use_client): try: disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")] disks = [{"size": size} for size in disk_sizes] - nics = [{}] + nic0_mac = qa_config.GetInstanceNicMac(instance, + default=constants.VALUE_GENERATE) + nics = [{ + constants.INIC_MAC: nic0_mac, + }] beparams = { constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)), diff --git a/qa/qa_utils.py b/qa/qa_utils.py index 7330cbc867e9266ac04b9b0f5169ea9144a58e59..a91cc94b4b366fb66456b5087272e46d6699b3ba 100644 --- a/qa/qa_utils.py +++ b/qa/qa_utils.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2007, 2011 Google Inc. +# Copyright (C) 2007, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -191,7 +191,7 @@ def AssertCommand(cmd, fail=False, node=None): return rcode -def GetSSHCommand(node, cmd, strict=True, opts=None, tty=True): +def GetSSHCommand(node, cmd, strict=True, opts=None, tty=None): """Builds SSH command to be executed. @type node: string @@ -203,11 +203,14 @@ def GetSSHCommand(node, cmd, strict=True, opts=None, tty=True): @param strict: whether to enable strict host key checking @type opts: list @param opts: list of additional options - @type tty: Bool - @param tty: If we should use tty + @type tty: boolean or None + @param tty: if we should use tty; if None, will be auto-detected """ - args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-l", "root"] + args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-lroot"] + + if tty is None: + tty = sys.stdout.isatty() if tty: args.append("-t") @@ -232,11 +235,15 @@ def GetSSHCommand(node, cmd, strict=True, opts=None, tty=True): return args -def StartLocalCommand(cmd, **kwargs): +def StartLocalCommand(cmd, _nolog_opts=False, **kwargs): """Starts a local command. """ - print "Command: %s" % utils.ShellQuoteArgs(cmd) + if _nolog_opts: + pcmd = [i for i in cmd if not i.startswith("-")] + else: + pcmd = cmd + print "Command: %s" % utils.ShellQuoteArgs(pcmd) return subprocess.Popen(cmd, shell=False, **kwargs) @@ -244,7 +251,8 @@ def StartSSH(node, cmd, strict=True): """Starts SSH. """ - return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict)) + return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict), + _nolog_opts=True) def StartMultiplexer(node): @@ -275,7 +283,7 @@ def CloseMultiplexers(): utils.RemoveFile(sname) -def GetCommandOutput(node, cmd, tty=True): +def GetCommandOutput(node, cmd, tty=None): """Returns the output of a command executed on the given node. """ diff --git a/tools/lvmstrap b/tools/lvmstrap index 6512477c9c942ae7619f4ab06579b5a072480f49..205eaeb889d0ede07b389c418df2795f9b45004c 100755 --- a/tools/lvmstrap +++ b/tools/lvmstrap @@ -1,7 +1,7 @@ #!/usr/bin/python # -# Copyright (C) 2006, 2007, 2011 Google Inc. +# Copyright (C) 2006, 2007, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -336,7 +336,7 @@ def CheckSysDev(name, devnum): @param name: the device name, e.g. 'sda' @param devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3 - @raises L{SysconfigError}: in case of failure of the check + @raises SysconfigError: in case of failure of the check """ path = "/dev/%s" % name