diff --git a/lib/cli.py b/lib/cli.py index 021591f6a43882307ea5e9a5614ae3e07115fdbf..50f5363cb99e1a7ece028df3245bf67793647905 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -118,6 +118,7 @@ __all__ = [ "REBOOT_TYPE_OPT", "REMOVE_INSTANCE_OPT", "REMOVE_UIDS_OPT", + "ROMAN_OPT", "SECONDARY_IP_OPT", "SELECT_OS_OPT", "SEP_OPT", @@ -977,6 +978,12 @@ REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None, " ranges separated by commas, to be" " removed from the user-id pool")) +ROMAN_OPT = cli_option("--roman", + dest="roman_integers", default=False, + action="store_true", + help="Use roman numbers for positive integers") + + def _ParseArgs(argv, commands, aliases): """Parser for the command line arguments. diff --git a/lib/compat.py b/lib/compat.py index db3ae2874cf955c78af59c4408dbb5ad42bf0a09..51ff904ca8968d8bd3607111fa309f911d3bb128 100644 --- a/lib/compat.py +++ b/lib/compat.py @@ -30,6 +30,11 @@ try: except ImportError: functools = None +try: + import roman +except ImportError: + roman = None + # compat.md5_hash and compat.sha1_hash can be called to generate and md5 and a # sha1 hashing modules, under python 2.4, 2.5 and 2.6, even though some changes @@ -101,6 +106,29 @@ def _partial(func, *args, **keywords): # pylint: disable-msg=W0622 return newfunc +def TryToRoman(val, convert=True): + """Try to convert a value to roman numerals + + If the roman module could be loaded convert the given value to a roman + numeral. Gracefully fail back to leaving the value untouched. + + @type val: integer + @param val: value to convert + @type convert: boolean + @param convert: if False, don't try conversion at all + @rtype: string or typeof(val) + @return: roman numeral for val, or val if conversion didn't succeed + + """ + if roman is not None and convert: + try: + return roman.toRoman(val) + except roman.RomanError: + return val + else: + return val + + if functools is None: partial = _partial else: diff --git a/lib/constants.py b/lib/constants.py index a6cdcd6b775159ca0841221908538d59a685fb7b..3dd8875975d03469304d88386097bfb59e5a1457 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -490,6 +490,8 @@ HV_USB_MOUSE = "usb_mouse" HV_DEVICE_MODEL = "device_model" HV_INIT_SCRIPT = "init_script" HV_MIGRATION_PORT = "migration_port" +HV_MIGRATION_BANDWIDTH = "migration_bandwidth" +HV_MIGRATION_DOWNTIME = "migration_downtime" HV_USE_LOCALTIME = "use_localtime" HV_DISK_CACHE = "disk_cache" HV_SECURITY_MODEL = "security_model" @@ -522,6 +524,8 @@ HVS_PARAMETER_TYPES = { HV_DEVICE_MODEL: VTYPE_STRING, HV_INIT_SCRIPT: VTYPE_STRING, HV_MIGRATION_PORT: VTYPE_INT, + HV_MIGRATION_BANDWIDTH: VTYPE_INT, + HV_MIGRATION_DOWNTIME: VTYPE_INT, HV_USE_LOCALTIME: VTYPE_BOOL, HV_DISK_CACHE: VTYPE_STRING, HV_SECURITY_MODEL: VTYPE_STRING, @@ -791,6 +795,8 @@ HVC_DEFAULTS = { HV_DISK_TYPE: HT_DISK_PARAVIRTUAL, HV_USB_MOUSE: '', HV_MIGRATION_PORT: 8102, + HV_MIGRATION_BANDWIDTH: 32, # MiB/s + HV_MIGRATION_DOWNTIME: 30, # ms HV_USE_LOCALTIME: False, HV_DISK_CACHE: HT_CACHE_DEFAULT, HV_SECURITY_MODEL: HT_SM_NONE, @@ -808,6 +814,7 @@ HVC_DEFAULTS = { HVC_GLOBALS = frozenset([ HV_MIGRATION_PORT, + HV_MIGRATION_BANDWIDTH, ]) BEC_DEFAULTS = { diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index a962a0b6d89bf5952cbb2a8032d92588e2232c9b..6edc5362cbb3492464b2dc5156a6d5091d7f004f 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -88,6 +88,8 @@ class KVMHypervisor(hv_base.BaseHypervisor): constants.HV_USB_MOUSE: hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES), constants.HV_MIGRATION_PORT: hv_base.NET_PORT_CHECK, + constants.HV_MIGRATION_BANDWIDTH: hv_base.NO_CHECK, + constants.HV_MIGRATION_DOWNTIME: hv_base.NO_CHECK, constants.HV_USE_LOCALTIME: hv_base.NO_CHECK, constants.HV_DISK_CACHE: hv_base.ParamInSet(True, constants.HT_VALID_CACHE_TYPES), @@ -854,6 +856,14 @@ class KVMHypervisor(hv_base.BaseHypervisor): if not live: self._CallMonitorCommand(instance_name, 'stop') + migrate_command = ('migrate_set_speed %dm' % + instance.hvparams[constants.HV_MIGRATION_BANDWIDTH]) + self._CallMonitorCommand(instance_name, migrate_command) + + migrate_command = ('migrate_set_downtime %dms' % + instance.hvparams[constants.HV_MIGRATION_DOWNTIME]) + self._CallMonitorCommand(instance_name, migrate_command) + migrate_command = 'migrate -d tcp:%s:%s' % (target, port) self._CallMonitorCommand(instance_name, migrate_command) diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py index a281e6a9b41d9434162a10847bfe010494b2e8f7..77f98b0798b4d43445ca50f17fcedd01d60ee7c9 100644 --- a/lib/hypervisor/hv_xen.py +++ b/lib/hypervisor/hv_xen.py @@ -213,6 +213,10 @@ class XenHypervisor(hv_base.BaseHypervisor): """ ini_info = self.GetInstanceInfo(instance.name) + if ini_info is None: + raise errors.HypervisorError("Failed to reboot instance %s," + " not running" % instance.name) + result = utils.RunCmd(["xm", "reboot", instance.name]) if result.failed: raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" % @@ -223,7 +227,8 @@ class XenHypervisor(hv_base.BaseHypervisor): new_info = self.GetInstanceInfo(instance.name) # check if the domain ID has changed or the run time has decreased - if new_info[1] != ini_info[1] or new_info[5] < ini_info[5]: + if (new_info is not None and + (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])): return raise utils.RetryAgain() diff --git a/lib/jqueue.py b/lib/jqueue.py index 62a5f4a5d0c13a6a4773a51ee48bbd918fa3992a..9e5d83663e04e35ef8432a9620e7f791c4f33417 100644 --- a/lib/jqueue.py +++ b/lib/jqueue.py @@ -77,11 +77,12 @@ class _QueuedOpCode(object): @ivar status: the current status @ivar result: the result of the LU execution @ivar start_timestamp: timestamp for the start of the execution + @ivar exec_timestamp: timestamp for the actual LU Exec() function invocation @ivar stop_timestamp: timestamp for the end of the execution """ __slots__ = ["input", "status", "result", "log", - "start_timestamp", "end_timestamp", + "start_timestamp", "exec_timestamp", "end_timestamp", "__weakref__"] def __init__(self, op): @@ -96,6 +97,7 @@ class _QueuedOpCode(object): self.result = None self.log = [] self.start_timestamp = None + self.exec_timestamp = None self.end_timestamp = None @classmethod @@ -114,6 +116,7 @@ class _QueuedOpCode(object): obj.result = state["result"] obj.log = state["log"] obj.start_timestamp = state.get("start_timestamp", None) + obj.exec_timestamp = state.get("exec_timestamp", None) obj.end_timestamp = state.get("end_timestamp", None) return obj @@ -130,6 +133,7 @@ class _QueuedOpCode(object): "result": self.result, "log": self.log, "start_timestamp": self.start_timestamp, + "exec_timestamp": self.exec_timestamp, "end_timestamp": self.end_timestamp, } @@ -385,6 +389,7 @@ class _OpExecCallbacks(mcpu.OpExecCbBase): raise CancelJob() self._op.status = constants.OP_STATUS_RUNNING + self._op.exec_timestamp = TimeStampNow() finally: self._queue.release() @@ -1383,6 +1388,8 @@ class JobQueue(object): row.append([op.log for op in job.ops]) elif fname == "opstart": row.append([op.start_timestamp for op in job.ops]) + elif fname == "opexec": + row.append([op.exec_timestamp for op in job.ops]) elif fname == "opend": row.append([op.end_timestamp for op in job.ops]) elif fname == "received_ts": diff --git a/lib/rapi/client.py b/lib/rapi/client.py index e1513aa9ac54d871a848dad3ded01d2e0b68b3e2..49affb2e6c7190d0f0c87d08a9c81d9858a2e79d 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -421,6 +421,8 @@ class GanetiRapiClient(object): raise CertificateError("SSL issue: %s (%r)" % (err, err)) except urllib2.HTTPError, err: raise GanetiApiError(str(err), code=err.code) + except urllib2.URLError, err: + raise GanetiApiError(str(err)) if encoded_response_content: response_content = simplejson.loads(encoded_response_content) diff --git a/lib/uidpool.py b/lib/uidpool.py index aba5febe605790275712e2ddee336b724e66ad15..9be7381cc90435d44c7b648a6fcd46d73fb46759 100644 --- a/lib/uidpool.py +++ b/lib/uidpool.py @@ -36,6 +36,7 @@ import random from ganeti import errors from ganeti import constants +from ganeti import compat from ganeti import utils @@ -114,16 +115,17 @@ def RemoveFromUidPool(uid_pool, remove_uids): uid_pool.remove(uid_range) -def _FormatUidRange(lower, higher): +def _FormatUidRange(lower, higher, roman=False): """Convert a user-id range definition into a string. """ if lower == higher: - return str(lower) - return "%s-%s" % (lower, higher) + return str(compat.TryToRoman(lower, convert=roman)) + return "%s-%s" % (compat.TryToRoman(lower, convert=roman), + compat.TryToRoman(higher, convert=roman)) -def FormatUidPool(uid_pool, separator=None): +def FormatUidPool(uid_pool, separator=None, roman=False): """Convert the internal representation of the user-id pool into a string. The output format is also accepted by ParseUidPool() @@ -136,7 +138,7 @@ def FormatUidPool(uid_pool, separator=None): """ if separator is None: separator = ", " - return separator.join([_FormatUidRange(lower, higher) + return separator.join([_FormatUidRange(lower, higher, roman=roman) for lower, higher in uid_pool]) diff --git a/lib/utils.py b/lib/utils.py index df14049d43732d5d3a5dec5c5d9cc4bf8029f866..11f20772297f1b189964a3f3319fe6bf4261f261 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -1699,6 +1699,11 @@ def EnsureDirs(dirs): if err.errno != errno.EEXIST: raise errors.GenericError("Cannot create needed directory" " '%s': %s" % (dir_name, err)) + try: + os.chmod(dir_name, dir_mode) + except EnvironmentError, err: + raise errors.GenericError("Cannot change directory permissions on" + " '%s': %s" % (dir_name, err)) if not os.path.isdir(dir_name): raise errors.GenericError("%s is not a directory" % dir_name) diff --git a/man/gnt-cluster.sgml b/man/gnt-cluster.sgml index d74c136d52f2aa970ba6085c07dceda101f2c71b..5044ae227f5fccadde6f024afd83c5b24238cb32 100644 --- a/man/gnt-cluster.sgml +++ b/man/gnt-cluster.sgml @@ -202,12 +202,20 @@ <cmdsynopsis> <command>info</command> + <arg>--roman</arg> </cmdsynopsis> <para> Shows runtime cluster information: cluster name, architecture (32 or 64 bit), master node, node list and instance list. </para> + + <para> + Passing the <option>--roman</option> option gnt-cluster info will try + to print its integer fields in a latin friendly way. This allows + further diffusion of Ganeti among ancient cultures. + </para> + </refsect2> <refsect2> @@ -419,6 +427,21 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term>migration_bandwidth</term> + <listitem> + <simpara>Valid for the KVM hypervisor.</simpara> + + <para> + This option specifies the maximum bandwidth that KVM will + use for instance live migrations. The value is in MiB/s. + </para> + + <simpara>This option is only effective with kvm versions >= 78 + and qemu-kvm versions >= 0.10.0. + </simpara> + </listitem> + </varlistentry> </variablelist> </para> diff --git a/man/gnt-instance.sgml b/man/gnt-instance.sgml index 3d3563fc4075d20817b5d194d0e9d719a5b3688c..9e10af9917858ce736df2593605185935e556a28 100644 --- a/man/gnt-instance.sgml +++ b/man/gnt-instance.sgml @@ -673,6 +673,23 @@ </para> <simpara>It is set to <quote>false</quote> by default.</simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term>migration_downtime</term> + <listitem> + <simpara>Valid for the KVM hypervisor.</simpara> + + <simpara>The maximum amount of time (in ms) a KVM instance is + allowed to be frozen during a live migration, in order to copy + dirty memory pages. Default value is 30ms, but you may need to + increase this value for busy instances. + </simpara> + + <simpara>This option is only effective with kvm versions >= 87 + and qemu-kvm versions >= 0.11.0. + </simpara> </listitem> </varlistentry> @@ -1016,6 +1033,7 @@ instance5: 11225 <arg>--no-headers</arg> <arg>--separator=<replaceable>SEPARATOR</replaceable></arg> <arg>-o <replaceable>[+]FIELD,...</replaceable></arg> + <arg>--roman</arg> <arg rep="repeat">instance</arg> </cmdsynopsis> @@ -1032,6 +1050,11 @@ instance5: 11225 the output fields. Both these options are to help scripting. </para> + <para> + The <option>--roman</option> option allows latin people to better + understand the cluster instances' status. + </para> + <para> The <option>-o</option> option takes a comma-separated list of output fields. The available fields and their meaning @@ -1390,6 +1413,7 @@ instance5: 11225 <arg>-s</arg> <arg>--static</arg> </group> + <arg>--roman</arg> <group choice="req"> <arg>--all</arg> <arg rep="repeat"><replaceable>instance</replaceable></arg> @@ -1413,6 +1437,13 @@ instance5: 11225 Use the <option>--all</option> to get info about all instances, rather than explicitly passing the ones you're interested in. </para> + + <para> + The <option>--roman</option> option can be used to cause envy among + people who like ancient cultures, but are stuck with non-latin-friendly + cluster virtualization technologies. + </para> + </refsect3> <refsect3> diff --git a/man/gnt-job.sgml b/man/gnt-job.sgml index 35d3c547ac23a2f49cbf959006fca2a6ca44a06e..df778d6c63449da8834739cdda8c99b6423bacb9 100644 --- a/man/gnt-job.sgml +++ b/man/gnt-job.sgml @@ -210,7 +210,15 @@ <varlistentry> <term>opstart</term> <listitem> - <simpara>the list of opcode start times</simpara> + <simpara>the list of opcode start times (before + acquiring locks)</simpara> + </listitem> + </varlistentry> + <varlistentry> + <term>opexec</term> + <listitem> + <simpara>the list of opcode execution start times (after + acquiring any necessary locks)</simpara> </listitem> </varlistentry> <varlistentry> diff --git a/man/gnt-node.sgml b/man/gnt-node.sgml index eb76deb4520a9a636fc73e48f11ce6de9db4a89a..22b89a048ef437f63bd78e1a6d54a4dbb99732b5 100644 --- a/man/gnt-node.sgml +++ b/man/gnt-node.sgml @@ -253,6 +253,8 @@ <arg>--units=<replaceable>UNITS</replaceable></arg> <arg>-o <replaceable>[+]FIELD,...</replaceable></arg> <sbr> + <arg>--roman</arg> + <sbr> <arg rep="repeat">node</arg> </cmdsynopsis> @@ -285,6 +287,12 @@ cluster (but this might stall the query for a long time). </para> + <para> + Passing the <option>--roman</option> option gnt-node list will try to + output some of its fields in a latin-friendly way. This is not the + default for backwards compatibility. + </para> + <para> The <option>-o</option> option takes a comma-separated list of output fields. The available fields and their meaning are: diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster index 4bde5b91f3affb1ce78b368e5499173d473c013c..bf160cdb0f0fd1c940be489c6bc8eb3591f16fcb 100755 --- a/scripts/gnt-cluster +++ b/scripts/gnt-cluster @@ -216,7 +216,7 @@ def ShowClusterMaster(opts, args): return 0 -def _PrintGroupedParams(paramsdict, level=1): +def _PrintGroupedParams(paramsdict, level=1, roman=False): """Print Grouped parameters (be, nic, disk) by group. @type paramsdict: dict of dicts @@ -229,7 +229,9 @@ def _PrintGroupedParams(paramsdict, level=1): for item, val in sorted(paramsdict.items()): if isinstance(val, dict): ToStdout("%s- %s:", indent, item) - _PrintGroupedParams(val, level=level + 1) + _PrintGroupedParams(val, level=level + 1, roman=roman) + elif roman and isinstance(val, int): + ToStdout("%s %s: %s", indent, item, compat.TryToRoman(val)) else: ToStdout("%s %s: %s", indent, item, val) @@ -276,19 +278,23 @@ def ShowClusterConfig(opts, args): _PrintGroupedParams(result["os_hvp"]) ToStdout("Cluster parameters:") - ToStdout(" - candidate pool size: %s", result["candidate_pool_size"]) + ToStdout(" - candidate pool size: %s", + compat.TryToRoman(result["candidate_pool_size"], + convert=opts.roman_integers)) ToStdout(" - master netdev: %s", result["master_netdev"]) ToStdout(" - lvm volume group: %s", result["volume_group_name"]) ToStdout(" - file storage path: %s", result["file_storage_dir"]) ToStdout(" - maintenance of node health: %s", result["maintain_node_health"]) - ToStdout(" - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"])) + ToStdout(" - uid pool: %s", + uidpool.FormatUidPool(result["uid_pool"], + roman=opts.roman_integers)) ToStdout("Default instance parameters:") - _PrintGroupedParams(result["beparams"]) + _PrintGroupedParams(result["beparams"], roman=opts.roman_integers) ToStdout("Default nic parameters:") - _PrintGroupedParams(result["nicparams"]) + _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers) return 0 @@ -817,8 +823,8 @@ commands = { [NODE_LIST_OPT], "[-n node...] <command>", "Runs a command on all (or only some) nodes"), 'info': ( - ShowClusterConfig, ARGS_NONE, [], - "", "Show cluster configuration"), + ShowClusterConfig, ARGS_NONE, [ROMAN_OPT], + "[--roman]", "Show cluster configuration"), 'list-tags': ( ListTags, ARGS_NONE, [], "", "List the tags of the cluster"), 'add-tags': ( diff --git a/scripts/gnt-instance b/scripts/gnt-instance index 250b11f4c6d864bc6ba350221618ca9790635524..710fb4dab635fcd304214134a720302b8c293e9a 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -34,6 +34,7 @@ from cStringIO import StringIO from ganeti.cli import * from ganeti import opcodes from ganeti import constants +from ganeti import compat from ganeti import utils from ganeti import errors @@ -323,6 +324,8 @@ def ListInstances(opts, args): val = ",".join(str(item) for item in val) elif val is None: val = "-" + if opts.roman_integers and isinstance(val, int): + val = compat.TryToRoman(val) row[idx] = str(val) data = GenerateTable(separator=opts.separator, headers=headers, @@ -941,16 +944,18 @@ def ConnectToInstanceConsole(opts, args): os._exit(1) # pylint: disable-msg=W0212 -def _FormatLogicalID(dev_type, logical_id): +def _FormatLogicalID(dev_type, logical_id, roman): """Formats the logical_id of a disk. """ if dev_type == constants.LD_DRBD8: node_a, node_b, port, minor_a, minor_b, key = logical_id data = [ - ("nodeA", "%s, minor=%s" % (node_a, minor_a)), - ("nodeB", "%s, minor=%s" % (node_b, minor_b)), - ("port", port), + ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a, + convert=roman))), + ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b, + convert=roman))), + ("port", compat.TryToRoman(port, convert=roman)), ("auth key", key), ] elif dev_type == constants.LD_LV: @@ -962,7 +967,7 @@ def _FormatLogicalID(dev_type, logical_id): return data -def _FormatBlockDevInfo(idx, top_level, dev, static): +def _FormatBlockDevInfo(idx, top_level, dev, static, roman): """Show block device information. This is only used by L{ShowInstanceConfig}, but it's too big to be @@ -977,6 +982,8 @@ def _FormatBlockDevInfo(idx, top_level, dev, static): @type static: boolean @param static: wheter the device information doesn't contain runtime information but only static data + @type roman: boolean + @param roman: whether to try to use roman integers @return: a list of either strings, tuples or lists (which should be formatted at a higher indent level) @@ -998,19 +1005,19 @@ def _FormatBlockDevInfo(idx, top_level, dev, static): if major is None: major_string = "N/A" else: - major_string = str(major) + major_string = str(compat.TryToRoman(major, convert=roman)) if minor is None: minor_string = "N/A" else: - minor_string = str(minor) + minor_string = str(compat.TryToRoman(minor, convert=roman)) txt += ("%s (%s:%s)" % (path, major_string, minor_string)) if dtype in (constants.LD_DRBD8, ): if syncp is not None: sync_text = "*RECOVERING* %5.2f%%," % syncp if estt: - sync_text += " ETA %ds" % estt + sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman) else: sync_text += " ETA unknown" else: @@ -1039,9 +1046,9 @@ def _FormatBlockDevInfo(idx, top_level, dev, static): if dev["iv_name"] is not None: txt = dev["iv_name"] else: - txt = "disk %d" % idx + txt = "disk %s" % compat.TryToRoman(idx, convert=roman) else: - txt = "child %d" % idx + txt = "child %s" % compat.TryToRoman(idx, convert=roman) if isinstance(dev["size"], int): nice_size = utils.FormatUnit(dev["size"], "h") else: @@ -1052,7 +1059,7 @@ def _FormatBlockDevInfo(idx, top_level, dev, static): data.append(("access mode", dev["mode"])) if dev["logical_id"] is not None: try: - l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"]) + l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman) except ValueError: l_id = [str(dev["logical_id"])] if len(l_id) == 1: @@ -1070,7 +1077,7 @@ def _FormatBlockDevInfo(idx, top_level, dev, static): if dev["children"]: data.append("child devices:") for c_idx, child in enumerate(dev["children"]): - data.append(_FormatBlockDevInfo(c_idx, False, child, static)) + data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman)) d1.append(data) return d1 @@ -1137,7 +1144,9 @@ def ShowInstanceConfig(opts, args): instance = result[instance_name] buf.write("Instance name: %s\n" % instance["name"]) buf.write("UUID: %s\n" % instance["uuid"]) - buf.write("Serial number: %s\n" % instance["serial_no"]) + buf.write("Serial number: %s\n" % + compat.TryToRoman(instance["serial_no"], + convert=opts.roman_integers)) buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"])) buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"])) buf.write("State: configured to be %s" % instance["config_state"]) @@ -1151,7 +1160,9 @@ def ShowInstanceConfig(opts, args): buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"])) buf.write(" Operating system: %s\n" % instance["os"]) if instance.has_key("network_port"): - buf.write(" Allocated network port: %s\n" % instance["network_port"]) + buf.write(" Allocated network port: %s\n" % + compat.TryToRoman(instance["network_port"], + convert=opts.roman_integers)) buf.write(" Hypervisor: %s\n" % instance["hypervisor"]) # custom VNC console information @@ -1181,10 +1192,12 @@ def ShowInstanceConfig(opts, args): val = "default (%s)" % instance["hv_actual"][key] buf.write(" - %s: %s\n" % (key, val)) buf.write(" Hardware:\n") - buf.write(" - VCPUs: %d\n" % - instance["be_actual"][constants.BE_VCPUS]) - buf.write(" - memory: %dMiB\n" % - instance["be_actual"][constants.BE_MEMORY]) + buf.write(" - VCPUs: %s\n" % + compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS], + convert=opts.roman_integers)) + buf.write(" - memory: %sMiB\n" % + compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY], + convert=opts.roman_integers)) buf.write(" - NICs:\n") for idx, (ip, mac, mode, link) in enumerate(instance["nics"]): buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" % @@ -1192,7 +1205,8 @@ def ShowInstanceConfig(opts, args): buf.write(" Disks:\n") for idx, device in enumerate(instance["disks"]): - _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2) + _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static, + opts.roman_integers), 2) ToStdout(buf.getvalue().rstrip('\n')) return retcode @@ -1379,12 +1393,12 @@ commands = { " (only for instances of type file and lv)"), 'info': ( ShowInstanceConfig, ARGS_MANY_INSTANCES, - [STATIC_OPT, ALL_OPT], + [STATIC_OPT, ALL_OPT, ROMAN_OPT], "[-s] {--all | <instance>...}", "Show information on the specified instance(s)"), 'list': ( ListInstances, ARGS_MANY_INSTANCES, - [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT], + [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT], "[<instance>...]", "Lists the instances and their status. The available fields are" " (see the man page for details): status, oper_state, oper_ram," diff --git a/scripts/gnt-job b/scripts/gnt-job index 2e63cf6de2c91eebc74199a2eeaeae8f6b48f4cf..b2b9b11d6eebf8711f18716fc7b613c4a8079ff5 100755 --- a/scripts/gnt-job +++ b/scripts/gnt-job @@ -80,6 +80,7 @@ def ListJobs(opts, args): "oplog": "OpCode_log", "summary": "Summary", "opstart": "OpCode_start", + "opexec": "OpCode_exec", "opend": "OpCode_end", "start_ts": "Start", "end_ts": "End", @@ -106,7 +107,7 @@ def ListJobs(opts, args): val = ",".join(val) elif field in ("start_ts", "end_ts", "received_ts"): val = FormatTimestamp(val) - elif field in ("opstart", "opend"): + elif field in ("opstart", "opexec", "opend"): val = [FormatTimestamp(entry) for entry in val] elif field == "lock_status" and not val: val = "-" @@ -212,7 +213,7 @@ def ShowJobs(opts, args): selected_fields = [ "id", "status", "ops", "opresult", "opstatus", "oplog", - "opstart", "opend", "received_ts", "start_ts", "end_ts", + "opstart", "opexec", "opend", "received_ts", "start_ts", "end_ts", ] result = GetClient().QueryJobs(args, selected_fields) @@ -235,7 +236,7 @@ def ShowJobs(opts, args): continue (job_id, status, ops, opresult, opstatus, oplog, - opstart, opend, recv_ts, start_ts, end_ts) = entry + opstart, opexec, opend, recv_ts, start_ts, end_ts) = entry format(0, "Job ID: %s" % job_id) if status in _USER_JOB_STATUS: status = _USER_JOB_STATUS[status] @@ -275,14 +276,18 @@ def ShowJobs(opts, args): else: format(1, "Total processing time: N/A") format(1, "Opcodes:") - for (opcode, result, status, log, s_ts, e_ts) in \ - zip(ops, opresult, opstatus, oplog, opstart, opend): + for (opcode, result, status, log, s_ts, x_ts, e_ts) in \ + zip(ops, opresult, opstatus, oplog, opstart, opexec, opend): format(2, "%s" % opcode["OP_ID"]) format(3, "Status: %s" % status) if isinstance(s_ts, (tuple, list)): format(3, "Processing start: %s" % FormatTimestamp(s_ts)) else: format(3, "No processing start time") + if isinstance(x_ts, (tuple, list)): + format(3, "Execution start: %s" % FormatTimestamp(x_ts)) + else: + format(3, "No execution start time") if isinstance(e_ts, (tuple, list)): format(3, "Processing end: %s" % FormatTimestamp(e_ts)) else: diff --git a/scripts/gnt-node b/scripts/gnt-node index 61c4f271488fd78b93495a6b5f8ed5eaf9371099..9e96248ee385888990c4d7d1838a1f04b690d34d 100755 --- a/scripts/gnt-node +++ b/scripts/gnt-node @@ -32,6 +32,7 @@ from ganeti.cli import * from ganeti import opcodes from ganeti import utils from ganeti import constants +from ganeti import compat from ganeti import errors from ganeti import bootstrap @@ -223,6 +224,8 @@ def ListNodes(opts, args): val = utils.FormatTime(val) elif val is None: val = "?" + elif opts.roman_integers and isinstance(val, int): + val = compat.TryToRoman(val) row[idx] = str(val) data = GenerateTable(separator=opts.separator, headers=headers, @@ -662,7 +665,7 @@ commands = { "[<node_name>...]", "Show information about the node(s)"), 'list': ( ListNodes, ARGS_MANY_NODES, - [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT], + [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT], "[nodes...]", "Lists the nodes in the cluster. The available fields are (see the man" " page for details): %s. The default field list is (in order): %s." % diff --git a/test/ganeti.compat_unittest.py b/test/ganeti.compat_unittest.py index 19f82b7a8f26b8d225aea2c3dc8364afcc630719..dbe9940e71403666d44c1d7f94f216a50b01172b 100755 --- a/test/ganeti.compat_unittest.py +++ b/test/ganeti.compat_unittest.py @@ -58,5 +58,26 @@ class TestPartial(testutils.GanetiTestCase): (("Foo", ), {"xyz": 999,})) +class TestTryToRoman(testutils.GanetiTestCase): + """test the compat.TryToRoman function""" + + def testAFewIntegers(self): + self.assertEquals(compat.TryToRoman(0), 0) + self.assertEquals(compat.TryToRoman(1), "I") + self.assertEquals(compat.TryToRoman(4), "IV") + self.assertEquals(compat.TryToRoman(5), "V") + + def testStrings(self): + self.assertEquals(compat.TryToRoman("astring"), "astring") + self.assertEquals(compat.TryToRoman("5"), "5") + + def testDontConvert(self): + self.assertEquals(compat.TryToRoman(0, convert=False), 0) + self.assertEquals(compat.TryToRoman(1, convert=False), 1) + self.assertEquals(compat.TryToRoman(7, convert=False), 7) + self.assertEquals(compat.TryToRoman("astring", convert=False), "astring") + self.assertEquals(compat.TryToRoman("19", convert=False), "19") + + if __name__ == "__main__": testutils.GanetiTestProgram() diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index b329ad7214f766e774041c812d343b35dcc25198..dee83b0d84764a9cd307a523c0430faec5e76813 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -2245,5 +2245,26 @@ class TestIgnoreSignals(unittest.TestCase): self.assertEquals(utils.IgnoreSignals(self._Return, 33), 33) +class TestEnsureDirs(unittest.TestCase): + """Tests for EnsureDirs""" + + def setUp(self): + self.dir = tempfile.mkdtemp() + self.old_umask = os.umask(0777) + + def testEnsureDirs(self): + utils.EnsureDirs([ + (utils.PathJoin(self.dir, "foo"), 0777), + (utils.PathJoin(self.dir, "bar"), 0000), + ]) + self.assertEquals(os.stat(utils.PathJoin(self.dir, "foo"))[0] & 0777, 0777) + self.assertEquals(os.stat(utils.PathJoin(self.dir, "bar"))[0] & 0777, 0000) + + def tearDown(self): + os.rmdir(utils.PathJoin(self.dir, "foo")) + os.rmdir(utils.PathJoin(self.dir, "bar")) + os.rmdir(self.dir) + os.umask(self.old_umask) + if __name__ == '__main__': testutils.GanetiTestProgram()