summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/tests/virt/xenapi/test_vmops.py75
-rw-r--r--nova/tests/virt/xenapi/test_xenapi.py11
-rw-r--r--nova/virt/xenapi/fake.py8
-rw-r--r--nova/virt/xenapi/vmops.py26
-rw-r--r--plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec1
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/console80
6 files changed, 195 insertions, 6 deletions
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
diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
index b93c7b071..85c2d1c05 100644
--- a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
+++ b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
@@ -32,6 +32,7 @@ rm -rf $RPM_BUILD_ROOT
/etc/xapi.d/plugins/bandwidth
/etc/xapi.d/plugins/bittorrent
/etc/xapi.d/plugins/config_file
+/etc/xapi.d/plugins/console
/etc/xapi.d/plugins/glance
/etc/xapi.d/plugins/kernel
/etc/xapi.d/plugins/migration
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/console b/plugins/xenserver/xenapi/etc/xapi.d/plugins/console
new file mode 100755
index 000000000..afcb783f7
--- /dev/null
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/console
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+To configure this plugin, you must set the following xenstore key:
+/local/logconsole/@ = "/var/log/xen/guest/console.%d"
+
+This can be done by running:
+xenstore-write /local/logconsole/@ "/var/log/xen/guest/console.%d"
+
+WARNING:
+You should ensure appropriate log rotation to ensure
+guests are not able to consume too much Dom0 disk space,
+and equally should not be able to stop other guests from logging.
+Adding and removing the following xenstore key will reopen the log,
+as will be required after a log rotate:
+/local/logconsole/<dom_id>
+"""
+
+import base64
+import logging
+import os
+import zlib
+
+import XenAPIPlugin
+
+import pluginlib_nova
+pluginlib_nova.configure_logging("console")
+
+CONSOLE_LOG_DIR = '/var/log/xen/guest'
+CONSOLE_LOG_FILE_PATTERN = CONSOLE_LOG_DIR + '/console.%d'
+
+MAX_CONSOLE_BYTES = 102400
+SEEK_SET = 0
+SEEK_END = 2
+
+
+def _last_bytes(file_like_object):
+ try:
+ file_like_object.seek(-MAX_CONSOLE_BYTES, SEEK_END)
+ except IOError, e:
+ if e.errno == 22:
+ file_like_object.seek(0, SEEK_SET)
+ else:
+ raise
+ return file_like_object.read()
+
+
+def get_console_log(session, arg_dict):
+ try:
+ raw_dom_id = arg_dict['dom_id']
+ except KeyError:
+ raise pluginlib_nova.PluginError("Missing dom_id")
+ try:
+ dom_id = int(raw_dom_id)
+ except ValueError:
+ raise pluginlib_nova.PluginError("Invalid dom_id")
+
+ logfile = CONSOLE_LOG_FILE_PATTERN % dom_id
+ try:
+ log_content = pluginlib_nova.with_file(logfile, 'rb', _last_bytes)
+ except IOError, e:
+ msg = "Error reading console: %s" % e
+ logging.debug(msg)
+ raise pluginlib_nova.PluginError(msg)
+ return base64.b64encode(zlib.compress(log_content))
+
+
+if __name__ == "__main__":
+ XenAPIPlugin.dispatch({"get_console_log": get_console_log})