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: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
- 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
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):
@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):
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,
#: Attribute holding field definitions
FIELDS = None
+ #: Field to sort by
+ SORT_FIELD = "name"
+
def __init__(self, qfilter, fields, use_locking):
"""Initializes this class.
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()
"""
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
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
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([
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()
#: 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,
constants.QR_GROUP: GROUP_FIELDS,
constants.QR_OS: OS_FIELDS,
constants.QR_JOB: JOB_FIELDS,
+ constants.QR_EXPORT: EXPORT_FIELDS,
}
#: All available field lists
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
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)
"""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."""
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
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"
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"