From dcb939711bb4ed751a71f522e41c9af986bfe96e Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Tue, 24 Jul 2007 11:45:11 +0000 Subject: [PATCH] =?UTF-8?q?-=20Implement=20=E2=80=9Cgnt-node=20volumes?= =?UTF-8?q?=E2=80=9D=20-=20Create=20all=20--output=20options=20using=20a?= =?UTF-8?q?=20constant=20-=20Put=20node=20checking=20code=20from=20opcodes?= =?UTF-8?q?=20into=20a=20single=20function=20-=20Do=20the=20same=20for=20o?= =?UTF-8?q?utput=20fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: iustinp --- daemons/ganeti-noded | 3 + lib/backend.py | 29 +++++++ lib/cli.py | 6 +- lib/cmdlib.py | 178 +++++++++++++++++++++++++++---------------- lib/mcpu.py | 1 + lib/opcodes.py | 6 ++ lib/rpc.py | 12 +++ man/gnt-node.sgml | 24 ++++++ scripts/gnt-instance | 6 +- scripts/gnt-node | 69 +++++++++++++++-- 10 files changed, 259 insertions(+), 75 deletions(-) diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded index 9c09140ce..60b77049f 100755 --- a/daemons/ganeti-noded +++ b/daemons/ganeti-noded @@ -235,6 +235,9 @@ class ServerObject(pb.Avatar): def perspective_node_leave_cluster(self, params): return backend.LeaveCluster() + def perspective_node_volumes(self, params): + return backend.NodeVolumes() + # cluster -------------------------- def perspective_version(self,params): diff --git a/lib/backend.py b/lib/backend.py index d57f3eb87..3064f21e3 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -260,6 +260,35 @@ def ListVolumeGroups(): return utils.ListVolumeGroups() +def NodeVolumes(): + """List all volumes on this node. + + """ + result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix", + "--separator=|", + "--options=lv_name,lv_size,devices,vg_name"]) + if result.failed: + logger.Error("Failed to list logical volumes, lvs output: %s" % + result.output) + return {} + + def parse_dev(dev): + if '(' in dev: + return dev.split('(')[0] + else: + return dev + + def map_line(line): + return { + 'name': line[0].strip(), + 'size': line[1].strip(), + 'dev': parse_dev(line[2].strip()), + 'vg': line[3].strip(), + } + + return [map_line(line.split('|')) for line in result.output.splitlines()] + + def BridgesExist(bridges_list): """Check if a list of bridges exist on the current node diff --git a/lib/cli.py b/lib/cli.py index 56ec477c6..5508fbdcf 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -39,7 +39,7 @@ from optparse import (OptionParser, make_option, TitledHelpFormatter, __all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode", "cli_option", "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE", - "USEUNITS_OPT"] + "USEUNITS_OPT", "FIELDS_OPT"] DEBUG_OPT = make_option("-d", "--debug", default=False, action="store_true", @@ -58,6 +58,10 @@ USEUNITS_OPT = make_option("--human-readable", default=False, action="store_true", dest="human_readable", help="Print sizes in human readable format") +FIELDS_OPT = make_option("-o", "--output", dest="output", action="store", + type="string", help="Select output fields", + metavar="FIELDS") + _LOCK_OPT = make_option("--lock-retries", default=None, type="int", help=SUPPRESS_HELP) diff --git a/lib/cmdlib.py b/lib/cmdlib.py index e7accebb3..a4beb2db0 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -164,6 +164,36 @@ class NoHooksLU(LogicalUnit): return +def _GetWantedNodes(lu, nodes): + if nodes is not None and not isinstance(nodes, list): + raise errors.OpPrereqError, "Invalid argument type 'nodes'" + + if nodes: + wanted_nodes = [] + + for name in nodes: + node = lu.cfg.GetNodeInfo(lu.cfg.ExpandNodeName(name)) + if node is None: + raise errors.OpPrereqError, ("No such node name '%s'" % name) + wanted_nodes.append(node) + + return wanted_nodes + else: + return [lu.cfg.GetNodeInfo(name) for name in lu.cfg.GetNodeList()] + + +def _CheckOutputFields(static, dynamic, selected): + static_fields = frozenset(static) + dynamic_fields = frozenset(dynamic) + + all_fields = static_fields | dynamic_fields + + if not all_fields.issuperset(selected): + raise errors.OpPrereqError, ("Unknown output fields selected: %s" + % ",".join(frozenset(selected). + difference(all_fields))) + + def _UpdateEtcHosts(fullnode, ip): """Ensure a node has a correct entry in /etc/hosts. @@ -1028,15 +1058,12 @@ class LUQueryNodes(NoHooksLU): This checks that the fields required are valid output fields. """ - self.static_fields = frozenset(["name", "pinst", "sinst", "pip", "sip"]) self.dynamic_fields = frozenset(["dtotal", "dfree", "mtotal", "mnode", "mfree"]) - self.all_fields = self.static_fields | self.dynamic_fields - if not self.all_fields.issuperset(self.op.output_fields): - raise errors.OpPrereqError, ("Unknown output fields selected: %s" - % ",".join(frozenset(self.op.output_fields). - difference(self.all_fields))) + _CheckOutputFields(static=["name", "pinst", "sinst", "pip", "sip"], + dynamic=self.dynamic_fields, + selected=self.op.output_fields) def Exec(self, feedback_fn): @@ -1106,6 +1133,73 @@ class LUQueryNodes(NoHooksLU): return output +class LUQueryNodeVolumes(NoHooksLU): + """Logical unit for getting volumes on node(s). + + """ + _OP_REQP = ["nodes", "output_fields"] + + def CheckPrereq(self): + """Check prerequisites. + + This checks that the fields required are valid output fields. + + """ + self.nodes = _GetWantedNodes(self, self.op.nodes) + + _CheckOutputFields(static=["node"], + dynamic=["phys", "vg", "name", "size", "instance"], + selected=self.op.output_fields) + + + def Exec(self, feedback_fn): + """Computes the list of nodes and their attributes. + + """ + nodenames = utils.NiceSort([node.name for node in self.nodes]) + volumes = rpc.call_node_volumes(nodenames) + + ilist = [self.cfg.GetInstanceInfo(iname) for iname + in self.cfg.GetInstanceList()] + + lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist]) + + output = [] + for node in nodenames: + node_vols = volumes[node][:] + node_vols.sort(key=lambda vol: vol['dev']) + + for vol in node_vols: + node_output = [] + for field in self.op.output_fields: + if field == "node": + val = node + elif field == "phys": + val = vol['dev'] + elif field == "vg": + val = vol['vg'] + elif field == "name": + val = vol['name'] + elif field == "size": + val = int(float(vol['size'])) + elif field == "instance": + for inst in ilist: + if node not in lv_by_node[inst]: + continue + if vol['name'] in lv_by_node[inst][node]: + val = inst.name + break + else: + val = '-' + else: + raise errors.ParameterError, field + node_output.append(str(val)) + + output.append(node_output) + + return output + + def _CheckNodesDirs(node_list, paths): """Verify if the given nodes have the same files. @@ -1477,16 +1571,8 @@ class LUClusterCopyFile(NoHooksLU): """ if not os.path.exists(self.op.filename): raise errors.OpPrereqError("No such filename '%s'" % self.op.filename) - if self.op.nodes: - nodes = self.op.nodes - else: - nodes = self.cfg.GetNodeList() - self.nodes = [] - for node in nodes: - nname = self.cfg.ExpandNodeName(node) - if nname is None: - raise errors.OpPrereqError, ("Node '%s' is unknown." % node) - self.nodes.append(nname) + + self.nodes = _GetWantedNodes(self, self.op.nodes) def Exec(self, feedback_fn): """Copy a file from master to some nodes. @@ -1540,16 +1626,7 @@ class LURunClusterCommand(NoHooksLU): It checks that the given list of nodes is valid. """ - if self.op.nodes: - nodes = self.op.nodes - else: - nodes = self.cfg.GetNodeList() - self.nodes = [] - for node in nodes: - nname = self.cfg.ExpandNodeName(node) - if nname is None: - raise errors.OpPrereqError, ("Node '%s' is unknown." % node) - self.nodes.append(nname) + self.nodes = _GetWantedNodes(self, self.op.nodes) def Exec(self, feedback_fn): """Run a command on some nodes. @@ -1557,8 +1634,8 @@ class LURunClusterCommand(NoHooksLU): """ data = [] for node in self.nodes: - result = utils.RunCmd(["ssh", node, self.op.command]) - data.append((node, result.cmd, result.output, result.exit_code)) + result = utils.RunCmd(["ssh", node.name, self.op.command]) + data.append((node.name, result.cmd, result.output, result.exit_code)) return data @@ -1884,7 +1961,7 @@ class LUQueryInstances(NoHooksLU): """Logical unit for querying instances. """ - OP_REQP = ["output_fields"] + _OP_REQP = ["output_fields"] def CheckPrereq(self): """Check prerequisites. @@ -1892,17 +1969,12 @@ class LUQueryInstances(NoHooksLU): This checks that the fields required are valid output fields. """ - - self.static_fields = frozenset(["name", "os", "pnode", "snodes", - "admin_state", "admin_ram", - "disk_template", "ip", "mac", "bridge"]) self.dynamic_fields = frozenset(["oper_state", "oper_ram"]) - self.all_fields = self.static_fields | self.dynamic_fields - - if not self.all_fields.issuperset(self.op.output_fields): - raise errors.OpPrereqError, ("Unknown output fields selected: %s" - % ",".join(frozenset(self.op.output_fields). - difference(self.all_fields))) + _CheckOutputFields(static=["name", "os", "pnode", "snodes", + "admin_state", "admin_ram", + "disk_template", "ip", "mac", "bridge"], + dynamic=self.dynamic_fields, + selected=self.op.output_fields) def Exec(self, feedback_fn): """Computes the list of nodes and their attributes. @@ -3074,20 +3146,7 @@ class LUQueryNodeData(NoHooksLU): This only checks the optional node list against the existing names. """ - if not isinstance(self.op.nodes, list): - raise errors.OpPrereqError, "Invalid argument type 'nodes'" - if self.op.nodes: - self.wanted_nodes = [] - names = self.op.nodes - for name in names: - node = self.cfg.GetNodeInfo(self.cfg.ExpandNodeName(name)) - if node is None: - raise errors.OpPrereqError, ("No such node name '%s'" % name) - self.wanted_nodes.append(node) - else: - self.wanted_nodes = [self.cfg.GetNodeInfo(name) for name - in self.cfg.GetNodeList()] - return + self.wanted_nodes = _GetWantedNodes(self, self.op.nodes) def Exec(self, feedback_fn): """Compute and return the list of nodes. @@ -3214,18 +3273,9 @@ class LUQueryExports(NoHooksLU): """Check that the nodelist contains only existing nodes. """ - nodes = getattr(self.op, "nodes", None) - if not nodes: - self.op.nodes = self.cfg.GetNodeList() - else: - expnodes = [self.cfg.ExpandNodeName(node) for node in nodes] - if expnodes.count(None) > 0: - raise errors.OpPrereqError, ("At least one of the given nodes %s" - " is unknown" % self.op.nodes) - self.op.nodes = expnodes + self.nodes = _GetWantedNodes(self, getattr(self.op, "nodes", None)) def Exec(self, feedback_fn): - """Compute the list of all the exported system images. Returns: @@ -3234,7 +3284,7 @@ class LUQueryExports(NoHooksLU): that node. """ - return rpc.call_export_list(self.op.nodes) + return rpc.call_export_list([node.name for node in self.nodes]) class LUExportInstance(LogicalUnit): diff --git a/lib/mcpu.py b/lib/mcpu.py index f67204044..daf79236f 100644 --- a/lib/mcpu.py +++ b/lib/mcpu.py @@ -59,6 +59,7 @@ class Processor(object): opcodes.OpAddNode: cmdlib.LUAddNode, opcodes.OpQueryNodes: cmdlib.LUQueryNodes, opcodes.OpQueryNodeData: cmdlib.LUQueryNodeData, + opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes, opcodes.OpRemoveNode: cmdlib.LURemoveNode, # instance lu opcodes.OpCreateInstance: cmdlib.LUCreateInstance, diff --git a/lib/opcodes.py b/lib/opcodes.py index 45bbf1e06..a54bb4320 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -122,6 +122,12 @@ class OpQueryNodeData(OpCode): __slots__ = ["nodes"] +class OpQueryNodeVolumes(OpCode): + """Get list of volumes on node.""" + OP_ID = "OP_NODE_QUERYVOLS" + __slots__ = ["nodes", "output_fields"] + + # instance opcodes class OpCreateInstance(OpCode): diff --git a/lib/rpc.py b/lib/rpc.py index ddb10ae8b..ce4d7872f 100644 --- a/lib/rpc.py +++ b/lib/rpc.py @@ -762,3 +762,15 @@ def call_node_leave_cluster(node): c.connect(node) c.run() return c.getresult().get(node, False) + + +def call_node_volumes(node_list): + """Gets all volumes on node(s). + + This is a multi-node call. + + """ + c = Client("node_volumes", []) + c.connect_list(node_list) + c.run() + return c.getresult() diff --git a/man/gnt-node.sgml b/man/gnt-node.sgml index f0d01196f..7341f1338 100644 --- a/man/gnt-node.sgml +++ b/man/gnt-node.sgml @@ -261,6 +261,30 @@ </para> </refsect2> + <refsect2> + <title>VOLUMES</title> + + <cmdsynopsis> + <command>volumes</command> + <arg rep="repeat"><replaceable>node</replaceable></arg> + </cmdsynopsis> + + <para> + Lists all logical volumes and their physical disks from the node(s) + provided. + </para> + + <para> + Example: + <screen> +# gnt-node volumes node5.example.com +Node PhysDev VG Name Size Instance +node1.example.com /dev/hdc1 xenvg instance1.example.com-sda_11000.meta 128 instance1.example.com +node1.example.com /dev/hdc1 xenvg instance1.example.com-sda_11001.data 256 instance1.example.com + </screen> + </para> + </refsect2> + </refsect1> &footer; diff --git a/scripts/gnt-instance b/scripts/gnt-instance index a9fc2f814..45f679588 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -489,11 +489,7 @@ commands = { 'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]", "Show information on the specified instance"), 'list': (ListInstances, ARGS_NONE, - [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, - make_option("-o", "--output", dest="output", action="store", - type="string", help="Select output fields", - metavar="FIELDS") - ], + [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "", "Lists the instances and their status"), 'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, force_opt], "[-f] <instance>", "Shuts down the instance and removes it"), diff --git a/scripts/gnt-node b/scripts/gnt-node index 88e8c2915..7593f8965 100755 --- a/scripts/gnt-node +++ b/scripts/gnt-node @@ -130,6 +130,66 @@ def RemoveNode(opts, args): SubmitOpCode(op) +def ListVolumes(opts, args): + """List logical volumes on node(s). + + """ + if opts.output is None: + selected_fields = ["node", "phys", "vg", + "name", "size", "instance"] + else: + selected_fields = opts.output.split(",") + + op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields) + output = SubmitOpCode(op) + + mlens = [0 for name in selected_fields] + format_fields = [] + unitformat_fields = ("size",) + for field in selected_fields: + if field in unitformat_fields: + format_fields.append("%*s") + else: + format_fields.append("%-*s") + + separator = opts.separator + if "%" in separator: + separator = separator.replace("%", "%%") + format = separator.join(format_fields) + + for row in output: + for idx, val in enumerate(row): + if opts.human_readable and selected_fields[idx] in unitformat_fields: + try: + val = int(val) + except ValueError: + pass + else: + val = row[idx] = utils.FormatUnit(val) + mlens[idx] = max(mlens[idx], len(val)) + + if not opts.no_headers: + header_list = {"node": "Node", "phys": "PhysDev", + "vg": "VG", "name": "Name", + "size": "Size", "instance": "Instance"} + args = [] + for idx, name in enumerate(selected_fields): + hdr = header_list[name] + mlens[idx] = max(mlens[idx], len(hdr)) + args.append(mlens[idx]) + args.append(hdr) + logger.ToStdout(format % tuple(args)) + + for row in output: + args = [] + for idx, val in enumerate(row): + args.append(mlens[idx]) + args.append(val) + logger.ToStdout(format % tuple(args)) + + return 0 + + commands = { 'add': (AddNode, ARGS_ONE, [DEBUG_OPT, @@ -140,14 +200,13 @@ commands = { 'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT], "[<node_name>...]", "Show information about the node(s)"), 'list': (ListNodes, ARGS_NONE, - [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, - make_option("-o", "--output", dest="output", action="store", - type="string", help="Select output fields", - metavar="FIELDS") - ], + [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "", "Lists the nodes in the cluster"), 'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT], "<node_name>", "Removes a node from the cluster"), + 'volumes': (ListVolumes, ARGS_ANY, + [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], + "[<node_name>...]", "List logical volumes on node(s)"), } -- GitLab