diff --git a/NEWS b/NEWS index 7ce44e0b627f7a543fe1ff7daf5bce54d032f43d..e396b1a2081abb8f209aa4b38fdae91ec42e4426 100644 --- a/NEWS +++ b/NEWS @@ -14,19 +14,7 @@ Version 2.6.0 beta1 Version 2.5.0 rc5 ----------------- -*(unreleased)* - -Improvements and bugfixes -~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Support for kvm version 1.0, that changed the version reporting format - (from 3 to 2 digits). - - -Version 2.5.0 rc4 ------------------ - -*(Released Thu, 27 Oct 2011)* +*(Released Mon, 9 Jan 2012)* Incompatible/important changes and bugfixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -60,6 +48,9 @@ Incompatible/important changes and bugfixes - :doc:`New iallocator modes <design-multi-reloc>` have been added to support operations involving multiple node groups. - Offline nodes are ignored when failing over an instance. +- Support for KVM version 1.0, which changed the version reporting format + from 3 to 2 digits. +- Includes all bugfixes made in the 2.4 series New features ~~~~~~~~~~~~ @@ -153,6 +144,14 @@ Misc - DRBD metadata volumes are overwritten with zeros during disk creation. +Version 2.5.0 rc4 +----------------- + +*(Released Thu, 27 Oct 2011)* + +This was the fourth release candidate of the 2.5 series. + + Version 2.5.0 rc3 ----------------- diff --git a/configure.ac b/configure.ac index 63a33d393ef01ea543dad4bdf673229e593b92f7..3dd5b691e661bb0424cfc87af4324982834ab3b9 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], [~rc4]) +m4_define([gnt_version_suffix], [~rc5]) m4_define([gnt_version_full], m4_format([%d.%d.%d%s], gnt_version_major, gnt_version_minor, diff --git a/lib/cli.py b/lib/cli.py index 282abad5f37029760f16a3c97046e04d92ca045c..a920fb0778d236df2776fda6a174049e86900a93 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -30,6 +30,7 @@ import logging import errno import itertools import shlex +import optparse from cStringIO import StringIO from ganeti import utils @@ -44,7 +45,7 @@ from ganeti import compat from ganeti import netutils from ganeti import qlang -from optparse import (OptionParser, TitledHelpFormatter, +from optparse import (TitledHelpFormatter, Option, OptionValueError) @@ -1480,10 +1481,10 @@ def _ParseArgs(argv, commands, aliases, env_override): argv = utils.InsertAtPos(argv, 1, shlex.split(env_args)) func, args_def, parser_opts, usage, description = commands[cmd] - parser = OptionParser(option_list=parser_opts + COMMON_OPTS, - description=description, - formatter=TitledHelpFormatter(), - usage="%%prog %s %s" % (cmd, usage)) + parser = CustomOptionParser(option_list=parser_opts + COMMON_OPTS, + description=description, + formatter=TitledHelpFormatter(), + usage="%%prog %s %s" % (cmd, usage)) parser.disable_interspersed_args() options, args = parser.parse_args(args=argv[1:]) @@ -1493,6 +1494,21 @@ def _ParseArgs(argv, commands, aliases, env_override): return func, options, args +class CustomOptionParser(optparse.OptionParser): + def _match_long_opt(self, opt): + """Override C{OptionParser}'s function for matching long options. + + The default implementation does prefix-based abbreviation matching. We + disable such behaviour as it can can lead to confusing conflicts (e.g. + C{--force} and C{--force-multi}). + + """ + if opt in self._long_opt: + return opt + else: + raise optparse.BadOptionError(opt) + + def _CheckArguments(cmd, args_def, args): """Verifies the arguments using the argument definition. diff --git a/lib/utils/text.py b/lib/utils/text.py index 058ee859238163c8503626ad7ba7c35ce6b4be4e..02382642bff0282a6d831dc09d2e2e4fbadbaefb 100644 --- a/lib/utils/text.py +++ b/lib/utils/text.py @@ -387,10 +387,12 @@ def UnescapeAndSplit(text, sep=","): num_b = len(e1) - len(e1.rstrip("\\")) if num_b % 2 == 1 and slist: e2 = slist.pop(0) - # here the backslashes remain (all), and will be reduced in - # the next step - rlist.append(e1 + sep + e2) + # Merge the two elements and push the result back to the source list for + # revisiting. If e2 ended with backslashes, further merging may need to + # be done. + slist.insert(0, e1 + sep + e2) continue + # here the backslashes remain (all), and will be reduced in the next step rlist.append(e1) # finally, replace backslash-something with something rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist] diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py index e662192650eb256d049eb8cb3ecb3866f89ae369..233cacb0c619bbfe7648b147fe341942e17b3b23 100644 --- a/qa/qa_cluster.py +++ b/qa/qa_cluster.py @@ -205,7 +205,7 @@ def TestClusterEpo(): # Assert that OOB is unavailable for all nodes result_output = GetCommandOutput(master["primary"], - "gnt-node list --verbose --no-header -o" + "gnt-node list --verbose --no-headers -o" " powered") AssertEqual(compat.all(powered == "(unavail)" for powered in result_output.splitlines()), True) @@ -223,7 +223,7 @@ def TestClusterEpo(): # All instances should have been stopped now result_output = GetCommandOutput(master["primary"], - "gnt-instance list --no-header -o status") + "gnt-instance list --no-headers -o status") AssertEqual(compat.all(status == "ADMIN_down" for status in result_output.splitlines()), True) @@ -232,7 +232,7 @@ def TestClusterEpo(): # All instances should have been started now result_output = GetCommandOutput(master["primary"], - "gnt-instance list --no-header -o status") + "gnt-instance list --no-headers -o status") AssertEqual(compat.all(status == "running" for status in result_output.splitlines()), True) diff --git a/qa/qa_instance.py b/qa/qa_instance.py index e235b09d250513ef662cdc7b56184693ceedc07a..a9b50a1337f1ad5c91d2ae9433abcd9c584196fa 100644 --- a/qa/qa_instance.py +++ b/qa/qa_instance.py @@ -112,7 +112,7 @@ def TestInstanceReboot(instance): AssertCommand(["gnt-instance", "reboot", name]) master = qa_config.GetMasterNode() - cmd = ["gnt-instance", "list", "--no-header", "-o", "status", name] + cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name] result_output = qa_utils.GetCommandOutput(master["primary"], utils.ShellQuoteArgs(cmd)) AssertEqual(result_output.strip(), constants.INSTST_RUNNING) diff --git a/qa/qa_utils.py b/qa/qa_utils.py index 671a6f21abf2ecb976a572c61a1371f590060564..4fdb46eb73f60e5690baea08f8513bff77fea9e7 100644 --- a/qa/qa_utils.py +++ b/qa/qa_utils.py @@ -400,7 +400,7 @@ def _List(listcmd, fields, names): """ master = qa_config.GetMasterNode() - cmd = [listcmd, "list", "--separator=|", "--no-header", + cmd = [listcmd, "list", "--separator=|", "--no-headers", "--output", ",".join(fields)] if names: diff --git a/test/ganeti.utils.text_unittest.py b/test/ganeti.utils.text_unittest.py index 92a983600dd15eab489858770b94b7174604c5c1..1e91468e3195670979e02b65907e0ff6b8c998dd 100755 --- a/test/ganeti.utils.text_unittest.py +++ b/test/ganeti.utils.text_unittest.py @@ -408,6 +408,12 @@ class TestUnescapeAndSplit(unittest.TestCase): b = [sep, sep, "c", "d.moo\\"] self.assertEqual(utils.UnescapeAndSplit("%s\\" % sep.join(a), sep=sep), b) + def testMultipleEscapes(self): + for sep in self._seps: + a = ["a", "b\\" + sep + "c", "d\\" + sep + "e\\" + sep + "f", "g"] + b = ["a", "b" + sep + "c", "d" + sep + "e" + sep + "f", "g"] + self.failUnlessEqual(utils.UnescapeAndSplit(sep.join(a), sep=sep), b) + class TestCommaJoin(unittest.TestCase): def test(self): diff --git a/tools/cluster-merge b/tools/cluster-merge index 7162d66c3759ca04302b9d7693b55b990acf3fe9..c3a0e09bcf443ef13c28679360045074eaac6f1c 100755 --- a/tools/cluster-merge +++ b/tools/cluster-merge @@ -192,7 +192,7 @@ class Merger(object): utils.WriteFile(key_path, mode=0600, data=result.stdout) result = self._RunCmd(cluster, "gnt-node list -o name,offline" - " --no-header --separator=,", private_key=key_path) + " --no-headers --separator=,", private_key=key_path) if result.failed: raise errors.RemoteError("Unable to retrieve list of nodes from %s." " Fail reason: %s; output: %s" % @@ -201,7 +201,7 @@ class Merger(object): nodes = [node_status[0] for node_status in nodes_statuses if node_status[1] == "N"] - result = self._RunCmd(cluster, "gnt-instance list -o name --no-header", + result = self._RunCmd(cluster, "gnt-instance list -o name --no-headers", private_key=key_path) if result.failed: raise errors.RemoteError("Unable to retrieve list of instances from"