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