Commit dcb93971 authored by Michael Hanselmann's avatar Michael Hanselmann

- Implement “gnt-node volumes”

- Create all --output options using a constant
- Put node checking code from opcodes into a single function
- Do the same for output fields

Reviewed-by: iustinp
parent 880478f8
......@@ -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):
......
......@@ -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
......
......@@ -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)
......
......@@ -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):
......
......@@ -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,
......
......@@ -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):
......
......@@ -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()
......@@ -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;
......
......@@ -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"),
......
......@@ -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)"),
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment