summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/cmd/baremetal_deploy_helper.py2
-rwxr-xr-xnova/compute/manager.py13
-rw-r--r--nova/db/sqlalchemy/models.py152
-rw-r--r--nova/objects/instance.py54
-rw-r--r--nova/tests/objects/test_instance.py114
-rw-r--r--nova/tests/virt/libvirt/test_libvirt_config.py49
-rw-r--r--nova/tests/virt/libvirt/test_libvirt_volume.py26
-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/libvirt/config.py16
-rw-r--r--nova/virt/libvirt/volume.py8
-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
15 files changed, 548 insertions, 87 deletions
diff --git a/nova/cmd/baremetal_deploy_helper.py b/nova/cmd/baremetal_deploy_helper.py
index d63d06ee9..539d5b517 100644
--- a/nova/cmd/baremetal_deploy_helper.py
+++ b/nova/cmd/baremetal_deploy_helper.py
@@ -201,6 +201,7 @@ def deploy(address, port, iqn, lun, image_path, pxe_config_path,
login_iscsi(address, port, iqn)
try:
root_uuid = work_on_disk(dev, root_mb, swap_mb, image_path)
+ switch_pxe_config(pxe_config_path, root_uuid)
except processutils.ProcessExecutionError as err:
# Log output if there was a error
LOG.error("Cmd : %s" % err.cmd)
@@ -208,7 +209,6 @@ def deploy(address, port, iqn, lun, image_path, pxe_config_path,
LOG.error("StdErr : %s" % err.stderr)
finally:
logout_iscsi(address, port, iqn)
- switch_pxe_config(pxe_config_path, root_uuid)
# Ensure the node started netcat on the port after POST the request.
time.sleep(3)
notify(address, 10000)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 5c697993b..8364722e4 100755
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -394,8 +394,10 @@ class ComputeManager(manager.SchedulerDependentManager):
instance_uuid=instance_uuid)
def _get_instances_on_driver(self, context, filters=None):
- """Return a list of instance records that match the instances found
- on the hypervisor.
+ """Return a list of instance records for the instances found
+ on the hypervisor which satisfy the specified filters. If filters=None
+ return a list of instance records for all the instances found on the
+ hypervisor.
"""
if not filters:
filters = {}
@@ -404,10 +406,6 @@ class ComputeManager(manager.SchedulerDependentManager):
filters['uuid'] = driver_uuids
local_instances = self.conductor_api.instance_get_all_by_filters(
context, filters, columns_to_join=[])
- local_instance_uuids = [inst['uuid'] for inst in local_instances]
- for uuid in set(driver_uuids) - set(local_instance_uuids):
- LOG.error(_('Instance %(uuid)s found in the hypervisor, but '
- 'not in the database'), locals())
return local_instances
except NotImplementedError:
pass
@@ -422,9 +420,6 @@ class ComputeManager(manager.SchedulerDependentManager):
for driver_instance in driver_instances:
instance = name_map.get(driver_instance)
if not instance:
- LOG.error(_('Instance %(driver_instance)s found in the '
- 'hypervisor, but not in the database'),
- locals())
continue
local_instances.append(instance)
return local_instances
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index d12e47246..99a68d2cf 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -21,7 +21,7 @@
SQLAlchemy models for nova data.
"""
-from sqlalchemy import Column, Integer, BigInteger, String, schema
+from sqlalchemy import Column, Index, Integer, BigInteger, String, schema
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float
from sqlalchemy.orm import relationship, backref, object_mapper
@@ -60,8 +60,9 @@ class ComputeNode(BASE, NovaBase):
"""Represents a running compute service on a host."""
__tablename__ = 'compute_nodes'
+ __table_args__ = ()
id = Column(Integer, primary_key=True)
- service_id = Column(Integer, ForeignKey('services.id'), nullable=True)
+ service_id = Column(Integer, ForeignKey('services.id'), nullable=False)
service = relationship(Service,
backref=backref('compute_node'),
foreign_keys=service_id,
@@ -69,23 +70,23 @@ class ComputeNode(BASE, NovaBase):
'ComputeNode.service_id == Service.id,'
'ComputeNode.deleted == 0)')
- vcpus = Column(Integer)
- memory_mb = Column(Integer)
- local_gb = Column(Integer)
- vcpus_used = Column(Integer)
- memory_mb_used = Column(Integer)
- local_gb_used = Column(Integer)
- hypervisor_type = Column(Text)
- hypervisor_version = Column(Integer)
- hypervisor_hostname = Column(String(255))
+ vcpus = Column(Integer, nullable=False)
+ memory_mb = Column(Integer, nullable=False)
+ local_gb = Column(Integer, nullable=False)
+ vcpus_used = Column(Integer, nullable=False)
+ memory_mb_used = Column(Integer, nullable=False)
+ local_gb_used = Column(Integer, nullable=False)
+ hypervisor_type = Column(Text, nullable=False)
+ hypervisor_version = Column(Integer, nullable=False)
+ hypervisor_hostname = Column(String(255), nullable=True)
# Free Ram, amount of activity (resize, migration, boot, etc) and
# the number of running VM's are a good starting point for what's
# important when making scheduling decisions.
- free_ram_mb = Column(Integer)
- free_disk_gb = Column(Integer)
- current_workload = Column(Integer)
- running_vms = Column(Integer)
+ free_ram_mb = Column(Integer, nullable=True)
+ free_disk_gb = Column(Integer, nullable=True)
+ current_workload = Column(Integer, nullable=True)
+ running_vms = Column(Integer, nullable=True)
# Note(masumotok): Expected Strings example:
#
@@ -98,17 +99,24 @@ class ComputeNode(BASE, NovaBase):
# above, since it is copied from <cpu> tag of getCapabilities()
# (See libvirt.virtConnection).
cpu_info = Column(Text, nullable=True)
- disk_available_least = Column(Integer)
+ disk_available_least = Column(Integer, nullable=True)
class ComputeNodeStat(BASE, NovaBase):
"""Stats related to the current workload of a compute host that are
intended to aid in making scheduler decisions."""
__tablename__ = 'compute_node_stats'
+ __table_args__ = (
+ Index('ix_compute_node_stats_compute_node_id', 'compute_node_id'),
+ Index('compute_node_stats_node_id_and_deleted_idx',
+ 'compute_node_id', 'deleted')
+ )
+
id = Column(Integer, primary_key=True)
- key = Column(String(511))
- value = Column(String(255))
- compute_node_id = Column(Integer, ForeignKey('compute_nodes.id'))
+ key = Column(String(511), nullable=False)
+ value = Column(String(255), nullable=False)
+ compute_node_id = Column(Integer, ForeignKey('compute_nodes.id'),
+ nullable=False)
primary_join = ('and_(ComputeNodeStat.compute_node_id == '
'ComputeNode.id, ComputeNodeStat.deleted == 0)')
@@ -122,16 +130,34 @@ class ComputeNodeStat(BASE, NovaBase):
class Certificate(BASE, NovaBase):
"""Represents a x509 certificate."""
__tablename__ = 'certificates'
+ __table_args__ = (
+ Index('certificates_project_id_deleted_idx', 'project_id', 'deleted'),
+ Index('certificates_user_id_deleted_idx', 'user_id', 'deleted')
+ )
id = Column(Integer, primary_key=True)
- user_id = Column(String(255))
- project_id = Column(String(255))
- file_name = Column(String(255))
+ user_id = Column(String(255), nullable=True)
+ project_id = Column(String(255), nullable=True)
+ file_name = Column(String(255), nullable=True)
class Instance(BASE, NovaBase):
"""Represents a guest VM."""
__tablename__ = 'instances'
+ __table_args__ = (
+ Index('instances_host_deleted_idx',
+ 'host', 'deleted'),
+ Index('instances_reservation_id_idx',
+ 'reservation_id'),
+ Index('instances_terminated_at_launched_at_idx',
+ 'terminated_at', 'launched_at'),
+ Index('instances_uuid_deleted_idx',
+ 'uuid', 'deleted'),
+ Index('instances_task_state_updated_at_idx',
+ 'task_state', 'updated_at'),
+ Index('instances_host_node_deleted_idx',
+ 'host', 'node', 'deleted')
+ )
injected_files = []
id = Column(Integer, primary_key=True, autoincrement=True)
@@ -161,87 +187,87 @@ class Instance(BASE, NovaBase):
def _extra_keys(self):
return ['name']
- user_id = Column(String(255))
- project_id = Column(String(255))
+ user_id = Column(String(255), nullable=True)
+ project_id = Column(String(255), nullable=True)
- image_ref = Column(String(255))
- kernel_id = Column(String(255))
- ramdisk_id = Column(String(255))
- hostname = Column(String(255))
+ image_ref = Column(String(255), nullable=True)
+ kernel_id = Column(String(255), nullable=True)
+ ramdisk_id = Column(String(255), nullable=True)
+ hostname = Column(String(255), nullable=True)
- launch_index = Column(Integer)
- key_name = Column(String(255))
+ launch_index = Column(Integer, nullable=True)
+ key_name = Column(String(255), nullable=True)
key_data = Column(Text)
- power_state = Column(Integer)
- vm_state = Column(String(255))
- task_state = Column(String(255))
+ power_state = Column(Integer, nullable=True)
+ vm_state = Column(String(255), nullable=True)
+ task_state = Column(String(255), nullable=True)
- memory_mb = Column(Integer)
- vcpus = Column(Integer)
- root_gb = Column(Integer)
- ephemeral_gb = Column(Integer)
+ memory_mb = Column(Integer, nullable=True)
+ vcpus = Column(Integer, nullable=True)
+ root_gb = Column(Integer, nullable=True)
+ ephemeral_gb = Column(Integer, nullable=True)
# This is not related to hostname, above. It refers
# to the nova node.
- host = Column(String(255)) # , ForeignKey('hosts.id'))
+ host = Column(String(255), nullable=True) # , ForeignKey('hosts.id'))
# To identify the "ComputeNode" which the instance resides in.
# This equals to ComputeNode.hypervisor_hostname.
- node = Column(String(255))
+ node = Column(String(255), nullable=True)
# *not* flavor_id
- instance_type_id = Column(Integer)
+ instance_type_id = Column(Integer, nullable=True)
- user_data = Column(Text)
+ user_data = Column(Text, nullable=True)
- reservation_id = Column(String(255))
+ reservation_id = Column(String(255), nullable=True)
- scheduled_at = Column(DateTime)
- launched_at = Column(DateTime)
- terminated_at = Column(DateTime)
+ scheduled_at = Column(DateTime, nullable=True)
+ launched_at = Column(DateTime, nullable=True)
+ terminated_at = Column(DateTime, nullable=True)
- availability_zone = Column(String(255))
+ availability_zone = Column(String(255), nullable=True)
# User editable field for display in user-facing UIs
- display_name = Column(String(255))
- display_description = Column(String(255))
+ display_name = Column(String(255), nullable=True)
+ display_description = Column(String(255), nullable=True)
# To remember on which host an instance booted.
# An instance may have moved to another host by live migration.
- launched_on = Column(Text)
- locked = Column(Boolean)
+ launched_on = Column(Text, nullable=True)
+ locked = Column(Boolean, nullable=True)
- os_type = Column(String(255))
- architecture = Column(String(255))
- vm_mode = Column(String(255))
- uuid = Column(String(36))
+ os_type = Column(String(255), nullable=True)
+ architecture = Column(String(255), nullable=True)
+ vm_mode = Column(String(255), nullable=True)
+ uuid = Column(String(36), unique=True)
- root_device_name = Column(String(255))
+ root_device_name = Column(String(255), nullable=True)
default_ephemeral_device = Column(String(255), nullable=True)
default_swap_device = Column(String(255), nullable=True)
- config_drive = Column(String(255))
+ config_drive = Column(String(255), nullable=True)
# User editable field meant to represent what ip should be used
# to connect to the instance
- access_ip_v4 = Column(types.IPAddress())
- access_ip_v6 = Column(types.IPAddress())
+ access_ip_v4 = Column(types.IPAddress(), nullable=True)
+ access_ip_v6 = Column(types.IPAddress(), nullable=True)
- auto_disk_config = Column(Boolean())
- progress = Column(Integer)
+ auto_disk_config = Column(Boolean(), nullable=True)
+ progress = Column(Integer, nullable=True)
# EC2 instance_initiated_shutdown_terminate
# True: -> 'terminate'
# False: -> 'stop'
# Note(maoy): currently Nova will always stop instead of terminate
# no matter what the flag says. So we set the default to False.
- shutdown_terminate = Column(Boolean(), default=False, nullable=False)
+ shutdown_terminate = Column(Boolean(), default=False, nullable=True)
# EC2 disable_api_termination
- disable_terminate = Column(Boolean(), default=False, nullable=False)
+ disable_terminate = Column(Boolean(), default=False, nullable=True)
# OpenStack compute cell name. This will only be set at the top of
# the cells tree and it'll be a full cell name such as 'api!hop1!hop2'
- cell_name = Column(String(255))
+ cell_name = Column(String(255), nullable=True)
class InstanceInfoCache(BASE, NovaBase):
diff --git a/nova/objects/instance.py b/nova/objects/instance.py
index 0489f1374..58c581542 100644
--- a/nova/objects/instance.py
+++ b/nova/objects/instance.py
@@ -239,3 +239,57 @@ class Instance(base.NovaObject):
uuid=self.uuid,
expected_attrs=extra)
self[attrname] = instance[attrname]
+
+
+def _make_instance_list(context, inst_list, db_inst_list, expected_attrs):
+ inst_list.objects = []
+ for db_inst in db_inst_list:
+ inst_obj = Instance._from_db_object(Instance(), db_inst,
+ expected_attrs=expected_attrs)
+ inst_obj._context = context
+ inst_list.objects.append(inst_obj)
+ inst_list.obj_reset_changes()
+ return inst_list
+
+
+class InstanceList(base.ObjectListBase, base.NovaObject):
+ @base.remotable_classmethod
+ def get_by_filters(cls, context, filters,
+ sort_key=None, sort_dir=None, limit=None, marker=None,
+ expected_attrs=None):
+ db_inst_list = db.instance_get_all_by_filters(
+ context, filters, sort_key, sort_dir, limit, marker,
+ columns_to_join=expected_attrs)
+
+ return _make_instance_list(context, cls(), db_inst_list,
+ expected_attrs)
+
+ @base.remotable_classmethod
+ def get_by_host(cls, context, host, expected_attrs=None):
+ db_inst_list = db.instance_get_all_by_host(
+ context, host, columns_to_join=expected_attrs)
+ return _make_instance_list(context, cls(), db_inst_list,
+ expected_attrs)
+
+ @base.remotable_classmethod
+ def get_by_host_and_node(cls, context, host, node, expected_attrs=None):
+ db_inst_list = db.instance_get_all_by_host_and_node(
+ context, host, node)
+ return _make_instance_list(context, cls(), db_inst_list,
+ expected_attrs)
+
+ @base.remotable_classmethod
+ def get_by_host_and_not_type(cls, context, host, type_id=None,
+ expected_attrs=None):
+ db_inst_list = db.instance_get_all_by_host_and_not_type(
+ context, host, type_id=type_id)
+ return _make_instance_list(context, cls(), db_inst_list,
+ expected_attrs)
+
+ @base.remotable_classmethod
+ def get_hung_in_rebooting(cls, context, reboot_window,
+ expected_attrs=None):
+ db_inst_list = db.instance_get_all_hung_in_rebooting(context,
+ reboot_window)
+ return _make_instance_list(context, cls(), db_inst_list,
+ expected_attrs)
diff --git a/nova/tests/objects/test_instance.py b/nova/tests/objects/test_instance.py
index 194f5f90c..54e010a8a 100644
--- a/nova/tests/objects/test_instance.py
+++ b/nova/tests/objects/test_instance.py
@@ -196,3 +196,117 @@ class TestInstanceObject(test_objects._LocalTest,
class TestRemoteInstanceObject(test_objects._RemoteTest,
_TestInstanceObject):
pass
+
+
+class _TestInstanceListObject(object):
+ def fake_instance(self, id, updates=None):
+ fake_instance = fakes.stub_instance(id=2,
+ access_ipv4='1.2.3.4',
+ access_ipv6='::1')
+ fake_instance['scheduled_at'] = None
+ fake_instance['terminated_at'] = None
+ fake_instance['deleted_at'] = None
+ fake_instance['created_at'] = None
+ fake_instance['updated_at'] = None
+ fake_instance['launched_at'] = (
+ fake_instance['launched_at'].replace(
+ tzinfo=iso8601.iso8601.Utc(), microsecond=0))
+ fake_instance['info_cache'] = {'network_info': 'foo',
+ 'instance_uuid': fake_instance['uuid']}
+ fake_instance['deleted'] = 0
+ if updates:
+ fake_instance.update(updates)
+ return fake_instance
+
+ def test_get_all_by_filters(self):
+ fakes = [self.fake_instance(1), self.fake_instance(2)]
+ ctxt = context.get_admin_context()
+ self.mox.StubOutWithMock(db, 'instance_get_all_by_filters')
+ db.instance_get_all_by_filters(ctxt, {'foo': 'bar'}, 'uuid', 'asc',
+ None, None,
+ columns_to_join=['metadata']).AndReturn(
+ fakes)
+ self.mox.ReplayAll()
+ inst_list = instance.InstanceList.get_by_filters(
+ ctxt, {'foo': 'bar'}, 'uuid', 'asc', expected_attrs=['metadata'])
+
+ for i in range(0, len(fakes)):
+ self.assertTrue(isinstance(inst_list.objects[i],
+ instance.Instance))
+ self.assertEqual(inst_list.objects[i].uuid, fakes[i]['uuid'])
+ self.assertRemotes()
+
+ def test_get_by_host(self):
+ fakes = [self.fake_instance(1),
+ self.fake_instance(2)]
+ ctxt = context.get_admin_context()
+ self.mox.StubOutWithMock(db, 'instance_get_all_by_host')
+ db.instance_get_all_by_host(ctxt, 'foo',
+ columns_to_join=None).AndReturn(fakes)
+ self.mox.ReplayAll()
+ inst_list = instance.InstanceList.get_by_host(ctxt, 'foo')
+ for i in range(0, len(fakes)):
+ self.assertTrue(isinstance(inst_list.objects[i],
+ instance.Instance))
+ self.assertEqual(inst_list.objects[i].uuid, fakes[i]['uuid'])
+ self.assertEqual(inst_list.obj_what_changed(), set())
+ self.assertRemotes()
+
+ def test_get_by_host_and_node(self):
+ fakes = [self.fake_instance(1),
+ self.fake_instance(2)]
+ ctxt = context.get_admin_context()
+ self.mox.StubOutWithMock(db, 'instance_get_all_by_host_and_node')
+ db.instance_get_all_by_host_and_node(ctxt, 'foo', 'bar').AndReturn(
+ fakes)
+ self.mox.ReplayAll()
+ inst_list = instance.InstanceList.get_by_host_and_node(ctxt, 'foo',
+ 'bar')
+ for i in range(0, len(fakes)):
+ self.assertTrue(isinstance(inst_list.objects[i],
+ instance.Instance))
+ self.assertEqual(inst_list.objects[i].uuid, fakes[i]['uuid'])
+ self.assertRemotes()
+
+ def test_get_by_host_and_not_type(self):
+ fakes = [self.fake_instance(1),
+ self.fake_instance(2)]
+ ctxt = context.get_admin_context()
+ self.mox.StubOutWithMock(db, 'instance_get_all_by_host_and_not_type')
+ db.instance_get_all_by_host_and_not_type(ctxt, 'foo',
+ type_id='bar').AndReturn(
+ fakes)
+ self.mox.ReplayAll()
+ inst_list = instance.InstanceList.get_by_host_and_not_type(ctxt, 'foo',
+ 'bar')
+ for i in range(0, len(fakes)):
+ self.assertTrue(isinstance(inst_list.objects[i],
+ instance.Instance))
+ self.assertEqual(inst_list.objects[i].uuid, fakes[i]['uuid'])
+ self.assertRemotes()
+
+ def test_get_hung_in_rebooting(self):
+ fakes = [self.fake_instance(1),
+ self.fake_instance(2)]
+ dt = timeutils.isotime()
+ ctxt = context.get_admin_context()
+ self.mox.StubOutWithMock(db, 'instance_get_all_hung_in_rebooting')
+ db.instance_get_all_hung_in_rebooting(ctxt, dt).AndReturn(
+ fakes)
+ self.mox.ReplayAll()
+ inst_list = instance.InstanceList.get_hung_in_rebooting(ctxt, dt)
+ for i in range(0, len(fakes)):
+ self.assertTrue(isinstance(inst_list.objects[i],
+ instance.Instance))
+ self.assertEqual(inst_list.objects[i].uuid, fakes[i]['uuid'])
+ self.assertRemotes()
+
+
+class TestInstanceListObject(test_objects._LocalTest,
+ _TestInstanceListObject):
+ pass
+
+
+class TestRemoteInstanceListObject(test_objects._RemoteTest,
+ _TestInstanceListObject):
+ pass
diff --git a/nova/tests/virt/libvirt/test_libvirt_config.py b/nova/tests/virt/libvirt/test_libvirt_config.py
index 8eed7136e..2d9e52f3c 100644
--- a/nova/tests/virt/libvirt/test_libvirt_config.py
+++ b/nova/tests/virt/libvirt/test_libvirt_config.py
@@ -434,7 +434,7 @@ class LibvirtConfigGuestDiskTest(LibvirtConfigBaseTest):
obj = config.LibvirtConfigGuestDisk()
obj.source_type = "network"
obj.source_protocol = "iscsi"
- obj.source_host = "foo.bar.com"
+ obj.source_name = "foo.bar.com"
obj.driver_name = "qemu"
obj.driver_format = "qcow2"
obj.target_dev = "/dev/hda"
@@ -448,11 +448,56 @@ class LibvirtConfigGuestDiskTest(LibvirtConfigBaseTest):
<target bus="ide" dev="/dev/hda"/>
</disk>""")
+ def test_config_network_no_name(self):
+ obj = config.LibvirtConfigGuestDisk()
+ obj.source_type = 'network'
+ obj.source_protocol = 'nbd'
+ obj.source_hosts = ['foo.bar.com']
+ obj.source_ports = [None]
+ obj.driver_name = 'qemu'
+ obj.driver_format = 'raw'
+ obj.target_dev = '/dev/vda'
+ obj.target_bus = 'virtio'
+
+ xml = obj.to_xml()
+ self.assertXmlEqual(xml, """
+ <disk type="network" device="disk">
+ <driver name="qemu" type="raw"/>
+ <source protocol="nbd">
+ <host name="foo.bar.com"/>
+ </source>
+ <target bus="virtio" dev="/dev/vda"/>
+ </disk>""")
+
+ def test_config_network_multihost(self):
+ obj = config.LibvirtConfigGuestDisk()
+ obj.source_type = 'network'
+ obj.source_protocol = 'rbd'
+ obj.source_name = 'pool/image'
+ obj.source_hosts = ['foo.bar.com', '::1', '1.2.3.4']
+ obj.source_ports = [None, '123', '456']
+ obj.driver_name = 'qemu'
+ obj.driver_format = 'raw'
+ obj.target_dev = '/dev/vda'
+ obj.target_bus = 'virtio'
+
+ xml = obj.to_xml()
+ self.assertXmlEqual(xml, """
+ <disk type="network" device="disk">
+ <driver name="qemu" type="raw"/>
+ <source name="pool/image" protocol="rbd">
+ <host name="foo.bar.com"/>
+ <host name="::1" port="123"/>
+ <host name="1.2.3.4" port="456"/>
+ </source>
+ <target bus="virtio" dev="/dev/vda"/>
+ </disk>""")
+
def test_config_network_auth(self):
obj = config.LibvirtConfigGuestDisk()
obj.source_type = "network"
obj.source_protocol = "rbd"
- obj.source_host = "pool/image"
+ obj.source_name = "pool/image"
obj.driver_name = "qemu"
obj.driver_format = "raw"
obj.target_dev = "/dev/vda"
diff --git a/nova/tests/virt/libvirt/test_libvirt_volume.py b/nova/tests/virt/libvirt/test_libvirt_volume.py
index 07a1a7b2f..a6dd9e7fe 100644
--- a/nova/tests/virt/libvirt/test_libvirt_volume.py
+++ b/nova/tests/virt/libvirt/test_libvirt_volume.py
@@ -218,6 +218,32 @@ class LibvirtVolumeTestCase(test.TestCase):
self.assertEqual(tree.find('./source/auth'), None)
libvirt_driver.disconnect_volume(connection_info, "vde")
+ def test_libvirt_rbd_driver_hosts(self):
+ libvirt_driver = volume.LibvirtNetVolumeDriver(self.fake_conn)
+ name = 'volume-00000001'
+ vol = {'id': 1, 'name': name}
+ connection_info = self.rbd_connection(vol)
+ disk_info = {
+ "bus": "virtio",
+ "dev": "vde",
+ "type": "disk",
+ }
+ hosts = ['example.com', '1.2.3.4', '::1']
+ ports = [None, '6790', '6791']
+ connection_info['data']['hosts'] = hosts
+ connection_info['data']['ports'] = ports
+ conf = libvirt_driver.connect_volume(connection_info, disk_info)
+ tree = conf.format_dom()
+ self.assertEqual(tree.get('type'), 'network')
+ self.assertEqual(tree.find('./source').get('protocol'), 'rbd')
+ rbd_name = '%s/%s' % ('rbd', name)
+ self.assertEqual(tree.find('./source').get('name'), rbd_name)
+ self.assertEqual(tree.find('./source/auth'), None)
+ found_hosts = tree.findall('./source/host')
+ self.assertEqual([host.get('name') for host in found_hosts], hosts)
+ self.assertEqual([host.get('port') for host in found_hosts], ports)
+ libvirt_driver.disconnect_volume(connection_info, "vde")
+
def test_libvirt_rbd_driver_auth_enabled(self):
libvirt_driver = volume.LibvirtNetVolumeDriver(self.fake_conn)
name = 'volume-00000001'
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/libvirt/config.py b/nova/virt/libvirt/config.py
index 08a0566dd..e5319c514 100644
--- a/nova/virt/libvirt/config.py
+++ b/nova/virt/libvirt/config.py
@@ -460,7 +460,9 @@ class LibvirtConfigGuestDisk(LibvirtConfigGuestDevice):
self.driver_cache = None
self.source_path = None
self.source_protocol = None
- self.source_host = None
+ self.source_name = None
+ self.source_hosts = []
+ self.source_ports = []
self.target_dev = None
self.target_path = None
self.target_bus = None
@@ -499,8 +501,16 @@ class LibvirtConfigGuestDisk(LibvirtConfigGuestDevice):
elif self.source_type == "mount":
dev.append(etree.Element("source", dir=self.source_path))
elif self.source_type == "network":
- dev.append(etree.Element("source", protocol=self.source_protocol,
- name=self.source_host))
+ source = etree.Element("source", protocol=self.source_protocol)
+ if self.source_name is not None:
+ source.set('name', self.source_name)
+ hosts_info = zip(self.source_hosts, self.source_ports)
+ for name, port in hosts_info:
+ host = etree.Element('host', name=name)
+ if port is not None:
+ host.set('port', port)
+ source.append(host)
+ dev.append(source)
if self.auth_secret_type is not None:
auth = etree.Element("auth")
diff --git a/nova/virt/libvirt/volume.py b/nova/virt/libvirt/volume.py
index 4a11e2704..712e108de 100644
--- a/nova/virt/libvirt/volume.py
+++ b/nova/virt/libvirt/volume.py
@@ -131,7 +131,7 @@ class LibvirtFakeVolumeDriver(LibvirtBaseVolumeDriver):
disk_info)
conf.source_type = "network"
conf.source_protocol = "fake"
- conf.source_host = "fake"
+ conf.source_name = "fake"
return conf
@@ -145,10 +145,12 @@ class LibvirtNetVolumeDriver(LibvirtBaseVolumeDriver):
conf = super(LibvirtNetVolumeDriver,
self).connect_volume(connection_info,
disk_info)
+ netdisk_properties = connection_info['data']
conf.source_type = "network"
conf.source_protocol = connection_info['driver_volume_type']
- conf.source_host = connection_info['data']['name']
- netdisk_properties = connection_info['data']
+ conf.source_name = netdisk_properties.get('name')
+ conf.source_hosts = netdisk_properties.get('hosts', [])
+ conf.source_ports = netdisk_properties.get('ports', [])
auth_enabled = netdisk_properties.get('auth_enabled')
if (conf.source_protocol == 'rbd' and
CONF.rbd_secret_uuid):
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})