Revision dcb93971

b/daemons/ganeti-noded
235 235
  def perspective_node_leave_cluster(self, params):
236 236
    return backend.LeaveCluster()
237 237

  
238
  def perspective_node_volumes(self, params):
239
    return backend.NodeVolumes()
240

  
238 241
  # cluster --------------------------
239 242

  
240 243
  def perspective_version(self,params):
b/lib/backend.py
260 260
  return utils.ListVolumeGroups()
261 261

  
262 262

  
263
def NodeVolumes():
264
  """List all volumes on this node.
265

  
266
  """
267
  result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
268
                         "--separator=|",
269
                         "--options=lv_name,lv_size,devices,vg_name"])
270
  if result.failed:
271
    logger.Error("Failed to list logical volumes, lvs output: %s" %
272
                 result.output)
273
    return {}
274

  
275
  def parse_dev(dev):
276
    if '(' in dev:
277
      return dev.split('(')[0]
278
    else:
279
      return dev
280

  
281
  def map_line(line):
282
    return {
283
      'name': line[0].strip(),
284
      'size': line[1].strip(),
285
      'dev': parse_dev(line[2].strip()),
286
      'vg': line[3].strip(),
287
    }
288

  
289
  return [map_line(line.split('|')) for line in result.output.splitlines()]
290

  
291

  
263 292
def BridgesExist(bridges_list):
264 293
  """Check if a list of bridges exist on the current node
265 294

  
b/lib/cli.py
39 39
__all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode",
40 40
           "cli_option",
41 41
           "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
42
           "USEUNITS_OPT"]
42
           "USEUNITS_OPT", "FIELDS_OPT"]
43 43

  
44 44
DEBUG_OPT = make_option("-d", "--debug", default=False,
45 45
                        action="store_true",
......
58 58
                           action="store_true", dest="human_readable",
59 59
                           help="Print sizes in human readable format")
60 60

  
61
FIELDS_OPT = make_option("-o", "--output", dest="output", action="store",
62
                         type="string", help="Select output fields",
63
                         metavar="FIELDS")
64

  
61 65
_LOCK_OPT = make_option("--lock-retries", default=None,
62 66
                        type="int", help=SUPPRESS_HELP)
63 67

  
b/lib/cmdlib.py
164 164
    return
165 165

  
166 166

  
167
def _GetWantedNodes(lu, nodes):
168
  if nodes is not None and not isinstance(nodes, list):
169
    raise errors.OpPrereqError, "Invalid argument type 'nodes'"
170

  
171
  if nodes:
172
    wanted_nodes = []
173

  
174
    for name in nodes:
175
      node = lu.cfg.GetNodeInfo(lu.cfg.ExpandNodeName(name))
176
      if node is None:
177
        raise errors.OpPrereqError, ("No such node name '%s'" % name)
178
    wanted_nodes.append(node)
179

  
180
    return wanted_nodes
181
  else:
182
    return [lu.cfg.GetNodeInfo(name) for name in lu.cfg.GetNodeList()]
183

  
184

  
185
def _CheckOutputFields(static, dynamic, selected):
186
    static_fields = frozenset(static)
187
    dynamic_fields = frozenset(dynamic)
188

  
189
    all_fields = static_fields | dynamic_fields
190

  
191
    if not all_fields.issuperset(selected):
192
      raise errors.OpPrereqError, ("Unknown output fields selected: %s"
193
                                   % ",".join(frozenset(selected).
194
                                              difference(all_fields)))
195

  
196

  
167 197
def _UpdateEtcHosts(fullnode, ip):
168 198
  """Ensure a node has a correct entry in /etc/hosts.
169 199

  
......
1028 1058
    This checks that the fields required are valid output fields.
1029 1059

  
1030 1060
    """
1031
    self.static_fields = frozenset(["name", "pinst", "sinst", "pip", "sip"])
1032 1061
    self.dynamic_fields = frozenset(["dtotal", "dfree",
1033 1062
                                     "mtotal", "mnode", "mfree"])
1034
    self.all_fields = self.static_fields | self.dynamic_fields
1035 1063

  
1036
    if not self.all_fields.issuperset(self.op.output_fields):
1037
      raise errors.OpPrereqError, ("Unknown output fields selected: %s"
1038
                                   % ",".join(frozenset(self.op.output_fields).
1039
                                              difference(self.all_fields)))
1064
    _CheckOutputFields(static=["name", "pinst", "sinst", "pip", "sip"],
1065
                       dynamic=self.dynamic_fields,
1066
                       selected=self.op.output_fields)
1040 1067

  
1041 1068

  
1042 1069
  def Exec(self, feedback_fn):
......
1106 1133
    return output
1107 1134

  
1108 1135

  
1136
class LUQueryNodeVolumes(NoHooksLU):
1137
  """Logical unit for getting volumes on node(s).
