diff --git a/NEWS b/NEWS index ccc5391d146d6940d6c98b6abf9d73eda5060374..12e26c35cf2c64a4da058cac387d966d4827bcb5 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,22 @@ News ==== +Version 2.3.0 rc1 +----------------- + +*(Released Fri, 19 Nov 2010)* + +A number of bugfixes and documentation updates: + +- Update ganeti-os-interface documentation +- Fixed a bug related to duplicate MACs or similar items which should be + unique +- Fix breakage in OS state modify +- Reinstall instance: disallow offline secondaries (fixes bug related to + OS changing but reinstall failing) +- plus all the other fixes between 2.2.1 and 2.2.2 + + Version 2.3.0 rc0 ----------------- @@ -44,6 +60,20 @@ Version 2.3.0 rc0 - Infrastructure changes for node group support in future versions +Version 2.2.2 +------------- + +*(Released Fri, 19 Nov 2010)* + +A few small bugs fixed, and some improvements to the build system: + +- Fix documentation regarding conversion to drbd +- Fix validation of parameters in cluster modify (``gnt-cluster modify + -B``) +- Fix error handling in node modify with multiple changes +- Allow remote imports without checked names + + Version 2.2.1 ------------- @@ -234,6 +264,21 @@ Version 2.2.0 beta 0 see the ``ganeti-os-interface(7)`` manpage and look for ``EXP_SIZE_FD`` + +Version 2.1.8 +------------- + +*(Released Tue, 16 Nov 2010)* + +Some more bugfixes. Unless critical bugs occur, this will be the last +2.1 release: + +- Fix case of MAC special-values +- Fix mac checker regex +- backend: Fix typo causing βout of rangeβ error +- Add missing --units in gnt-instance list man page + + Version 2.1.7 ------------- diff --git a/configure.ac b/configure.ac index afae4b4254239424700b4eda3ba6ede2d0564736..1595d9dfde51f6b244797c33e6d4be0a7c092c4d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ m4_define([gnt_version_major], [2]) m4_define([gnt_version_minor], [3]) m4_define([gnt_version_revision], [0]) -m4_define([gnt_version_suffix], [~rc0]) +m4_define([gnt_version_suffix], [~rc1]) m4_define([gnt_version_full], m4_format([%d.%d.%d%s], gnt_version_major, gnt_version_minor, diff --git a/doc/admin.rst b/doc/admin.rst index 9e90df8590902f28d8b1cc101803ed59dbbfcff2..3daf408650221995f4acc9ff0d8e49042d076a7e 100644 --- a/doc/admin.rst +++ b/doc/admin.rst @@ -542,7 +542,7 @@ modify`` command:: # later convert it to redundant gnt-instance stop INSTANCE - gnt-instance modify -t drbd INSTANCE + gnt-instance modify -t drbd -n NEW_SECONDARY INSTANCE gnt-instance start INSTANCE # and convert it back diff --git a/lib/cmdlib.py b/lib/cmdlib.py index babc361a99bc41bec5653fde6d60b55ccb5e99fb..5a0e43685faffadc80a7623f11e39f2a3db424f1 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -593,17 +593,19 @@ def _CheckGlobalHvParams(params): raise errors.OpPrereqError(msg, errors.ECODE_INVAL) -def _CheckNodeOnline(lu, node): +def _CheckNodeOnline(lu, node, msg=None): """Ensure that a given node is online. @param lu: the LU on behalf of which we make the check @param node: the node to check + @param msg: if passed, should be a message to replace the default one @raise errors.OpPrereqError: if the node is offline """ + if msg is None: + msg = "Can't use offline node" if lu.cfg.GetNodeInfo(node).offline: - raise errors.OpPrereqError("Can't use offline node %s" % node, - errors.ECODE_STATE) + raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE) def _CheckNodeNotDrained(lu, node): @@ -2575,8 +2577,7 @@ class LUSetClusterParams(LogicalUnit): ht.TNone)), ("hvparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict), ht.TNone)), - ("beparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict), - ht.TNone)), + ("beparams", None, ht.TOr(ht.TDict, ht.TNone)), ("os_hvp", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict), ht.TNone)), ("osparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict), @@ -2883,14 +2884,14 @@ class LUSetClusterParams(LogicalUnit): for key, val in mods: if key == constants.DDM_ADD: if val in lst: - feedback_fn("OS %s already in %s, ignoring", val, desc) + feedback_fn("OS %s already in %s, ignoring" % (val, desc)) else: lst.append(val) elif key == constants.DDM_REMOVE: if val in lst: lst.remove(val) else: - feedback_fn("OS %s not found in %s, ignoring", val, desc) + feedback_fn("OS %s not found in %s, ignoring" % (val, desc)) else: raise errors.ProgrammerError("Invalid modification '%s'" % key) @@ -5009,7 +5010,11 @@ class LUReinstallInstance(LogicalUnit): instance = self.cfg.GetInstanceInfo(self.op.instance_name) assert instance is not None, \ "Cannot retrieve locked instance %s" % self.op.instance_name - _CheckNodeOnline(self, instance.primary_node) + _CheckNodeOnline(self, instance.primary_node, "Instance primary node" + " offline, cannot reinstall") + for node in instance.secondary_nodes: + _CheckNodeOnline(self, node, "Instance secondary node offline," + " cannot reinstall") if instance.disk_template == constants.DT_DISKLESS: raise errors.OpPrereqError("Instance '%s' has no disks" % diff --git a/lib/config.py b/lib/config.py index c5d1a6ab091a5accf3b615e907fefc50f8fcff76..669dadafbe440cc179c8c5e8b6d6dfae0bc02a2b 100644 --- a/lib/config.py +++ b/lib/config.py @@ -81,15 +81,15 @@ class TemporaryReservationManager: self._ec_reserved = {} def Reserved(self, resource): - for holder_reserved in self._ec_reserved.items(): + for holder_reserved in self._ec_reserved.values(): if resource in holder_reserved: return True return False def Reserve(self, ec_id, resource): if self.Reserved(resource): - raise errors.ReservationError("Duplicate reservation for resource: %s." % - (resource)) + raise errors.ReservationError("Duplicate reservation for resource '%s'" + % str(resource)) if ec_id not in self._ec_reserved: self._ec_reserved[ec_id] = set([resource]) else: diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst index bc1281122e744db96642c23ed356a8459806d265..5e8d8fe23a5280e7d522d9c1668ddfd525438e04 100644 --- a/man/gnt-instance.rst +++ b/man/gnt-instance.rst @@ -834,7 +834,7 @@ MODIFY | [-B *BACKEND\_PARAMETERS*] | [--net add*[:options]* \| --net remove \| --net *N:options*] | [--disk add:size=*SIZE* \| --disk remove \| --disk *N*:mode=*MODE*] -| [-t {plain \| drbd}] +| [-t plain | -t drbd -n *new_secondary*] | [--os-name=*OS* [--force-variant]] | [--submit] | {*instance*} @@ -849,9 +849,10 @@ name=value[,...]. For details which options can be specified, see the **add** command. The ``-t`` option will change the disk template of the instance. -Currently only conversions between the plain and drbd disk -templates are supported, and the instance must be stopped before -attempting the conversion. +Currently only conversions between the plain and drbd disk templates +are supported, and the instance must be stopped before attempting the +conversion. When changing from the plain to the drbd disk template, a +new secondary node must be specified via the ``-n`` option. The ``--disk add:size=``*SIZE* option adds a disk to the instance. The ``--disk remove`` option will remove the last disk of the diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index aace2348022105cbd2c3d9257424a7a988cb2510..af8ddb59698e20dca806a04b4bd2ef941185b92b 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -128,6 +128,10 @@ def RunClusterTests(): if qa_config.TestEnabled('cluster-reserved-lvs'): RunTest(qa_cluster.TestClusterReservedLvs) + if qa_config.TestEnabled("cluster-modify"): + RunTest(qa_cluster.TestClusterModifyBe) + # TODO: add more cluster modify tests + if qa_config.TestEnabled('cluster-rename'): RunTest(qa_cluster.TestClusterRename) diff --git a/qa/qa-sample.json b/qa/qa-sample.json index 3b1be0df60ecb7e6258f4ffb8fb15077f7508134..57fd3539f1655461535aeeb32ae4788a61a90c34 100644 --- a/qa/qa-sample.json +++ b/qa/qa-sample.json @@ -51,6 +51,7 @@ "cluster-destroy": true, "cluster-rename": true, "cluster-reserved-lvs": true, + "cluster-modify": true, "node-info": true, "node-volumes": true, diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py index bab5bc6280676dae241645e87bcdf7c9a9a01306..f615a736bb35a7305bc349663dbf3408dca7fd27 100644 --- a/qa/qa_cluster.py +++ b/qa/qa_cluster.py @@ -171,6 +171,34 @@ def TestClusterReservedLvs(): utils.ShellQuoteArgs(cmd)).wait(), rcode) +def TestClusterModifyBe(): + """gnt-cluster modify -B""" + master = qa_config.GetMasterNode() + + for rcode, cmd in [ + # mem + (0, ["gnt-cluster", "modify", "-B", "memory=256"]), + (0, ["sh", "-c", "gnt-cluster info|grep '^ *memory: 256$'"]), + (1, ["gnt-cluster", "modify", "-B", "memory=a"]), + (0, ["gnt-cluster", "modify", "-B", "memory=128"]), + (0, ["sh", "-c", "gnt-cluster info|grep '^ *memory: 128$'"]), + # vcpus + (0, ["gnt-cluster", "modify", "-B", "vcpus=4"]), + (0, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]), + (1, ["gnt-cluster", "modify", "-B", "vcpus=a"]), + (0, ["gnt-cluster", "modify", "-B", "vcpus=1"]), + (0, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]), + # auto_balance + (0, ["gnt-cluster", "modify", "-B", "auto_balance=False"]), + (0, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]), + (1, ["gnt-cluster", "modify", "-B", "auto_balance=1"]), + (0, ["gnt-cluster", "modify", "-B", "auto_balance=True"]), + (0, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]), + ]: + AssertEqual(StartSSH(master['primary'], + utils.ShellQuoteArgs(cmd)).wait(), rcode) + + def TestClusterInfo(): """gnt-cluster info""" master = qa_config.GetMasterNode() diff --git a/qa/qa_os.py b/qa/qa_os.py index 143fedc53df3eb575659e9804daeabcd13767202..e6e259aadb8dc3c7408aa7d86d1cede5f29d6fe0 100644 --- a/qa/qa_os.py +++ b/qa/qa_os.py @@ -86,6 +86,9 @@ def _TestOsStates(): new_cmd = cmd + ["--%s" % param, val, _TEMP_OS_NAME] AssertEqual(StartSSH(master["primary"], utils.ShellQuoteArgs(new_cmd)).wait(), 0) + # check that double-running the command is OK + AssertEqual(StartSSH(master["primary"], + utils.ShellQuoteArgs(new_cmd)).wait(), 0) def _SetupTempOs(node, dir, valid): diff --git a/test/ganeti.config_unittest.py b/test/ganeti.config_unittest.py index 984bfff629eb24530f91d7a4c3b359c348be1b48..51284a7827412628e15b7d1cc69b80425a850e28 100755 --- a/test/ganeti.config_unittest.py +++ b/test/ganeti.config_unittest.py @@ -37,6 +37,8 @@ from ganeti import objects from ganeti import utils from ganeti import netutils +from ganeti.config import TemporaryReservationManager + import testutils import mocks @@ -186,5 +188,23 @@ class TestConfigRunner(unittest.TestCase): CheckSyntax, {mode: m_bridged, link: ''}) +class TestTRM(unittest.TestCase): + EC_ID = 1 + + def testEmpty(self): + t = TemporaryReservationManager() + t.Reserve(self.EC_ID, "a") + self.assertFalse(t.Reserved(self.EC_ID)) + self.assertTrue(t.Reserved("a")) + self.assertEqual(len(t.GetReserved()), 1) + + def testDuplicate(self): + t = TemporaryReservationManager() + t.Reserve(self.EC_ID, "a") + self.assertRaises(errors.ReservationError, t.Reserve, 2, "a") + t.DropECReservations(self.EC_ID) + self.assertFalse(t.Reserved("a")) + + if __name__ == '__main__': testutils.GanetiTestProgram()