From 6e93fefade26ddb6282e423af51f806a08efc8b0 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 23 May 2013 17:42:51 +0100 Subject: xenapi: implement get_console_output for XCP/XenServer If an administrator has enabled the logging of guest consoles on XCP/XenServer, this enables nova to return the last MB of those logs to the user. The management of the logs on the server is a little tricky, and will be sorted in a later patch. Change was based on this previous idea: https://review.openstack.org/#/c/17959/ DocImpact Part of blueprint xenapi-server-log Change-Id: I23c83bcf8c648cc2714a0c78951acc29a16d5c31 --- nova/tests/virt/xenapi/test_vmops.py | 75 +++++++++++++++++++++++++++++++++++ nova/tests/virt/xenapi/test_xenapi.py | 11 +++++ nova/virt/xenapi/fake.py | 8 ++++ nova/virt/xenapi/vmops.py | 26 +++++++++--- 4 files changed, 114 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/tests/virt/xenapi/test_vmops.py b/nova/tests/virt/xenapi/test_vmops.py index 18a444f41..674d84882 100644 --- a/nova/tests/virt/xenapi/test_vmops.py +++ b/nova/tests/virt/xenapi/test_vmops.py @@ -18,8 +18,12 @@ from nova.compute import task_states from nova.compute import vm_mode +from nova import exception from nova import test +from nova.tests.virt.xenapi import stubs from nova.virt import fake +from nova.virt.xenapi import driver as xenapi_conn +from nova.virt.xenapi import fake as xenapi_fake from nova.virt.xenapi import vm_utils from nova.virt.xenapi import vmops @@ -166,3 +170,74 @@ class VMOpsTestCase(test.TestCase): self.assertTrue(self._vmops._is_xsm_sr_check_relaxed()) self.assertEqual(self.make_plugin_call_count, 1) + + +class GetConsoleOutputTestCase(stubs.XenAPITestBase): + def setUp(self): + super(GetConsoleOutputTestCase, self).setUp() + stubs.stubout_session(self.stubs, xenapi_fake.SessionBase) + self._session = xenapi_conn.XenAPISession('test_url', 'root', + 'test_pass', fake.FakeVirtAPI()) + self.vmops = vmops.VMOps(self._session, fake.FakeVirtAPI()) + self.vms = [] + + def tearDown(self): + super(GetConsoleOutputTestCase, self).tearDown() + for vm in self.vms: + xenapi_fake.destroy_vm(vm) + + def _create_vm(self, name, state): + vm = xenapi_fake.create_vm(name, state) + self.vms.append(vm) + return vm + + def test_get_console_output_works(self): + self.mox.StubOutWithMock(self.vmops, '_get_dom_id') + + instance = {"name": "dummy"} + self.vmops._get_dom_id(instance, check_rescue=True).AndReturn(42) + self.mox.ReplayAll() + + self.assertEqual("dom_id: 42", self.vmops.get_console_output(instance)) + + def test_get_console_output_throws_nova_exception(self): + self.mox.StubOutWithMock(self.vmops, '_get_dom_id') + + instance = {"name": "dummy"} + # dom_id=0 used to trigger exception in fake XenAPI + self.vmops._get_dom_id(instance, check_rescue=True).AndReturn(0) + self.mox.ReplayAll() + + self.assertRaises(exception.NovaException, + self.vmops.get_console_output, instance) + + def test_get_dom_id_works(self): + instance = {"name": "dummy"} + vm_ref = self._create_vm("dummy", "Running") + vm_rec = xenapi_fake.get_record("VM", vm_ref) + + self.assertEqual(vm_rec["domid"], self.vmops._get_dom_id(instance)) + + def test_get_dom_id_works_with_rescue_vm(self): + instance = {"name": "dummy"} + vm_ref = self._create_vm("dummy-rescue", "Running") + vm_rec = xenapi_fake.get_record("VM", vm_ref) + + self.assertEqual(vm_rec["domid"], + self.vmops._get_dom_id(instance, check_rescue=True)) + + def test_get_dom_id_raises_not_found(self): + instance = {"name": "dummy"} + vm_ref = self._create_vm("notdummy", "Running") + vm_rec = xenapi_fake.get_record("VM", vm_ref) + + self.assertRaises(exception.NotFound, + self.vmops._get_dom_id, instance) + + def test_get_dom_id_works_with_vmref(self): + instance = {"name": "dummy"} + vm_ref = self._create_vm("dummy", "Running") + vm_rec = xenapi_fake.get_record("VM", vm_ref) + + self.assertEqual(vm_rec["domid"], + self.vmops._get_dom_id(vm_ref=vm_ref)) diff --git a/nova/tests/virt/xenapi/test_xenapi.py b/nova/tests/virt/xenapi/test_xenapi.py index 7dcb1cec8..d99fdcb8e 100644 --- a/nova/tests/virt/xenapi/test_xenapi.py +++ b/nova/tests/virt/xenapi/test_xenapi.py @@ -1121,6 +1121,17 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): conn.reboot(self.context, instance, None, "SOFT") + def test_get_console_output_succeeds(self): + + def fake_get_console_output(instance): + self.assertEqual("instance", instance) + return "console_log" + self.stubs.Set(self.conn._vmops, 'get_console_output', + fake_get_console_output) + + self.assertEqual(self.conn.get_console_output("instance"), + "console_log") + def _test_maintenance_mode(self, find_host, find_aggregate): real_call_xenapi = self.conn._session.call_xenapi instance = self._create_instance(spawn=True) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 69b0c2010..379671370 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -50,10 +50,12 @@ A fake XenAPI SDK. """ +import base64 import pickle import random import uuid from xml.sax import saxutils +import zlib import pprint @@ -608,6 +610,12 @@ class SessionBase(object): def _plugin_xenhost_host_uptime(self, method, args): return jsonutils.dumps({"uptime": "fake uptime"}) + def _plugin_console_get_console_log(self, method, args): + dom_id = args["dom_id"] + if dom_id == 0: + raise Failure('Guest does not have a console') + return base64.b64encode(zlib.compress("dom_id: %s" % dom_id)) + def host_call_plugin(self, _1, _2, plugin, method, args): func = getattr(self, '_plugin_%s_%s' % (plugin, method), None) if not func: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 56a4b18d8..bf8aa3e52 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -19,9 +19,11 @@ Management class for VM-related functions (spawn, reboot, etc). """ +import base64 import functools import itertools import time +import zlib from eventlet import greenthread import netaddr @@ -1421,9 +1423,18 @@ class VMOps(object): return bw def get_console_output(self, instance): - """Return snapshot of console.""" - # TODO(armando-migliaccio): implement this to fix pylint! - return 'FAKE CONSOLE OUTPUT of instance' + """Return last few lines of instance console.""" + dom_id = self._get_dom_id(instance, check_rescue=True) + + try: + raw_console_data = self._session.call_plugin('console', + 'get_console_log', {'dom_id': dom_id}) + except self._session.XenAPI.Failure as exc: + LOG.exception(exc) + msg = _("Guest does not have a console available") + raise exception.NovaException(msg) + + return zlib.decompress(base64.b64decode(raw_console_data)) def get_vnc_console(self, instance): """Return connection info for a vnc console.""" @@ -1607,9 +1618,7 @@ class VMOps(object): """ args = {} if instance or vm_ref: - vm_ref = vm_ref or self._get_vm_opaque_ref(instance) - vm_rec = self._session.call_xenapi("VM.get_record", vm_ref) - args['dom_id'] = vm_rec['domid'] + args['dom_id'] = self._get_dom_id(instance, vm_ref) args.update(addl_args) try: return self._session.call_plugin(plugin, method, args) @@ -1630,6 +1639,11 @@ class VMOps(object): return {'returncode': 'error', 'message': err_msg} return None + def _get_dom_id(self, instance=None, vm_ref=None, check_rescue=False): + vm_ref = vm_ref or self._get_vm_opaque_ref(instance, check_rescue) + vm_rec = self._session.call_xenapi("VM.get_record", vm_ref) + return vm_rec['domid'] + def _add_to_param_xenstore(self, vm_ref, key, val): """ Takes a key/value pair and adds it to the xenstore parameter -- cgit