1138

  
1139
  """
1140
  _OP_REQP = ["nodes", "output_fields"]
1141

  
1142
  def CheckPrereq(self):
1143
    """Check prerequisites.
1144

  
1145
    This checks that the fields required are valid output fields.
1146

  
1147
    """
1148
    self.nodes = _GetWantedNodes(self, self.op.nodes)
1149

  
1150
    _CheckOutputFields(static=["node"],
1151
                       dynamic=["phys", "vg", "name", "size", "instance"],
1152
                       selected=self.op.output_fields)
1153

  
1154

  
1155
  def Exec(self, feedback_fn):
1156
    """Computes the list of nodes and their attributes.
1157

  
1158
    """
1159
    nodenames = utils.NiceSort([node.name for node in self.nodes])
1160
    volumes = rpc.call_node_volumes(nodenames)
1161

  
1162
    ilist = [self.cfg.GetInstanceInfo(iname) for iname
1163
             in self.cfg.GetInstanceList()]
1164

  
1165
    lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist])
1166

  
1167
    output = []
1168
    for node in nodenames:
1169
      node_vols = volumes[node][:]
1170
      node_vols.sort(key=lambda vol: vol['dev'])
1171

  
1172
      for vol in node_vols:
1173
        node_output = []
1174
        for field in self.op.output_fields:
1175
          if field == "node":
1176
            val = node
1177
          elif field == "phys":
1178
            val = vol['dev']
1179
          elif field == "vg":
1180
            val = vol['vg']
1181
          elif field == "name":
1182
            val = vol['name']
1183
          elif field == "size":
1184
            val = int(float(vol['size']))
1185
          elif field == "instance":
1186
            for inst in ilist:
1187
              if node not in lv_by_node[inst]:
1188
                continue
1189
              if vol['name'] in lv_by_node[inst][node]:
1190
                val = inst.name
1191
                break
1192
            else:
1193
              val = '-'
1194
          else:
1195
            raise errors.ParameterError, field
1196
          node_output.append(str(val))
1197

  
1198
        output.append(node_output)
1199

  
1200
    return output
1201

  
1202

  
1109 1203
def _CheckNodesDirs(node_list, paths):
1110 1204
  """Verify if the given nodes have the same files.
1111 1205

  
......
1477 1571
    """
1478 1572
    if not os.path.exists(self.op.filename):
1479 1573
      raise errors.OpPrereqError("No such filename '%s'" % self.op.filename)
1480
    if self.op.nodes:
1481
      nodes = self.op.nodes
1482
    else:
1483
      nodes = self.cfg.GetNodeList()
1484
    self.nodes = []
1485
    for node in nodes:
1486
      nname = self.cfg.ExpandNodeName(node)
1487
      if nname is None:
1488
        raise errors.OpPrereqError, ("Node '%s' is unknown." % node)
1489
      self.nodes.append(nname)
1574

  
1575
    self.nodes = _GetWantedNodes(self, self.op.nodes)
1490 1576

  
1491 1577
  def Exec(self, feedback_fn):
1492 1578
    """Copy a file from master to some nodes.
......
1540 1626
    It checks that the given list of nodes is valid.
1541 1627

  
1542 1628
    """
1543
    if self.op.nodes:
1544
      nodes = self.op.nodes
1545
    else:
1546
      nodes = self.cfg.GetNodeList()
1547
    self.nodes = []
1548
    for node in nodes:
1549
      nname = self.cfg.ExpandNodeName(node)
1550
      if nname is None:
1551
        raise errors.OpPrereqError, ("Node '%s' is unknown." % node)
1552
      self.nodes.append(nname)
1629
    self.nodes = _GetWantedNodes(self, self.op.nodes)
1553 1630

  
1554 1631
  def Exec(self, feedback_fn):
1555 1632
    """Run a command on some nodes.
......
1557 1634
    """
1558 1635
    data = []
1559 1636
    for node in self.nodes:
1560
      result = utils.RunCmd(["ssh", node, self.op.command])
1561
      data.append((node, result.cmd, result.output, result.exit_code))
1637
      result = utils.RunCmd(["ssh", node.name, self.op.command])
1638
      data.append((node.name, result.cmd, result.output, result.exit_code))
1562 1639

  
1563 1640
    return data
1564 1641

  
......
1884 1961
  """Logical unit for querying instances.
1885 1962

  
1886 1963
  """
1887
  OP_REQP = ["output_fields"]
1964
  _OP_REQP = ["output_fields"]
1888 1965

  
1889 1966
  def CheckPrereq(self):
1890 1967
    """Check prerequisites.
