Revision 0fdf247d

b/NEWS
15 15
- Removed deprecated ``QueryLocks`` LUXI request. Use
16 16
  ``Query(what=QR_LOCK, ...)`` instead.
17 17
- The LUXI requests :pyeval:`luxi.REQ_QUERY_JOBS`,
18
  :pyeval:`luxi.REQ_QUERY_INSTANCES`, :pyeval:`luxi.REQ_QUERY_NODES` and
19
  :pyeval:`luxi.REQ_QUERY_GROUPS` are deprecated and will be removed in
20
  a future version. :pyeval:`luxi.REQ_QUERY` should be used instead.
18
  :pyeval:`luxi.REQ_QUERY_INSTANCES`, :pyeval:`luxi.REQ_QUERY_NODES`,
19
  :pyeval:`luxi.REQ_QUERY_GROUPS` and :pyeval:`luxi.REQ_QUERY_EXPORTS`
20
  are deprecated and will be removed in a future version.
21
  :pyeval:`luxi.REQ_QUERY` should be used instead.
21 22

  
22 23

  
23 24
Version 2.5.0
b/lib/client/gnt_backup.py
30 30
from ganeti import opcodes
31 31
from ganeti import constants
32 32
from ganeti import errors
33
from ganeti import qlang
34

  
35

  
36
_LIST_DEF_FIELDS = ["node", "export"]
33 37

  
34 38

  
35 39
def PrintExportList(opts, args):
......
42 46
  @return: the desired exit code
43 47

  
44 48
  """
45
  exports = GetClient().QueryExports(opts.nodes, False)
46
  retcode = 0
47
  for node in exports:
48
    ToStdout("Node: %s", node)
49
    ToStdout("Exports:")
50
    if isinstance(exports[node], list):
51
      for instance_name in exports[node]:
52
        ToStdout("\t%s", instance_name)
53
    else:
54
      ToStdout("  Could not get exports list")
55
      retcode = 1
56
  return retcode
49
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
50

  
51
  qfilter = qlang.MakeSimpleFilter("node", opts.nodes)
52

  
53
  return GenericList(constants.QR_EXPORT, selected_fields, None, opts.units,
54
                     opts.separator, not opts.no_headers,
55
                     verbose=opts.verbose, qfilter=qfilter)
56

  
57

  
58
def ListExportFields(opts, args):
59
  """List export fields.
60

  
61
  @param opts: the command line options selected by the user
62
  @type args: list
63
  @param args: fields to list, or empty for all
64
  @rtype: int
65
  @return: the desired exit code
66

  
67
  """
68
  return GenericListFields(constants.QR_EXPORT, args, opts.separator,
69
                           not opts.no_headers)
57 70

  
58 71

  
59 72
def ExportInstance(opts, args):
......
122 135
commands = {
123 136
  "list": (
124 137
    PrintExportList, ARGS_NONE,
125
    [NODE_LIST_OPT],
138
    [NODE_LIST_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
126 139
    "", "Lists instance exports available in the ganeti cluster"),
140
  "list-fields": (
141
    ListExportFields, [ArgUnknown()],
142
    [NOHDR_OPT, SEP_OPT],
143
    "[fields...]",
144
    "Lists all available fields for exports"),
127 145
  "export": (
128 146
    ExportInstance, ARGS_ONE_INSTANCE,
129 147
    [FORCE_OPT, SINGLE_NODE_OPT, NOSHUTDOWN_OPT, SHUTDOWN_TIMEOUT_OPT,
b/lib/cmdlib.py
493 493
  #: Attribute holding field definitions
494 494
  FIELDS = None
495 495

  
496
  #: Field to sort by
497
  SORT_FIELD = "name"
498

  
496 499
  def __init__(self, qfilter, fields, use_locking):
497 500
    """Initializes this class.
498 501

  
......
500 503
    self.use_locking = use_locking
501 504

  
502 505
    self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
503
                             namefield="name")
506
                             namefield=self.SORT_FIELD)
504 507
    self.requested_data = self.query.RequestedData()
505 508
    self.names = self.query.RequestedNames()
506 509

  
......
13024 13027
  """
13025 13028
  REQ_BGL = False
13026 13029

  
13030
  def CheckArguments(self):
13031
    self.expq = _ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
13032
                             ["node", "export"], self.op.use_locking)
13033

  
13027 13034
  def ExpandNames(self):
13028
    self.needed_locks = {}
13029
    self.share_locks[locking.LEVEL_NODE] = 1
13030
    if not self.op.nodes:
13031
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13032
    else:
13033
      self.needed_locks[locking.LEVEL_NODE] = \
13034
        _GetWantedNodes(self, self.op.nodes)
13035
    self.expq.ExpandNames(self)
13036

  
13037
  def DeclareLocks(self, level):
13038
    self.expq.DeclareLocks(self, level)
13035 13039

  
13036 13040
  def Exec(self, feedback_fn):
13037
    """Compute the list of all the exported system images.
13041
    result = {}
13038 13042

  
13039
    @rtype: dict
13040
    @return: a dictionary with the structure node->(export-list)
