diff --git a/daemons/daemon-util.in b/daemons/daemon-util.in index 5bf2c3fa0085286d7473e3a66469d8a499707117..b754e7f603a00769a2c7f6c833f2146c44fed6d8 100644 --- a/daemons/daemon-util.in +++ b/daemons/daemon-util.in @@ -203,6 +203,9 @@ start() { fi local name="$1"; shift + # Convert daemon name to uppercase after removing "ganeti-" prefix + local plain_name=${name#ganeti-} + local ucname=$(tr a-z A-Z <<<$plain_name) local pidfile=$(_daemon_pidfile $name) local usergroup=$(_daemon_usergroup $plain_name) local daemonexec=$(_daemon_executable $name) @@ -213,10 +216,6 @@ start() { return 1 fi - # Convert daemon name to uppercase after removing "ganeti-" prefix - local plain_name=${name#ganeti-} - local ucname=$(tr a-z A-Z <<<$plain_name) - # Read $<daemon>_ARGS and $EXTRA_<daemon>_ARGS eval local args="\"\$${ucname}_ARGS \$EXTRA_${ucname}_ARGS\"" diff --git a/lib/bdev.py b/lib/bdev.py index 310ddf877df3328ac281ebbfa1f7af6fed1db071..6c16da4250f590ef9e1e7fd1bd93d45a2414c2f9 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -1058,7 +1058,7 @@ class BaseDRBD(BlockDev): # pylint: disable=W0223 def _CheckMetaSize(meta_device): """Check if the given meta device looks like a valid one. - This currently only check the size, which must be around + This currently only checks the size, which must be around 128MiB. """ @@ -1197,7 +1197,7 @@ class DRBD8(BaseDRBD): def _GetShowParser(cls): """Return a parser for `drbd show` output. - This will either create or return an already-create parser for the + This will either create or return an already-created parser for the output of the command `drbd show`. """ diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 34902d1270e914c3c08b4d2749e8458c0092dc3c..ba6661c3830310ff9929dc25831775a95d724f0e 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -10138,6 +10138,11 @@ class LUInstanceCreate(LogicalUnit): _ReleaseLocks(self, locking.LEVEL_NODE_RES) if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks: + # we need to set the disks ID to the primary node, since the + # preceding code might or might have not done it, depending on + # disk template and other options + for disk in iobj.disks: + self.cfg.SetDiskID(disk, pnode_name) if self.op.mode == constants.INSTANCE_CREATE: if not self.op.no_install: pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index 2d6e23b7bbee04d0dd47a466908a59fe027ca99a..087939e95574182d83ff0ff067ea60fa9374b6f6 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -936,6 +936,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): try: info = self.GetInstanceInfo(name) except errors.HypervisorError: + # Ignore exceptions due to instances being shut down continue if info: data.append(info) diff --git a/lib/qlang.py b/lib/qlang.py index 839a615baaf9fd0d1605a571410039161257750a..2923194f93d43dac948639390d13e569c5657821 100644 --- a/lib/qlang.py +++ b/lib/qlang.py @@ -58,12 +58,16 @@ OP_TRUE = "?" # operator-specific value OP_EQUAL = "=" OP_NOT_EQUAL = "!=" +OP_LT = "<" +OP_LE = "<=" +OP_GT = ">" +OP_GE = ">=" OP_REGEXP = "=~" OP_CONTAINS = "=[]" #: Characters used for detecting user-written filters (see L{_CheckFilter}) -FILTER_DETECTION_CHARS = frozenset("()=/!~'\"\\" + string.whitespace) +FILTER_DETECTION_CHARS = frozenset("()=/!~'\"\\<>" + string.whitespace) #: Characters used to detect globbing filters (see L{_CheckGlobbing}) GLOB_DETECTION_CHARS = frozenset("*?") @@ -165,6 +169,10 @@ def BuildFilterParser(): binopstbl = { "==": OP_EQUAL, "!=": OP_NOT_EQUAL, + "<": OP_LT, + "<=": OP_LE, + ">": OP_GT, + ">=": OP_GE, } binary_cond = (field_name + pyp.oneOf(binopstbl.keys()) + rval) diff --git a/lib/query.py b/lib/query.py index ffd4d204ce0b5b230d7828bce3bd8c3a28054e23..a8f19f0a5629cb429c0b5b4705c8fad8037af50c 100644 --- a/lib/query.py +++ b/lib/query.py @@ -403,6 +403,18 @@ class _FilterCompilerHelper: qlang.OP_NOT_EQUAL: (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn) for (flags, fn, valprepfn) in _EQUALITY_CHECKS]), + qlang.OP_LT: (_OPTYPE_BINARY, [ + (None, operator.lt, None), + ]), + qlang.OP_GT: (_OPTYPE_BINARY, [ + (None, operator.gt, None), + ]), + qlang.OP_LE: (_OPTYPE_BINARY, [ + (None, operator.le, None), + ]), + qlang.OP_GE: (_OPTYPE_BINARY, [ + (None, operator.ge, None), + ]), qlang.OP_REGEXP: (_OPTYPE_BINARY, [ (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex), ]), diff --git a/man/ganeti.rst b/man/ganeti.rst index 2d5c82f729f8fd49e6a76c493b3d9fe43f370d7e..d31adfca1bad7494b4354567e4225136e13a6a6c 100644 --- a/man/ganeti.rst +++ b/man/ganeti.rst @@ -362,7 +362,7 @@ Syntax in pseudo-BNF:: <condition> ::= { /* Value comparison */ - <field> { == | != } <value> + <field> { == | != | < | <= | >= | > } <value> /* Collection membership */ | <value> [ not ] in <field> @@ -389,6 +389,14 @@ Operators: Equality *!=* Inequality +*<* + Less than +*<=* + Less than or equal +*>* + Greater than +*>=* + Greater than or equal *=~* Pattern match using regular expression *!~* diff --git a/man/gnt-cluster.rst b/man/gnt-cluster.rst index fb28646eb01a83285e763c36d9de0f59746c1358..8082d14d94b85263fb8b696d579107ae1de32ef8 100644 --- a/man/gnt-cluster.rst +++ b/man/gnt-cluster.rst @@ -654,7 +654,7 @@ The ``continue`` option will let the watcher continue. The ``info`` option shows whether the watcher is currently paused. -redist-conf +REDIST-CONF ~~~~~~~~~~~ **redist-conf** [\--submit] diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst index 6d3c1bab1c752ad7e9e366798009d6317579aa15..77bec0d5435e5537e46e1a9442d04ff605c5c4af 100644 --- a/man/gnt-instance.rst +++ b/man/gnt-instance.rst @@ -1250,7 +1250,7 @@ REPLACE-DISKS [\--disks *idx*] {*instance*} **replace-disks** [\--submit] [\--early-release] [\--ignore-ipolicy] -{\--iallocator *name* \| \--node *node* } {*instance*} +{{-I\|\--iallocator} *name* \| \--node *node* } {*instance*} **replace-disks** [\--submit] [\--early-release] [\--ignore-ipolicy] {\--auto} {*instance*} diff --git a/qa/qa_instance.py b/qa/qa_instance.py index 65af80e1c45eb9c6dd0ce3b46c6327914b78a594..5303f255a7a32ab9dd2ba8257f44f946e6f1ec7e 100644 --- a/qa/qa_instance.py +++ b/qa/qa_instance.py @@ -513,7 +513,7 @@ def _TestInstanceDiskFailure(instance, node, node2, onmaster): AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)]) print qa_utils.FormatInfo("Write to disks and give some time to notice" - " to notice the problem") + " the problem") cmds = [] for disk in devpath: cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc", diff --git a/test/cfgupgrade_unittest.py b/test/cfgupgrade_unittest.py index a8eb8bcd183aa0b61b56426dadb4ac4e38f6fb78..7ca5c3d017f6986ed9712ced92ad1f43ce5500f8 100755 --- a/test/cfgupgrade_unittest.py +++ b/test/cfgupgrade_unittest.py @@ -91,6 +91,7 @@ class TestCfgupgrade(unittest.TestCase): utils.WriteFile(self.config_path, data=serializer.DumpJson({ "version": constants.CONFIG_VERSION, "cluster": {}, + "instances": {}, })) hostname = netutils.GetHostname().name @@ -105,6 +106,7 @@ class TestCfgupgrade(unittest.TestCase): utils.WriteFile(self.config_path, data=serializer.DumpJson({ "version": constants.CONFIG_VERSION, "cluster": {}, + "instances": {}, })) utils.WriteFile(self.ss_master_node_path, @@ -120,6 +122,7 @@ class TestCfgupgrade(unittest.TestCase): "cluster": { "config_version": 0, }, + "instances": {}, } utils.WriteFile(self.config_path, data=serializer.DumpJson(cfg)) self.assertRaises(Exception, _RunUpgrade, self.tmpdir, False, True) @@ -134,6 +137,7 @@ class TestCfgupgrade(unittest.TestCase): cfg = { "version": from_version, "cluster": {}, + "instances": {}, } self._CreateValidConfigDir() utils.WriteFile(self.config_path, data=serializer.DumpJson(cfg)) diff --git a/test/ganeti.qlang_unittest.py b/test/ganeti.qlang_unittest.py index 02b953406655efa29cf6b7120507c0e7e8752e06..df249ec68915a8118d674ba14f17caad92df5ac4 100755 --- a/test/ganeti.qlang_unittest.py +++ b/test/ganeti.qlang_unittest.py @@ -147,6 +147,11 @@ class TestParseFilter(unittest.TestCase): [qlang.OP_NOT, [qlang.OP_REGEXP, "field", utils.DnsNameGlobPattern("*.example.*")]]) + self._Test("ctime < 1234", [qlang.OP_LT, "ctime", 1234]) + self._Test("ctime > 1234", [qlang.OP_GT, "ctime", 1234]) + self._Test("mtime <= 9999", [qlang.OP_LE, "mtime", 9999]) + self._Test("mtime >= 9999", [qlang.OP_GE, "mtime", 9999]) + def testAllFields(self): for name in frozenset(i for d in query.ALL_FIELD_LISTS for i in d.keys()): self._Test("%s == \"value\"" % name, [qlang.OP_EQUAL, name, "value"]) @@ -167,6 +172,11 @@ class TestParseFilter(unittest.TestCase): # Non-matching regexp delimiters tests.append("name =~ /foobarbaz#") + # Invalid operators + tests.append("name <> value") + tests.append("name => value") + tests.append("name =< value") + for qfilter in tests: try: qlang.ParseFilter(qfilter, parser=self.parser) diff --git a/test/ganeti.query_unittest.py b/test/ganeti.query_unittest.py index 87034436a03c4a9827da54b445aeaff9b182c219..ca83d1cc9c99ddecb45553730af2af5e98b8a64f 100755 --- a/test/ganeti.query_unittest.py +++ b/test/ganeti.query_unittest.py @@ -1745,6 +1745,38 @@ class TestQueryFilter(unittest.TestCase): self.assertRaises(errors.ParameterError, query.Query, fielddefs, ["name"], qfilter=["=~", "name", r"["]) + def testFilterLessGreater(self): + fielddefs = query._PrepareFieldList([ + (query._MakeField("value", "Value", constants.QFT_NUMBER, "Value"), + None, 0, lambda ctx, item: item), + ], []) + + data = range(100) + + q = query.Query(fielddefs, ["value"], + qfilter=["<", "value", 20]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(20)]) + + q = query.Query(fielddefs, ["value"], + qfilter=["<=", "value", 30]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(31)]) + + q = query.Query(fielddefs, ["value"], + qfilter=[">", "value", 40]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(41, 100)]) + + q = query.Query(fielddefs, ["value"], + qfilter=[">=", "value", 50]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(50, 100)]) + if __name__ == "__main__": testutils.GanetiTestProgram() diff --git a/tools/cfgupgrade b/tools/cfgupgrade index 05022c906054e67857efdee49bbd235e8438dadf..81dce1dd2ce6378e09b1b9bcef6353f6acc05d08 100755 --- a/tools/cfgupgrade +++ b/tools/cfgupgrade @@ -188,6 +188,21 @@ def main(): config_data["version"] = constants.BuildVersion(TARGET_MAJOR, TARGET_MINOR, 0) + if "instances" not in config_data: + raise Error("Can't find the 'instances' key in the configuration!") + for instance, iobj in config_data["instances"].items(): + if "disks" not in iobj: + raise Error("Instance '%s' doesn't have a disks entry?!" % instance) + disks = iobj["disks"] + for idx, dobj in enumerate(disks): + expected = "disk/%s" % idx + current = dobj.get("iv_name", "") + if current != expected: + logging.warning("Updating iv_name for instance %s/disk %s" + " from '%s' to '%s'", + instance, idx, current, expected) + dobj["iv_name"] = expected + elif config_major == TARGET_MAJOR and config_minor == TARGET_MINOR: logging.info("No changes necessary")