......
1892 1969
    This checks that the fields required are valid output fields.
1893 1970

  
1894 1971
    """
1895

  
1896
    self.static_fields = frozenset(["name", "os", "pnode", "snodes",
1897
                                    "admin_state", "admin_ram",
1898
                                    "disk_template", "ip", "mac", "bridge"])
1899 1972
    self.dynamic_fields = frozenset(["oper_state", "oper_ram"])
1900
    self.all_fields = self.static_fields | self.dynamic_fields
1901

  
1902
    if not self.all_fields.issuperset(self.op.output_fields):
1903
      raise errors.OpPrereqError, ("Unknown output fields selected: %s"
1904
                                   % ",".join(frozenset(self.op.output_fields).
1905
                                              difference(self.all_fields)))
1973
    _CheckOutputFields(static=["name", "os", "pnode", "snodes",
1974
                               "admin_state", "admin_ram",
1975
                               "disk_template", "ip", "mac", "bridge"],
1976
                       dynamic=self.dynamic_fields,
1977
                       selected=self.op.output_fields)
1906 1978

  
1907 1979
  def Exec(self, feedback_fn):
1908 1980
    """Computes the list of nodes and their attributes.
......
3074 3146
    This only checks the optional node list against the existing names.
3075 3147

  
3076 3148
    """
3077
    if not isinstance(self.op.nodes, list):
3078
      raise errors.OpPrereqError, "Invalid argument type 'nodes'"
3079
    if self.op.nodes:
3080
      self.wanted_nodes = []
3081
      names = self.op.nodes
3082
      for name in names:
3083
        node = self.cfg.GetNodeInfo(self.cfg.ExpandNodeName(name))
3084
        if node is None:
3085
          raise errors.OpPrereqError, ("No such node name '%s'" % name)
3086
      self.wanted_nodes.append(node)
3087
    else:
3088
      self.wanted_nodes = [self.cfg.GetNodeInfo(name) for name
3089
                           in self.cfg.GetNodeList()]
3090
    return
3149
    self.wanted_nodes = _GetWantedNodes(self, self.op.nodes)
3091 3150

  
3092 3151
  def Exec(self, feedback_fn):
3093 3152
    """Compute and return the list of nodes.
......
3214 3273
    """Check that the nodelist contains only existing nodes.
3215 3274

  
3216 3275
    """
3217
    nodes = getattr(self.op, "nodes", None)
3218
    if not nodes:
3219
      self.op.nodes = self.cfg.GetNodeList()
3220
    else:
3221
      expnodes = [self.cfg.ExpandNodeName(node) for node in nodes]
3222
      if expnodes.count(None) > 0:
3223
        raise errors.OpPrereqError, ("At least one of the given nodes %s"
3224
                                     " is unknown" % self.op.nodes)
3225
      self.op.nodes = expnodes
3276
    self.nodes = _GetWantedNodes(self, getattr(self.op, "nodes", None))
3226 3277

  
3227 3278
  def Exec(self, feedback_fn):
3228

  
3229 3279
    """Compute the list of all the exported system images.
3230 3280

  
3231 3281
    Returns:
......
3234 3284
      that node.
3235 3285

  
3236 3286
    """
3237
    return rpc.call_export_list(self.op.nodes)
3287
    return rpc.call_export_list([node.name for node in self.nodes])
3238 3288

  
3239 3289

  
3240 3290
class LUExportInstance(LogicalUnit):
b/lib/mcpu.py
59 59
    opcodes.OpAddNode: cmdlib.LUAddNode,
60 60
    opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
61 61
    opcodes.OpQueryNodeData: cmdlib.LUQueryNodeData,
62
    opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
62 63
    opcodes.OpRemoveNode: cmdlib.LURemoveNode,
63 64
    # instance lu
64 65
    opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
b/lib/opcodes.py
122 122
  __slots__ = ["nodes"]
123 123

  
124 124

  
125
class OpQueryNodeVolumes(OpCode):
126
  """Get list of volumes on node."""
127
  OP_ID = "OP_NODE_QUERYVOLS"
128
  __slots__ = ["nodes", "output_fields"]
129

  
130

  
125 131
# instance opcodes
126 132

  
127 133
class OpCreateInstance(OpCode):
b/lib/rpc.py
762 762
  c.connect(node)
763 763
  c.run()
764 764
  return c.getresult().get(node, False)
765

  
766

  
767
def call_node_volumes(node_list):
768
  """Gets all volumes on node(s).
769

  
770
  This is a multi-node call.
771

  
772
  """
773
  c = Client("node_volumes", [])
