From 05484a249b4f785dfb7c579851833b5df375d249 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Mon, 11 Jul 2011 23:48:12 +0200 Subject: [PATCH] cli.GetOnlineNodes: Support node group filter, use query2 This patc changes cli.GetOnlineNodes to use query2, which does the filtering in the master daemon, and adds a new parameter to filter by node group. Unittests were added for the old implementation and then adopted to ensure no functionality was lost. Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- lib/cli.py | 58 +++++++++++---- test/ganeti.cli_unittest.py | 143 ++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 13 deletions(-) diff --git a/lib/cli.py b/lib/cli.py index 13050dda8..8dd8732a4 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -2868,7 +2868,7 @@ def ParseTimespec(value): def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False, - filter_master=False): + filter_master=False, nodegroup=None): """Returns the names of online nodes. This function will also log a warning on stderr with the names of @@ -2889,28 +2889,60 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False, @param filter_master: if True, do not return the master node in the list (useful in coordination with secondary_ips where we cannot check our node name against the list) + @type nodegroup: string + @param nodegroup: If set, only return nodes in this node group """ if cl is None: cl = GetClient() - if secondary_ips: - name_idx = 2 - else: - name_idx = 0 + filter_ = [] + + if nodes: + filter_.append(qlang.MakeSimpleFilter("name", nodes)) + + if nodegroup is not None: + filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup], + [qlang.OP_EQUAL, "group.uuid", nodegroup]]) if filter_master: - master_node = cl.QueryConfigValues(["master_node"])[0] - filter_fn = lambda x: x != master_node + filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) + + if filter_: + if len(filter_) > 1: + final_filter = [qlang.OP_AND] + filter_ + else: + assert len(filter_) == 1 + final_filter = filter_[0] else: - filter_fn = lambda _: True + final_filter = None + + result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter) + + def _IsOffline(row): + (_, (_, offline), _) = row + return offline + + def _GetName(row): + ((_, name), _, _) = row + return name + + def _GetSip(row): + (_, _, (_, sip)) = row + return sip + + (offline, online) = compat.partition(result.data, _IsOffline) - result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"], - use_locking=False) - offline = [row[0] for row in result if row[1]] if offline and not nowarn: - ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline)) - return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])] + ToStderr("Note: skipping offline node(s): %s" % + utils.CommaJoin(map(_GetName, offline))) + + if secondary_ips: + fn = _GetSip + else: + fn = _GetName + + return map(fn, online) def _ToStream(stream, txt, *args): diff --git a/test/ganeti.cli_unittest.py b/test/ganeti.cli_unittest.py index a587a1786..1f5b3d0e4 100755 --- a/test/ganeti.cli_unittest.py +++ b/test/ganeti.cli_unittest.py @@ -32,6 +32,7 @@ from ganeti import cli from ganeti import errors from ganeti import utils from ganeti import objects +from ganeti import qlang from ganeti.errors import OpPrereqError, ParameterError @@ -753,5 +754,147 @@ class TestFormatResultError(unittest.TestCase): self.assertTrue(result.endswith(")")) +class TestGetOnlineNodes(unittest.TestCase): + class _FakeClient: + def __init__(self): + self._query = [] + + def AddQueryResult(self, *args): + self._query.append(args) + + def CountPending(self): + return len(self._query) + + def Query(self, res, fields, filter_): + if res != constants.QR_NODE: + raise Exception("Querying wrong resource") + + (exp_fields, check_filter, result) = self._query.pop(0) + + if exp_fields != fields: + raise Exception("Expected fields %s, got %s" % (exp_fields, fields)) + + if not (filter_ is None or check_filter(filter_)): + raise Exception("Filter doesn't match expectations") + + return objects.QueryResponse(fields=None, data=result) + + def testEmpty(self): + cl = self._FakeClient() + + cl.AddQueryResult(["name", "offline", "sip"], None, []) + self.assertEqual(cli.GetOnlineNodes(None, cl=cl), []) + self.assertEqual(cl.CountPending(), 0) + + def testNoSpecialFilter(self): + cl = self._FakeClient() + + cl.AddQueryResult(["name", "offline", "sip"], None, [ + [(constants.RS_NORMAL, "master.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.1")], + [(constants.RS_NORMAL, "node2.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.2")], + ]) + self.assertEqual(cli.GetOnlineNodes(None, cl=cl), + ["master.example.com", "node2.example.com"]) + self.assertEqual(cl.CountPending(), 0) + + def testNoMaster(self): + cl = self._FakeClient() + + def _CheckFilter(filter_): + self.assertEqual(filter_, [qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) + return True + + cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [ + [(constants.RS_NORMAL, "node2.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.2")], + ]) + self.assertEqual(cli.GetOnlineNodes(None, cl=cl, filter_master=True), + ["node2.example.com"]) + self.assertEqual(cl.CountPending(), 0) + + def testSecondaryIpAddress(self): + cl = self._FakeClient() + + cl.AddQueryResult(["name", "offline", "sip"], None, [ + [(constants.RS_NORMAL, "master.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.1")], + [(constants.RS_NORMAL, "node2.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.2")], + ]) + self.assertEqual(cli.GetOnlineNodes(None, cl=cl, secondary_ips=True), + ["192.0.2.1", "192.0.2.2"]) + self.assertEqual(cl.CountPending(), 0) + + def testNoMasterFilterNodeName(self): + cl = self._FakeClient() + + def _CheckFilter(filter_): + self.assertEqual(filter_, + [qlang.OP_AND, + [qlang.OP_OR] + [[qlang.OP_EQUAL, "name", name] + for name in ["node2", "node3"]], + [qlang.OP_NOT, [qlang.OP_TRUE, "master"]]]) + return True + + cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [ + [(constants.RS_NORMAL, "node2.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.12")], + [(constants.RS_NORMAL, "node3.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.13")], + ]) + self.assertEqual(cli.GetOnlineNodes(["node2", "node3"], cl=cl, + secondary_ips=True, filter_master=True), + ["192.0.2.12", "192.0.2.13"]) + self.assertEqual(cl.CountPending(), 0) + + def testOfflineNodes(self): + cl = self._FakeClient() + + cl.AddQueryResult(["name", "offline", "sip"], None, [ + [(constants.RS_NORMAL, "master.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.1")], + [(constants.RS_NORMAL, "node2.example.com"), + (constants.RS_NORMAL, True), + (constants.RS_NORMAL, "192.0.2.2")], + [(constants.RS_NORMAL, "node3.example.com"), + (constants.RS_NORMAL, True), + (constants.RS_NORMAL, "192.0.2.3")], + ]) + self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nowarn=True), + ["master.example.com"]) + self.assertEqual(cl.CountPending(), 0) + + def testNodeGroup(self): + cl = self._FakeClient() + + def _CheckFilter(filter_): + self.assertEqual(filter_, + [qlang.OP_OR, [qlang.OP_EQUAL, "group", "foobar"], + [qlang.OP_EQUAL, "group.uuid", "foobar"]]) + return True + + cl.AddQueryResult(["name", "offline", "sip"], _CheckFilter, [ + [(constants.RS_NORMAL, "master.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.1")], + [(constants.RS_NORMAL, "node3.example.com"), + (constants.RS_NORMAL, False), + (constants.RS_NORMAL, "192.0.2.3")], + ]) + self.assertEqual(cli.GetOnlineNodes(None, cl=cl, nodegroup="foobar"), + ["master.example.com", "node3.example.com"]) + self.assertEqual(cl.CountPending(), 0) + + if __name__ == '__main__': testutils.GanetiTestProgram() -- GitLab