13041
        where export-list is a list of the instances exported on
13042
        that node.
13043
    for (node, expname) in self.expq.OldStyleQuery(self):
13044
      if expname is None:
13045
        result[node] = False
13046
      else:
13047
        result.setdefault(node, []).append(expname)
13048

  
13049
    return result
13050

  
13051

  
13052
class _ExportQuery(_QueryBase):
13053
  FIELDS = query.EXPORT_FIELDS
13054

  
13055
  #: The node name is not a unique key for this query
13056
  SORT_FIELD = "node"
13057

  
13058
  def ExpandNames(self, lu):
13059
    lu.needed_locks = {}
13060

  
13061
    # The following variables interact with _QueryBase._GetNames
13062
    if self.names:
13063
      self.wanted = _GetWantedNodes(lu, self.names)
13064
    else:
13065
      self.wanted = locking.ALL_SET
13066

  
13067
    self.do_locking = self.use_locking
13068

  
13069
    if self.do_locking:
13070
      lu.share_locks = _ShareAll()
13071
      lu.needed_locks = {
13072
        locking.LEVEL_NODE: self.wanted,
13073
        }
13074

  
13075
  def DeclareLocks(self, lu, level):
13076
    pass
13077

  
13078
  def _GetQueryData(self, lu):
13079
    """Computes the list of nodes and their attributes.
13043 13080

  
13044 13081
    """
13045
    self.nodes = self.owned_locks(locking.LEVEL_NODE)
13046
    rpcresult = self.rpc.call_export_list(self.nodes)
13047
    result = {}
13048
    for node in rpcresult:
13049
      if rpcresult[node].fail_msg:
13050
        result[node] = False
13082
    # Locking is not used
13083
    assert not (compat.any(lu.glm.is_owned(level)
13084
                           for level in locking.LEVELS
13085
                           if level != locking.LEVEL_CLUSTER) or
13086
                self.do_locking or self.use_locking)
13087

  
13088
    nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
13089

  
13090
    result = []
13091

  
13092
    for (node, nres) in lu.rpc.call_export_list(nodes).items():
13093
      if nres.fail_msg:
13094
        result.append((node, None))
13051 13095
      else:
13052
        result[node] = rpcresult[node].payload
13096
        result.extend((node, expname) for expname in nres.payload)
13053 13097

  
13054 13098
    return result
13055 13099

  
......
15174 15218
  constants.QR_NODE: _NodeQuery,
15175 15219
  constants.QR_GROUP: _GroupQuery,
15176 15220
  constants.QR_OS: _OsQuery,
15221
  constants.QR_EXPORT: _ExportQuery,
15177 15222
  }
15178 15223

  
15179 15224
assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
b/lib/constants.py
1631 1631
QR_GROUP = "group"
1632 1632
QR_OS = "os"
1633 1633
QR_JOB = "job"
1634
QR_EXPORT = "export"
1634 1635

  
1635 1636
#: List of resources which can be queried using L{opcodes.OpQuery}
1636
QR_VIA_OP = frozenset([QR_INSTANCE, QR_NODE, QR_GROUP, QR_OS])
1637
QR_VIA_OP = frozenset([
1638
  QR_INSTANCE,
1639
  QR_NODE,
1640
  QR_GROUP,
1641
  QR_OS,
1642
  QR_EXPORT,
1643
  ])