774
  c.connect_list(node_list)
775
  c.run()
776
  return c.getresult()
b/man/gnt-node.sgml
261 261
      </para>
262 262
    </refsect2>
263 263

  
264
    <refsect2>
265
      <title>VOLUMES</title>
266

  
267
      <cmdsynopsis>
268
        <command>volumes</command>
269
        <arg rep="repeat"><replaceable>node</replaceable></arg>
270
      </cmdsynopsis>
271

  
272
      <para>
273
        Lists all logical volumes and their physical disks from the node(s)
274
        provided.
275
      </para>
276

  
277
      <para>
278
        Example:
279
        <screen>
280
# gnt-node volumes node5.example.com
281
Node              PhysDev   VG    Name                                 Size Instance
282
node1.example.com /dev/hdc1 xenvg instance1.example.com-sda_11000.meta 128  instance1.example.com
283
node1.example.com /dev/hdc1 xenvg instance1.example.com-sda_11001.data 256  instance1.example.com
284
        </screen>
285
      </para>
286
    </refsect2>
287

  
264 288
  </refsect1>
265 289

  
266 290
  &footer;
b/scripts/gnt-instance
489 489
  'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]",
490 490
           "Show information on the specified instance"),
491 491
  'list': (ListInstances, ARGS_NONE,
492
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT,
493
            make_option("-o", "--output", dest="output", action="store",
494
                        type="string", help="Select output fields",
495
                        metavar="FIELDS")
496
            ],
492
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
497 493
           "", "Lists the instances and their status"),
498 494
  'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, force_opt],
499 495
             "[-f] <instance>", "Shuts down the instance and removes it"),
b/scripts/gnt-node
130 130
  SubmitOpCode(op)
131 131

  
132 132

  
133
def ListVolumes(opts, args):
134
  """List logical volumes on node(s).
135

  
136
  """
137
  if opts.output is None:
138
    selected_fields = ["node", "phys", "vg",
139
                       "name", "size", "instance"]
140
  else:
141
    selected_fields = opts.output.split(",")
142

  
143
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
144
  output = SubmitOpCode(op)
145

  
146
  mlens = [0 for name in selected_fields]
147
  format_fields = []
148
  unitformat_fields = ("size",)
149
  for field in selected_fields:
150
    if field in unitformat_fields:
151
      format_fields.append("%*s")
152
    else:
153
      format_fields.append("%-*s")
154

  
155
  separator = opts.separator
156
  if "%" in separator:
157
    separator = separator.replace("%", "%%")
158
  format = separator.join(format_fields)
159

  
160
  for row in output:
161
    for idx, val in enumerate(row):
162
      if opts.human_readable and selected_fields[idx] in unitformat_fields:
163
        try:
164
          val = int(val)
165
        except ValueError:
166
          pass
167
        else:
168
          val = row[idx] = utils.FormatUnit(val)
169
      mlens[idx] = max(mlens[idx], len(val))
170

  
171
  if not opts.no_headers:
172
    header_list = {"node": "Node", "phys": "PhysDev",
173
                   "vg": "VG", "name": "Name",
174
                   "size": "Size", "instance": "Instance"}
175
    args = []
176
    for idx, name in enumerate(selected_fields):
177
      hdr = header_list[name]
178
      mlens[idx] = max(mlens[idx], len(hdr))
179
      args.append(mlens[idx])
180
      args.append(hdr)
181
    logger.ToStdout(format % tuple(args))
182

  
183
  for row in output:
184
    args = []
185
    for idx, val in enumerate(row):
186
      args.append(mlens[idx])
187
      args.append(val)
188
    logger.ToStdout(format % tuple(args))
189

  
190
  return 0
191

  
192

  
133 193
commands = {
134 194
  'add': (AddNode, ARGS_ONE,
135 195
          [DEBUG_OPT,
......
140 200
  'info': (ShowNodeConfig, ARGS_ANY, [DEBUG_OPT],
141 201
           "[<node_name>...]", "Show information about the node(s)"),
142 202
  'list': (ListNodes, ARGS_NONE,
143
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT,
144
            make_option("-o", "--output", dest="output", action="store",
145
                        type="string", help="Select output fields",
146
                        metavar="FIELDS")
147
            ],
203
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
148 204
           "", "Lists the nodes in the cluster"),
149 205
  'remove': (RemoveNode, ARGS_ONE, [DEBUG_OPT],
150 206
             "<node_name>", "Removes a node from the cluster"),
207
  'volumes': (ListVolumes, ARGS_ANY,
208
              [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
209
              "[<node_name>...]", "List logical volumes on node(s)"),
151 210
  }
152 211

  
153 212

  

Also available in: Unified diff