diff --git a/lib/cli.py b/lib/cli.py index 14be846b643978ec27aa47aab833d17a3f872e50..3c5e97a40942c577088d6a813243331fc5dbe7c6 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -70,6 +70,7 @@ __all__ = [ "IALLOCATOR_OPT", "IGNORE_CONSIST_OPT", "IGNORE_FAILURES_OPT", + "IGNORE_REMOVE_FAILURES_OPT", "IGNORE_SECONDARIES_OPT", "IGNORE_SIZE_OPT", "MAC_PREFIX_OPT", @@ -101,6 +102,7 @@ __all__ = [ "OS_SIZE_OPT", "READD_OPT", "REBOOT_TYPE_OPT", + "REMOVE_INSTANCE_OPT", "SECONDARY_IP_OPT", "SELECT_OS_OPT", "SEP_OPT", @@ -684,6 +686,18 @@ IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures", " configuration even if there are failures" " during the removal process") +IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures", + dest="ignore_remove_failures", + action="store_true", default=False, + help="Remove the instance from the" + " cluster configuration even if there" + " are failures during the removal" + " process") + +REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance", + action="store_true", default=False, + help="Remove the instance from the cluster") + NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node", help="Specifies the new secondary node", metavar="NODE", default=None, diff --git a/lib/objects.py b/lib/objects.py index 76ca042c4fc8bb353654db5514707b3aceae0e7a..81cde182da2e410a27158d4867c335f8ecf74bda 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -48,6 +48,7 @@ __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", _TIMESTAMPS = ["ctime", "mtime"] _UUID = ["uuid"] + def FillDict(defaults_dict, custom_dict, skip_keys=None): """Basic function to apply settings on top a default dict. diff --git a/man/gnt-backup.sgml b/man/gnt-backup.sgml index d9c1fe74b21c1a3cd72834556f081be7c246cea8..ff6c3cee9a45dbf02b86ac84642ce5ecfa86d4d2 100644 --- a/man/gnt-backup.sgml +++ b/man/gnt-backup.sgml @@ -65,6 +65,8 @@ <arg choice="req">-n <replaceable>node</replaceable></arg> <arg>--shutdown-timeout=<replaceable>N</replaceable></arg> <arg>--noshutdown</arg> + <arg>--remove-instance</arg> + <arg>--ignore-remove-failures</arg> <arg choice="req"><replaceable>instance</replaceable></arg> </cmdsynopsis> @@ -91,6 +93,12 @@ in the exported dump. </para> + <para> + The <option>--remove</option> option can be used to remove the + instance after it was exported. This is useful to make one last + backup before removing the instance. + </para> + <para> The exit code of the command is 0 if all disks were backed up successfully, 1 if no data was backed up or if the diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index 5c399e72ccd68e7567ea706be20c8f8cb34607c7..99041853810c95f818decbc8c3ab8269d6b06af1 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -169,6 +169,7 @@ def RunCommonInstanceTests(instance): if qa_rapi.Enabled(): RunTest(qa_rapi.TestInstance, instance) + def RunExportImportTests(instance, pnode): """Tries to export and import the instance. @@ -312,6 +313,18 @@ def main(): finally: qa_config.ReleaseNode(snode) + if (qa_config.TestEnabled('instance-add-plain-disk') and + qa_config.TestEnabled("instance-export")): + instance = RunTest(qa_instance.TestInstanceAddWithPlainDisk, pnode) + expnode = qa_config.AcquireNode(exclude=pnode) + try: + RunTest(qa_instance.TestInstanceExportWithRemove, instance, expnode) + RunTest(qa_instance.TestBackupList, expnode) + finally: + qa_config.ReleaseNode(expnode) + del expnode + del instance + finally: qa_config.ReleaseNode(pnode) diff --git a/qa/qa_instance.py b/qa/qa_instance.py index af3afeb6af10295f027240cd7213f884008cb4d3..b0d9a6574390c575250dda73c70f2d6e7110cd86 100644 --- a/qa/qa_instance.py +++ b/qa/qa_instance.py @@ -249,6 +249,16 @@ def TestInstanceExport(instance, node): return qa_utils.ResolveInstanceName(instance) +def TestInstanceExportWithRemove(instance, node): + """gnt-backup export --remove-instance""" + master = qa_config.GetMasterNode() + + cmd = ['gnt-backup', 'export', '-n', node['primary'], "--remove-instance", + instance['name']] + AssertEqual(StartSSH(master['primary'], + utils.ShellQuoteArgs(cmd)).wait(), 0) + + def TestInstanceImport(node, newinst, expnode, name): """gnt-backup import""" master = qa_config.GetMasterNode() diff --git a/scripts/gnt-backup b/scripts/gnt-backup index fd04a92959156a63ec0eb1b2ea2dc8ae4aada14e..fb1b767cfeed1e3a946b54cdd39d95bfe6a4e1e4 100755 --- a/scripts/gnt-backup +++ b/scripts/gnt-backup @@ -71,10 +71,14 @@ def ExportInstance(opts, args): @return: the desired exit code """ + ignore_remove_failures = opts.ignore_remove_failures + op = opcodes.OpExportInstance(instance_name=args[0], target_node=opts.node, shutdown=opts.shutdown, - shutdown_timeout=opts.shutdown_timeout) + shutdown_timeout=opts.shutdown_timeout, + remove_instance=opts.remove_instance, + ignore_remove_failures=ignore_remove_failures) fin_resu, dlist = SubmitOpCode(op) if not isinstance(dlist, list): @@ -97,6 +101,7 @@ def ExportInstance(opts, args): rcode = 1 return rcode + def ImportInstance(opts, args): """Add an instance to the cluster. @@ -144,6 +149,7 @@ import_opts = [ SUBMIT_OPT, ] + commands = { 'list': ( PrintExportList, ARGS_NONE, @@ -151,7 +157,8 @@ commands = { "", "Lists instance exports available in the ganeti cluster"), 'export': ( ExportInstance, ARGS_ONE_INSTANCE, - [FORCE_OPT, SINGLE_NODE_OPT, NOSHUTDOWN_OPT, SHUTDOWN_TIMEOUT_OPT], + [FORCE_OPT, SINGLE_NODE_OPT, NOSHUTDOWN_OPT, SHUTDOWN_TIMEOUT_OPT, + REMOVE_INSTANCE_OPT, IGNORE_REMOVE_FAILURES_OPT], "-n <target_node> [opts...] <name>", "Exports an instance to an image"), 'import': (