1637 1644

  
1638 1645
#: List of resources which can be queried using Local UniX Interface
1639 1646
QR_VIA_LUXI = QR_VIA_OP.union([
b/lib/query.py
2240 2240
  return _PrepareFieldList(fields, [])
2241 2241

  
2242 2242

  
2243
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
2244
  """Returns an export name if available.
2245

  
2246
  """
2247
  if expname is None:
2248
    return _FS_UNAVAIL
2249
  else:
2250
    return expname
2251

  
2252

  
2253
def _BuildExportFields():
2254
  """Builds list of fields for exports.
2255

  
2256
  """
2257
  fields = [
2258
    (_MakeField("node", "Node", QFT_TEXT, "Node name"),
2259
     None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
2260
    (_MakeField("export", "Export", QFT_TEXT, "Export name"),
2261
     None, 0, _GetExportName),
2262
    ]
2263

  
2264
  return _PrepareFieldList(fields, [])
2265

  
2266

  
2243 2267
#: Fields available for node queries
2244 2268
NODE_FIELDS = _BuildNodeFields()
2245 2269

  
......
2258 2282
#: Fields available for job queries
2259 2283
JOB_FIELDS = _BuildJobFields()
2260 2284

  
2285
#: Fields available for exports
2286
EXPORT_FIELDS = _BuildExportFields()
2287

  
2261 2288
#: All available resources
2262 2289
ALL_FIELDS = {
2263 2290
  constants.QR_INSTANCE: INSTANCE_FIELDS,
......
2266 2293
  constants.QR_GROUP: GROUP_FIELDS,
2267 2294
  constants.QR_OS: OS_FIELDS,
2268 2295
  constants.QR_JOB: JOB_FIELDS,
2296
  constants.QR_EXPORT: EXPORT_FIELDS,
2269 2297
  }
2270 2298

  
2271 2299
#: All available field lists
b/man/gnt-backup.rst
228 228
LIST
229 229
~~~~
230 230

  
231
**list** [\--node=*NODE*]
231
| **list** [\--node=*NODE*] [\--no-headers] [\--separator=*SEPARATOR*]
232
| [-o *[+]FIELD,...*]
232 233

  
233 234
Lists the exports currently available in the default directory in
234 235
all the nodes of the current cluster, or optionally only a subset
235 236
of them specified using the ``--node`` option (which can be used
236 237
multiple times)
237 238

  
239
The ``--no-headers`` option will skip the initial header line. The
240
``--separator`` option takes an argument which denotes what will be
241
used between the output fields. Both these options are to help
242
scripting.
243

  
244
The ``-o`` option takes a comma-separated list of output fields.
245
The available fields and their meaning are:
246

  
247
@QUERY_FIELDS_EXPORT@
248

  
249
If the value of the option starts with the character ``+``, the new
250
fields will be added to the default list. This allows one to quickly
251
see the default list plus a few other fields, instead of retyping
252
the entire list of fields.
253

  
238 254
Example::
239 255

  
240
    # gnt-backup list --nodes node1 --nodes node2
256
    # gnt-backup list --node node1 --node node2
257

  
258

  
259
LIST-FIELDS
260
~~~~~~~~~~~
261

  
262
**list-fields** [field...]
263

  
264
Lists available fields for exports.
241 265

  
242 266

  
243 267
REMOVE
b/qa/ganeti-qa.py
149 149
  RunTestIf("node-list", qa_node.TestNodeListFields)
150 150
  RunTestIf("instance-list", qa_instance.TestInstanceListFields)
151 151
  RunTestIf("job-list", qa_job.TestJobListFields)
152
  RunTestIf("instance-export", qa_instance.TestBackupListFields)
152 153

  
153 154
  RunTestIf("node-info", qa_node.TestNodeInfo)
154 155

  
b/qa/qa_instance.py
358 358
  """gnt-backup list"""
359 359
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
360 360

  
361
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
362
                            namefield=None, test_unknown=False)
363

  
364

  
365
def TestBackupListFields():
366
  """gnt-backup list-fields"""
367
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
368

  
361 369

  
362 370
def _TestInstanceDiskFailure(instance, node, node2, onmaster):
363 371
  """Testing disk failure."""
b/qa/qa_utils.py
426 426
  for testfields in _SelectQueryFields(rnd, fields):
427 427
    AssertCommand([cmd, "list", "--output", ",".join(testfields)])
428 428

  
429
  namelist_fn = compat.partial(_List, cmd, [namefield])
429
  if namefield is not None:
430
    namelist_fn = compat.partial(_List, cmd, [namefield])
430 431

  
431
  # When no names were requested, the list must be sorted
432
  names = namelist_fn(None)
433
  AssertEqual(names, utils.NiceSort(names))
432
    # When no names were requested, the list must be sorted
433
    names = namelist_fn(None)
434
    AssertEqual(names, utils.NiceSort(names))
434 435

  
435
  # When requesting specific names, the order must be kept
436
  revnames = list(reversed(names))
437
  AssertEqual(namelist_fn(revnames), revnames)
436
    # When requesting specific names, the order must be kept
437
    revnames = list(reversed(names))
438
    AssertEqual(namelist_fn(revnames), revnames)
438 439

  
439
  randnames = list(names)
440
  rnd.shuffle(randnames)
441
  AssertEqual(namelist_fn(randnames), randnames)
440
    randnames = list(names)
441
    rnd.shuffle(randnames)
442
    AssertEqual(namelist_fn(randnames), randnames)
442 443

  
443 444
  if test_unknown:
444 445
    # Listing unknown items must fail
b/test/ganeti.query_unittest.py
1128 1128

  
1129 1129
class TestQueryFilter(unittest.TestCase):
1130 1130
  def testRequestedNames(self):
1131
    for fielddefs in query.ALL_FIELD_LISTS:
1132
      if "id" in fielddefs:
1131
    for (what, fielddefs) in query.ALL_FIELDS.items():
1132
      if what == constants.QR_JOB:
1133 1133
        namefield = "id"
1134
      elif what == constants.QR_EXPORT:
1135
        namefield = "export"
1134 1136
      else:
1135 1137
        namefield = "name"
1136 1138

  
......
1207 1209
  def testCompileFilter(self):
1208 1210
    levels_max = query._FilterCompilerHelper._LEVELS_MAX
1209 1211

  
1210
    for fielddefs in query.ALL_FIELD_LISTS:
1211
      if "id" in fielddefs:
1212
    for (what, fielddefs) in query.ALL_FIELDS.items():
1213
      if what == constants.QR_JOB:
1212 1214
        namefield = "id"
1215
      elif what == constants.QR_EXPORT:
1216
        namefield = "export"
1213 1217
      else:
1214 1218
        namefield = "name"
1215 1219

  

Also available in: Unified diff