Commit 0fdf247d authored by Michael Hanselmann's avatar Michael Hanselmann

Convert listing exports to query2

This solves one case where locks are acquired during LUXI queries.
Pretty late into the transition I noticed that OpBackupQuery had a
“use_locking” parameter for a long time, but didn't use it. Since
most of the other changes were already and this allows exports to
be listed via RAPI (/2/query) I decided to finish.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent dd27bc21
......@@ -15,9 +15,10 @@ Version 2.6.0 beta1
- Removed deprecated ``QueryLocks`` LUXI request. Use
``Query(what=QR_LOCK, ...)`` instead.
- The LUXI requests :pyeval:`luxi.REQ_QUERY_JOBS`,
:pyeval:`luxi.REQ_QUERY_INSTANCES`, :pyeval:`luxi.REQ_QUERY_NODES` and
:pyeval:`luxi.REQ_QUERY_GROUPS` are deprecated and will be removed in
a future version. :pyeval:`luxi.REQ_QUERY` should be used instead.
:pyeval:`luxi.REQ_QUERY_INSTANCES`, :pyeval:`luxi.REQ_QUERY_NODES`,
:pyeval:`luxi.REQ_QUERY_GROUPS` and :pyeval:`luxi.REQ_QUERY_EXPORTS`
are deprecated and will be removed in a future version.
:pyeval:`luxi.REQ_QUERY` should be used instead.
Version 2.5.0
......
......@@ -30,6 +30,10 @@ from ganeti.cli import *
from ganeti import opcodes
from ganeti import constants
from ganeti import errors
from ganeti import qlang
_LIST_DEF_FIELDS = ["node", "export"]
def PrintExportList(opts, args):
......@@ -42,18 +46,27 @@ def PrintExportList(opts, args):
@return: the desired exit code
"""
exports = GetClient().QueryExports(opts.nodes, False)
retcode = 0
for node in exports:
ToStdout("Node: %s", node)
ToStdout("Exports:")
if isinstance(exports[node], list):
for instance_name in exports[node]:
ToStdout("\t%s", instance_name)
else:
ToStdout(" Could not get exports list")
retcode = 1
return retcode
selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
qfilter = qlang.MakeSimpleFilter("node", opts.nodes)
return GenericList(constants.QR_EXPORT, selected_fields, None, opts.units,
opts.separator, not opts.no_headers,
verbose=opts.verbose, qfilter=qfilter)
def ListExportFields(opts, args):
"""List export fields.
@param opts: the command line options selected by the user
@type args: list
@param args: fields to list, or empty for all
@rtype: int
@return: the desired exit code
"""
return GenericListFields(constants.QR_EXPORT, args, opts.separator,
not opts.no_headers)
def ExportInstance(opts, args):
......@@ -122,8 +135,13 @@ import_opts = [
commands = {
"list": (
PrintExportList, ARGS_NONE,
[NODE_LIST_OPT],
[NODE_LIST_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
"", "Lists instance exports available in the ganeti cluster"),
"list-fields": (
ListExportFields, [ArgUnknown()],
[NOHDR_OPT, SEP_OPT],
"[fields...]",
"Lists all available fields for exports"),
"export": (
ExportInstance, ARGS_ONE_INSTANCE,
[FORCE_OPT, SINGLE_NODE_OPT, NOSHUTDOWN_OPT, SHUTDOWN_TIMEOUT_OPT,
......
......@@ -493,6 +493,9 @@ class _QueryBase:
#: Attribute holding field definitions
FIELDS = None
#: Field to sort by
SORT_FIELD = "name"
def __init__(self, qfilter, fields, use_locking):
"""Initializes this class.
......@@ -500,7 +503,7 @@ class _QueryBase:
self.use_locking = use_locking
self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
namefield="name")
namefield=self.SORT_FIELD)
self.requested_data = self.query.RequestedData()
self.names = self.query.RequestedNames()
......@@ -13024,32 +13027,73 @@ class LUBackupQuery(NoHooksLU):
"""
REQ_BGL = False
def CheckArguments(self):
self.expq = _ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
["node", "export"], self.op.use_locking)
def ExpandNames(self):
self.needed_locks = {}
self.share_locks[locking.LEVEL_NODE] = 1
if not self.op.nodes:
self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
else:
self.needed_locks[locking.LEVEL_NODE] = \
_GetWantedNodes(self, self.op.nodes)
self.expq.ExpandNames(self)
def DeclareLocks(self, level):
self.expq.DeclareLocks(self, level)
def Exec(self, feedback_fn):
"""Compute the list of all the exported system images.
result = {}
@rtype: dict
@return: a dictionary with the structure node->(export-list)
where export-list is a list of the instances exported on
that node.
for (node, expname) in self.expq.OldStyleQuery(self):
if expname is None:
result[node] = False
else:
result.setdefault(node, []).append(expname)
return result
class _ExportQuery(_QueryBase):
FIELDS = query.EXPORT_FIELDS
#: The node name is not a unique key for this query
SORT_FIELD = "node"
def ExpandNames(self, lu):
lu.needed_locks = {}
# The following variables interact with _QueryBase._GetNames
if self.names:
self.wanted = _GetWantedNodes(lu, self.names)
else:
self.wanted = locking.ALL_SET
self.do_locking = self.use_locking
if self.do_locking:
lu.share_locks = _ShareAll()
lu.needed_locks = {
locking.LEVEL_NODE: self.wanted,
}
def DeclareLocks(self, lu, level):
pass
def _GetQueryData(self, lu):
"""Computes the list of nodes and their attributes.
"""
self.nodes = self.owned_locks(locking.LEVEL_NODE)
rpcresult = self.rpc.call_export_list(self.nodes)
result = {}
for node in rpcresult:
if rpcresult[node].fail_msg:
result[node] = False
# Locking is not used
assert not (compat.any(lu.glm.is_owned(level)
for level in locking.LEVELS
if level != locking.LEVEL_CLUSTER) or
self.do_locking or self.use_locking)
nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
result = []
for (node, nres) in lu.rpc.call_export_list(nodes).items():
if nres.fail_msg:
result.append((node, None))
else:
result[node] = rpcresult[node].payload
result.extend((node, expname) for expname in nres.payload)
return result
......@@ -15174,6 +15218,7 @@ _QUERY_IMPL = {
constants.QR_NODE: _NodeQuery,
constants.QR_GROUP: _GroupQuery,
constants.QR_OS: _OsQuery,
constants.QR_EXPORT: _ExportQuery,
}
assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
......
......@@ -1631,9 +1631,16 @@ QR_LOCK = "lock"
QR_GROUP = "group"
QR_OS = "os"
QR_JOB = "job"
QR_EXPORT = "export"
#: List of resources which can be queried using L{opcodes.OpQuery}
QR_VIA_OP = frozenset([QR_INSTANCE, QR_NODE, QR_GROUP, QR_OS])
QR_VIA_OP = frozenset([
QR_INSTANCE,
QR_NODE,
QR_GROUP,
QR_OS,
QR_EXPORT,
])
#: List of resources which can be queried using Local UniX Interface
QR_VIA_LUXI = QR_VIA_OP.union([
......
......@@ -2240,6 +2240,30 @@ def _BuildJobFields():
return _PrepareFieldList(fields, [])
def _GetExportName(_, (node_name, expname)): # pylint: disable=W0613
"""Returns an export name if available.
"""
if expname is None:
return _FS_UNAVAIL
else:
return expname
def _BuildExportFields():
"""Builds list of fields for exports.
"""
fields = [
(_MakeField("node", "Node", QFT_TEXT, "Node name"),
None, QFF_HOSTNAME, lambda _, (node_name, expname): node_name),
(_MakeField("export", "Export", QFT_TEXT, "Export name"),
None, 0, _GetExportName),
]
return _PrepareFieldList(fields, [])
#: Fields available for node queries
NODE_FIELDS = _BuildNodeFields()
......@@ -2258,6 +2282,9 @@ OS_FIELDS = _BuildOsFields()
#: Fields available for job queries
JOB_FIELDS = _BuildJobFields()
#: Fields available for exports
EXPORT_FIELDS = _BuildExportFields()
#: All available resources
ALL_FIELDS = {
constants.QR_INSTANCE: INSTANCE_FIELDS,
......@@ -2266,6 +2293,7 @@ ALL_FIELDS = {
constants.QR_GROUP: GROUP_FIELDS,
constants.QR_OS: OS_FIELDS,
constants.QR_JOB: JOB_FIELDS,
constants.QR_EXPORT: EXPORT_FIELDS,
}
#: All available field lists
......
......@@ -228,16 +228,40 @@ Explicit configuration example::
LIST
~~~~
**list** [\--node=*NODE*]
| **list** [\--node=*NODE*] [\--no-headers] [\--separator=*SEPARATOR*]
| [-o *[+]FIELD,...*]
Lists the exports currently available in the default directory in
all the nodes of the current cluster, or optionally only a subset
of them specified using the ``--node`` option (which can be used
multiple times)
The ``--no-headers`` option will skip the initial header line. The
``--separator`` option takes an argument which denotes what will be
used between the output fields. Both these options are to help
scripting.
The ``-o`` option takes a comma-separated list of output fields.
The available fields and their meaning are:
@QUERY_FIELDS_EXPORT@
If the value of the option starts with the character ``+``, the new
fields will be added to the default list. This allows one to quickly
see the default list plus a few other fields, instead of retyping
the entire list of fields.
Example::
# gnt-backup list --nodes node1 --nodes node2
# gnt-backup list --node node1 --node node2
LIST-FIELDS
~~~~~~~~~~~
**list-fields** [field...]
Lists available fields for exports.
REMOVE
......
......@@ -149,6 +149,7 @@ def SetupCluster(rapi_user, rapi_secret):
RunTestIf("node-list", qa_node.TestNodeListFields)
RunTestIf("instance-list", qa_instance.TestInstanceListFields)
RunTestIf("job-list", qa_job.TestJobListFields)
RunTestIf("instance-export", qa_instance.TestBackupListFields)
RunTestIf("node-info", qa_node.TestNodeInfo)
......
......@@ -358,6 +358,14 @@ def TestBackupList(expnode):
"""gnt-backup list"""
AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
namefield=None, test_unknown=False)
def TestBackupListFields():
"""gnt-backup list-fields"""
qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
def _TestInstanceDiskFailure(instance, node, node2, onmaster):
"""Testing disk failure."""
......
......@@ -426,19 +426,20 @@ def GenericQueryTest(cmd, fields, namefield="name", test_unknown=True):
for testfields in _SelectQueryFields(rnd, fields):
AssertCommand([cmd, "list", "--output", ",".join(testfields)])
namelist_fn = compat.partial(_List, cmd, [namefield])
if namefield is not None:
namelist_fn = compat.partial(_List, cmd, [namefield])
# When no names were requested, the list must be sorted
names = namelist_fn(None)
AssertEqual(names, utils.NiceSort(names))
# When no names were requested, the list must be sorted
names = namelist_fn(None)
AssertEqual(names, utils.NiceSort(names))
# When requesting specific names, the order must be kept
revnames = list(reversed(names))
AssertEqual(namelist_fn(revnames), revnames)
# When requesting specific names, the order must be kept
revnames = list(reversed(names))
AssertEqual(namelist_fn(revnames), revnames)
randnames = list(names)
rnd.shuffle(randnames)
AssertEqual(namelist_fn(randnames), randnames)
randnames = list(names)
rnd.shuffle(randnames)
AssertEqual(namelist_fn(randnames), randnames)
if test_unknown:
# Listing unknown items must fail
......
......@@ -1128,9 +1128,11 @@ class TestQueryFields(unittest.TestCase):
class TestQueryFilter(unittest.TestCase):
def testRequestedNames(self):
for fielddefs in query.ALL_FIELD_LISTS:
if "id" in fielddefs:
for (what, fielddefs) in query.ALL_FIELDS.items():
if what == constants.QR_JOB:
namefield = "id"
elif what == constants.QR_EXPORT:
namefield = "export"
else:
namefield = "name"
......@@ -1207,9 +1209,11 @@ class TestQueryFilter(unittest.TestCase):
def testCompileFilter(self):
levels_max = query._FilterCompilerHelper._LEVELS_MAX
for fielddefs in query.ALL_FIELD_LISTS:
if "id" in fielddefs:
for (what, fielddefs) in query.ALL_FIELDS.items():
if what == constants.QR_JOB:
namefield = "id"
elif what == constants.QR_EXPORT:
namefield = "export"
else:
namefield = "name"
......
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