diff --git a/lib/cmdlib.py b/lib/cmdlib.py index edc91358ce2e85e517298f808b5fb2a0b6af9fcc..2e52a59868a9a6b1ac12b745a42d25baae3e6a4e 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -3868,6 +3868,7 @@ class _InstanceQuery(_QueryBase): """Computes the list of instances and their attributes. """ + cluster = lu.cfg.GetClusterInfo() all_info = lu.cfg.GetAllInstancesInfo() instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE) @@ -3881,7 +3882,7 @@ class _InstanceQuery(_QueryBase): wrongnode_inst = set() # Gather data as requested - if query.IQ_LIVE in self.requested_data: + if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]): live_data = {} node_data = lu.rpc.call_all_instances_info(nodes, hv_list) for name in nodes: @@ -3911,9 +3912,21 @@ class _InstanceQuery(_QueryBase): else: disk_usage = None + if query.IQ_CONSOLE in self.requested_data: + consinfo = {} + for inst in instance_list: + if inst.name in live_data: + # Instance is running + consinfo[inst.name] = _GetInstanceConsole(cluster, inst) + else: + consinfo[inst.name] = None + assert set(consinfo.keys()) == set(instance_names) + else: + consinfo = None + return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(), disk_usage, offline_nodes, bad_nodes, - live_data, wrongnode_inst) + live_data, wrongnode_inst, consinfo) class LUQuery(NoHooksLU): @@ -7804,18 +7817,28 @@ class LUInstanceConsole(NoHooksLU): logging.debug("Connecting to console of %s on %s", instance.name, node) - hyper = hypervisor.GetHypervisor(instance.hypervisor) - cluster = self.cfg.GetClusterInfo() - # beparams and hvparams are passed separately, to avoid editing the - # instance and then saving the defaults in the instance itself. - hvparams = cluster.FillHV(instance) - beparams = cluster.FillBE(instance) - console = hyper.GetInstanceConsole(instance, hvparams, beparams) + return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance) + + +def _GetInstanceConsole(cluster, instance): + """Returns console information for an instance. + + @type cluster: L{objects.Cluster} + @type instance: L{objects.Instance} + @rtype: dict + + """ + hyper = hypervisor.GetHypervisor(instance.hypervisor) + # beparams and hvparams are passed separately, to avoid editing the + # instance and then saving the defaults in the instance itself. + hvparams = cluster.FillHV(instance) + beparams = cluster.FillBE(instance) + console = hyper.GetInstanceConsole(instance, hvparams, beparams) - assert console.instance == instance.name - assert console.Validate() + assert console.instance == instance.name + assert console.Validate() - return console.ToDict() + return console.ToDict() class LUInstanceReplaceDisks(LogicalUnit): diff --git a/lib/query.py b/lib/query.py index 1bbdcee7523fc2e5ea64ab33c323eb94f05c3954..8d8b6676bc2b6afe60874a2ce48afb5736625a6b 100644 --- a/lib/query.py +++ b/lib/query.py @@ -79,7 +79,8 @@ from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER, (IQ_CONFIG, IQ_LIVE, - IQ_DISKUSAGE) = range(100, 103) + IQ_DISKUSAGE, + IQ_CONSOLE) = range(100, 104) (LQ_MODE, LQ_OWNER, @@ -664,7 +665,7 @@ class InstanceQueryData: """ def __init__(self, instances, cluster, disk_usage, offline_nodes, bad_nodes, - live_data, wrongnode_inst): + live_data, wrongnode_inst, console): """Initializes this class. @param instances: List of instance objects @@ -679,6 +680,8 @@ class InstanceQueryData: @param live_data: Per-instance live data @type wrongnode_inst: set @param wrongnode_inst: Set of instances running on wrong node(s) + @type console: dict; instance name as key + @param console: Per-instance console information """ assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \ @@ -693,6 +696,7 @@ class InstanceQueryData: self.bad_nodes = bad_nodes self.live_data = live_data self.wrongnode_inst = wrongnode_inst + self.console = console # Used for individual rows self.inst_hvparams = None @@ -991,6 +995,22 @@ def _GetInstDiskUsage(ctx, inst): return usage +def _GetInstanceConsole(ctx, inst): + """Get console information for instance. + + @type ctx: L{InstanceQueryData} + @type inst: L{objects.Instance} + @param inst: Instance object + + """ + consinfo = ctx.console[inst.name] + + if consinfo is None: + return _FS_UNAVAIL + + return consinfo + + def _GetInstanceDiskFields(): """Get instance fields involving disks. @@ -1108,6 +1128,8 @@ def _BuildInstanceFields(): _GetItemAttr("admin_up")), (_MakeField("tags", "Tags", QFT_OTHER), IQ_CONFIG, lambda ctx, inst: list(inst.GetTags())), + (_MakeField("console", "Console", QFT_OTHER), IQ_CONSOLE, + _GetInstanceConsole), ] # Add simple fields diff --git a/test/ganeti.query_unittest.py b/test/ganeti.query_unittest.py index 0af589db36009b4a6c7d7cac55f7a4b082d99128..db66405d28d25c4cc551173a8d68a5a603d95718 100755 --- a/test/ganeti.query_unittest.py +++ b/test/ganeti.query_unittest.py @@ -539,7 +539,8 @@ class TestInstanceQuery(unittest.TestCase): nics=[objects.NIC(ip="192.0.2.99", nicparams={})]), ] - iqd = query.InstanceQueryData(instances, cluster, None, [], [], {}, set()) + iqd = query.InstanceQueryData(instances, cluster, None, [], [], {}, + set(), {}) self.assertEqual(q.Query(iqd), [[(constants.RS_NORMAL, "inst1"), (constants.RS_NORMAL, 128), @@ -567,7 +568,8 @@ class TestInstanceQuery(unittest.TestCase): q = self._Create(selected) self.assertEqual(q.RequestedData(), - set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE])) + set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE, + query.IQ_CONSOLE])) cluster = objects.Cluster(cluster_name="testcluster", hvparams=constants.HVC_DEFAULTS, @@ -671,6 +673,8 @@ class TestInstanceQuery(unittest.TestCase): assert not utils.FindDuplicates(inst.name for inst in instances) + instbyname = dict((inst.name, inst) for inst in instances) + disk_usage = dict((inst.name, cmdlib._ComputeDiskSize(inst.disk_template, [{"size": disk.size} @@ -696,9 +700,16 @@ class TestInstanceQuery(unittest.TestCase): } wrongnode_inst = set("inst2") + consinfo = dict((inst.name, None) for inst in instances) + consinfo["inst7"] = \ + objects.InstanceConsole(instance="inst7", kind=constants.CONS_SSH, + host=instbyname["inst7"].primary_node, + user=constants.GANETI_RUNAS, + command=["hostname"]).ToDict() + iqd = query.InstanceQueryData(instances, cluster, disk_usage, offline_nodes, bad_nodes, live_data, - wrongnode_inst) + wrongnode_inst, consinfo) result = q.Query(iqd) self.assertEqual(len(result), len(instances)) self.assert_(compat.all(len(row) == len(selected) @@ -792,12 +803,23 @@ class TestInstanceQuery(unittest.TestCase): exp = (constants.RS_NORMAL, getattr(inst, field)) self.assertEqual(row[fieldidx[field]], exp) + self._CheckInstanceConsole(inst, row[fieldidx["console"]]) + # Ensure all possible status' have been tested self.assertEqual(tested_status, set(["ERROR_nodeoffline", "ERROR_nodedown", "running", "ERROR_up", "ERROR_down", "ADMIN_down"])) + def _CheckInstanceConsole(self, instance, (status, consdata)): + if instance.name == "inst7": + self.assertEqual(status, constants.RS_NORMAL) + console = objects.InstanceConsole.FromDict(consdata) + self.assertTrue(console.Validate()) + self.assertEqual(console.host, instance.primary_node) + else: + self.assertEqual(status, constants.RS_UNAVAIL) + class TestGroupQuery(unittest.TestCase):