diff --git a/lib/backend.py b/lib/backend.py index 601a3f3627debdecb8f7ad903485b9d4cc674b3b..9b98a7675e239552141f4a6c4ba0df2de6e80c30 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -2109,7 +2109,7 @@ def FinalizeExport(instance, snap_disks): utils.WriteFile(utils.PathJoin(destdir, constants.EXPORT_CONF_FILE), data=config.Dumps()) - shutil.rmtree(finaldestdir, True) + shutil.rmtree(finaldestdir, ignore_errors=True) shutil.move(destdir, finaldestdir) @@ -2273,7 +2273,7 @@ def _TransformFileStorageDir(file_storage_dir): cfg = _GetConfig() file_storage_dir = os.path.normpath(file_storage_dir) base_file_storage_dir = cfg.GetFileStorageDir() - if (not os.path.commonprefix([file_storage_dir, base_file_storage_dir]) == + if (os.path.commonprefix([file_storage_dir, base_file_storage_dir]) != base_file_storage_dir): _Fail("File storage directory '%s' is not under base file" " storage directory '%s'", file_storage_dir, base_file_storage_dir) diff --git a/lib/cli.py b/lib/cli.py index c178bb62188cddda0cff1fc3622c3d606f28bf63..2a5dc17b3a163f2f6b7f9c891150ca4441b8a57c 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -91,6 +91,7 @@ __all__ = [ "NODE_PLACEMENT_OPT", "NOHDR_OPT", "NOIPCHECK_OPT", + "NO_INSTALL_OPT", "NONAMECHECK_OPT", "NOLVM_STORAGE_OPT", "NOMODIFY_ETCHOSTS_OPT", @@ -126,6 +127,7 @@ __all__ = [ "TAG_SRC_OPT", "TIMEOUT_OPT", "USEUNITS_OPT", + "USE_REPL_NET_OPT", "VERBOSE_OPT", "VG_NAME_OPT", "YES_DOIT_OPT", @@ -595,6 +597,11 @@ FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant", action="store_true", default=False, help="Force an unknown variant") +NO_INSTALL_OPT = cli_option("--no-install", dest="no_install", + action="store_true", default=False, + help="Do not install the OS (will" + " enable no-start)") + BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams", type="keyval", default={}, help="Backend parameters") @@ -911,6 +918,12 @@ NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret", help=("Create a new cluster domain" " secret")) +USE_REPL_NET_OPT = cli_option("--use-replication-network", + dest="use_replication_network", + help="Whether to use the replication network" + " for talking to the nodes", + action="store_true", default=False) + def _ParseArgs(argv, commands, aliases): """Parser for the command line arguments. @@ -1580,11 +1593,13 @@ def GenericInstanceCreate(mode, opts, args): os_type = opts.os src_node = None src_path = None + no_install = opts.no_install elif mode == constants.INSTANCE_IMPORT: start = False os_type = None src_node = opts.src_node src_path = opts.src_dir + no_install = None else: raise errors.ProgrammerError("Invalid creation mode %s" % mode) @@ -1606,7 +1621,8 @@ def GenericInstanceCreate(mode, opts, args): start=start, os_type=os_type, src_node=src_node, - src_path=src_path) + src_path=src_path, + no_install=no_install) SubmitOrSend(op, opts) return 0 @@ -1901,7 +1917,8 @@ def ParseTimespec(value): return value -def GetOnlineNodes(nodes, cl=None, nowarn=False): +def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False, + filter_master=False): """Returns the names of online nodes. This function will also log a warning on stderr with the names of @@ -1914,17 +1931,36 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False): @param nowarn: by default, this function will output a note with the offline nodes that are skipped; if this parameter is True the note is not displayed + @type secondary_ips: boolean + @param secondary_ips: if True, return the secondary IPs instead of the + names, useful for doing network traffic over the replication interface + (if any) + @type filter_master: boolean + @param filter_master: if True, do not return the master node in the list + (useful in coordination with secondary_ips where we cannot check our + node name against the list) """ if cl is None: cl = GetClient() - result = cl.QueryNodes(names=nodes, fields=["name", "offline"], + if secondary_ips: + name_idx = 2 + else: + name_idx = 0 + + if filter_master: + master_node = cl.QueryConfigValues(["master_node"])[0] + filter_fn = lambda x: x != master_node + else: + filter_fn = lambda _: True + + result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"], use_locking=False) offline = [row[0] for row in result if row[1]] if offline and not nowarn: ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline)) - return [row[0] for row in result if not row[1]] + return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])] def _ToStream(stream, txt, *args): diff --git a/lib/cmdlib.py b/lib/cmdlib.py index cf25d9037b5eccc0715175fd65ab3a92376a7bb4..f15cd14b622c53ddcdefab8059044b2eaa9fd700 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -542,6 +542,24 @@ def _CheckNodeNotDrained(lu, node): errors.ECODE_INVAL) +def _CheckNodeHasOS(lu, node, os_name, force_variant): + """Ensure that a node supports a given OS. + + @param lu: the LU on behalf of which we make the check + @param node: the node to check + @param os_name: the OS to query about + @param force_variant: whether to ignore variant errors + @raise errors.OpPrereqError: if the node is not supporting the OS + + """ + result = lu.rpc.call_os_get(node, os_name) + result.Raise("OS '%s' not in supported OS list for node %s" % + (os_name, node), + prereq=True, ecode=errors.ECODE_INVAL) + if not force_variant: + _CheckOSVariant(result.payload, os_name) + + def _CheckDiskTemplate(template): """Ensure a given disk template is valid. @@ -4139,12 +4157,7 @@ class LUReinstallInstance(LogicalUnit): if self.op.os_type is not None: # OS verification pnode = _ExpandNodeName(self.cfg, instance.primary_node) - result = self.rpc.call_os_get(pnode, self.op.os_type) - result.Raise("OS '%s' not in supported OS list for primary node %s" % - (self.op.os_type, pnode), - prereq=True, ecode=errors.ECODE_INVAL) - if not self.op.force_variant: - _CheckOSVariant(result.payload, self.op.os_type) + _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant) self.instance = instance @@ -5824,6 +5837,11 @@ class LUCreateInstance(LogicalUnit): # for tools if not hasattr(self.op, "name_check"): self.op.name_check = True + if not hasattr(self.op, "no_install"): + self.op.no_install = False + if self.op.no_install and self.op.start: + self.LogInfo("No-installation mode selected, disabling startup") + self.op.start = False # validate/normalize the instance name self.op.instance_name = utils.HostInfo.NormalizeName(self.op.instance_name) if self.op.ip_check and not self.op.name_check: @@ -6068,6 +6086,9 @@ class LUCreateInstance(LogicalUnit): # works again! self.op.force_variant = True + if self.op.no_install: + self.LogInfo("No-installation mode has no effect during import") + else: # INSTANCE_CREATE if getattr(self.op, "os_type", None) is None: raise errors.OpPrereqError("No guest OS specified", @@ -6316,13 +6337,7 @@ class LUCreateInstance(LogicalUnit): _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams) - # os verification - result = self.rpc.call_os_get(pnode.name, self.op.os_type) - result.Raise("OS '%s' not in supported os list for primary node %s" % - (self.op.os_type, pnode.name), - prereq=True, ecode=errors.ECODE_INVAL) - if not self.op.force_variant: - _CheckOSVariant(result.payload, self.op.os_type) + _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant) _CheckNicsBridgesExist(self, self.nics, self.pnode.name) @@ -6440,12 +6455,13 @@ class LUCreateInstance(LogicalUnit): if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks: if self.op.mode == constants.INSTANCE_CREATE: - feedback_fn("* running the instance OS create scripts...") - # FIXME: pass debug option from opcode to backend - result = self.rpc.call_instance_os_add(pnode_name, iobj, False, - self.op.debug_level) - result.Raise("Could not add os for instance %s" - " on node %s" % (instance, pnode_name)) + if not self.op.no_install: + feedback_fn("* running the instance OS create scripts...") + # FIXME: pass debug option from opcode to backend + result = self.rpc.call_instance_os_add(pnode_name, iobj, False, + self.op.debug_level) + result.Raise("Could not add os for instance %s" + " on node %s" % (instance, pnode_name)) elif self.op.mode == constants.INSTANCE_IMPORT: feedback_fn("* running the instance OS import scripts...") @@ -7743,9 +7759,13 @@ class LUSetInstanceParams(LogicalUnit): self.op.disk_template = None if not hasattr(self.op, "remote_node"): self.op.remote_node = None + if not hasattr(self.op, "os_name"): + self.op.os_name = None + if not hasattr(self.op, "force_variant"): + self.op.force_variant = False self.op.force = getattr(self.op, "force", False) if not (self.op.nics or self.op.disks or self.op.disk_template or - self.op.hvparams or self.op.beparams): + self.op.hvparams or self.op.beparams or self.op.os_name): raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL) if self.op.hvparams: @@ -8173,6 +8193,11 @@ class LUSetInstanceParams(LogicalUnit): (disk_op, len(instance.disks)), errors.ECODE_INVAL) + # OS change + if self.op.os_name and not self.op.force: + _CheckNodeHasOS(self, instance.primary_node, self.op.os_name, + self.op.force_variant) + return def _ConvertPlainToDrbd(self, feedback_fn): @@ -8383,6 +8408,10 @@ class LUSetInstanceParams(LogicalUnit): for key, val in self.op.beparams.iteritems(): result.append(("be/%s" % key, val)) + # OS change + if self.op.os_name: + instance.os = self.op.os_name + self.cfg.Update(instance, feedback_fn) return result diff --git a/lib/opcodes.py b/lib/opcodes.py index e718a8e7b740fb83904df1a9ceed1b6e7ab94467..d6d026ae69ff4a4729dac5e8ed1b3eb67e9656fc 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -460,7 +460,8 @@ class OpCreateInstance(OpCode): OP_ID = "OP_INSTANCE_CREATE" OP_DSC_FIELD = "instance_name" __slots__ = [ - "instance_name", "os_type", "force_variant", + "instance_name", + "os_type", "force_variant", "no_install", "pnode", "disk_template", "snode", "mode", "disks", "nics", "src_node", "src_path", "start", @@ -621,7 +622,7 @@ class OpSetInstanceParams(OpCode): "instance_name", "hvparams", "beparams", "force", "nics", "disks", "disk_template", - "remote_node", + "remote_node", "os_name", "force_variant", ] diff --git a/man/gnt-cluster.sgml b/man/gnt-cluster.sgml index 6ee98c2d83bb4a777477529807442199c2228602..a54b1c4117288a1e237398328c6c310006bd4fdf 100644 --- a/man/gnt-cluster.sgml +++ b/man/gnt-cluster.sgml @@ -138,6 +138,7 @@ <cmdsynopsis> <command>copyfile</command> + <arg>--use-replication-network</arg> <arg>-n <replaceable>node</replaceable></arg> <arg choice="req"><replaceable>file</replaceable></arg> </cmdsynopsis> @@ -150,6 +151,10 @@ <option>-n</option> is not given at all, the file will be copied to all nodes. + Passing the <option>--use-replication-network</option> option + will cause the copy to be done over the replication network + (only matters if the primary/secondary IPs are different). + Example: <screen> # gnt-cluster -n node1.example.com -n node2.example.com copyfile /tmp/test diff --git a/man/gnt-instance.sgml b/man/gnt-instance.sgml index b8ed14b3a03d1c145f43c2c1faca65842168f501..178a3596fd6d7cb6b06e5df8a848b82e15c0844a 100644 --- a/man/gnt-instance.sgml +++ b/man/gnt-instance.sgml @@ -85,6 +85,7 @@ <arg>--no-ip-check</arg> <arg>--no-name-check</arg> <arg>--no-start</arg> + <arg>--no-install</arg> <sbr> <group> <arg rep="repeat">--net=<replaceable>N</replaceable><arg rep="repeat">:options</arg></arg> @@ -243,7 +244,13 @@ <para> The <option>-o</option> options specifies the operating system to be installed. The available operating systems can - be listed with <command>gnt-os list</command>. + be listed with <command>gnt-os + list</command>. Passing <option>--no-install</option> will + however skip the OS installation, allowing a manual import + if so desired. Note that the no-installation mode will + automatically disable the start-up of the instance (without + an OS, it most likely won't be able to start-up + successfully). </para> <para> @@ -1403,6 +1410,8 @@ instance5: 11225 <arg>drbd</arg> </group></arg> + <sbr> + <arg>--os-name=<replaceable>OS</replaceable> <arg>--force-variant</arg></arg> <sbr> <arg>--submit</arg> @@ -1453,6 +1462,16 @@ instance5: 11225 option will change the parameters of the Nth instance NIC. </para> + <para> + The option <option>--os-name</option> will change the OS + name for the instance (without reinstallation). In case an + OS variant is specified that is not found, then by default + the modification is refused, + unless <option>--force-variant</option> is passed. An + invalid OS will also be refused, unless + the <option>--force</option> option is given. + </para> + <para> The <option>--submit</option> option is used to send the job to the master daemon but not wait for its completion. The job diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster index c51b73dd13a7476635166fa86bdb1b26a2d92e23..8adfff26e119fe4b404424f5a433c8e5425c7d32 100755 --- a/scripts/gnt-cluster +++ b/scripts/gnt-cluster @@ -300,12 +300,10 @@ def ClusterCopyFile(opts, args): cl = GetClient() - myname = utils.GetHostInfo().name - cluster_name = cl.QueryConfigValues(["cluster_name"])[0] - results = GetOnlineNodes(nodes=opts.nodes, cl=cl) - results = [name for name in results if name != myname] + results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True, + secondary_ips=opts.use_replication_network) srun = ssh.SshRunner(cluster_name=cluster_name) for node in results: @@ -777,7 +775,7 @@ commands = { "", "Shows the cluster master"), 'copyfile': ( ClusterCopyFile, [ArgFile(min=1, max=1)], - [NODE_LIST_OPT], + [NODE_LIST_OPT, USE_REPL_NET_OPT], "[-n node...] <filename>", "Copies a file to all (or only some) nodes"), 'command': ( RunClusterCommand, [ArgCommand(min=1)], diff --git a/scripts/gnt-instance b/scripts/gnt-instance index 45444f11afa76dcc6ac30e018c088f229e3341a4..406325348400fe2f9c18060491f68fbc5a767477 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -1208,7 +1208,7 @@ def SetInstanceParams(opts, args): """ if not (opts.nics or opts.disks or opts.disk_template or - opts.hvparams or opts.beparams): + opts.hvparams or opts.beparams or opts.os): ToStderr("Please give at least one of the parameters.") return 1 @@ -1261,6 +1261,8 @@ def SetInstanceParams(opts, args): remote_node=opts.node, hvparams=opts.hvparams, beparams=opts.beparams, + os_name=opts.os, + force_variant=opts.force_variant, force=opts.force) # even if here we process the result, we allow submit only @@ -1339,6 +1341,7 @@ add_opts = [ NWSYNC_OPT, OS_OPT, FORCE_VARIANT_OPT, + NO_INSTALL_OPT, OS_SIZE_OPT, SUBMIT_OPT, ] @@ -1417,7 +1420,7 @@ commands = { 'modify': ( SetInstanceParams, ARGS_ONE_INSTANCE, [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT, - DISK_TEMPLATE_OPT, SINGLE_NODE_OPT], + DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT], "<instance>", "Alters the parameters of an instance"), 'shutdown': ( GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],