summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorKevin L. Mitchell <kevin.mitchell@rackspace.com>2012-07-25 15:31:11 -0500
committerKevin L. Mitchell <kevin.mitchell@rackspace.com>2012-07-25 15:31:34 -0500
commit82afe7ad5eac668aaefc79e16bbf2226eddde97d (patch)
tree2f6fbe30e9e5e1b1246c64027be1a4b0415cb6c7 /nova
parent9a40e9e9a4538f6ba87451137bf0d6d2598f2319 (diff)
downloadnova-82afe7ad5eac668aaefc79e16bbf2226eddde97d.tar.gz
nova-82afe7ad5eac668aaefc79e16bbf2226eddde97d.tar.xz
nova-82afe7ad5eac668aaefc79e16bbf2226eddde97d.zip
Inject instance metadata into xenstore
When using Xenserver, inject instance metadata into the xenstore, for the use of the instance. Implements blueprint xenstore-metadata. Change-Id: I88a59f1b783eaaaef6ba5efd8bd448aece2f869c
Diffstat (limited to 'nova')
-rw-r--r--nova/compute/api.py10
-rw-r--r--nova/compute/manager.py12
-rw-r--r--nova/compute/rpcapi.py7
-rw-r--r--nova/db/sqlalchemy/api.py1
-rw-r--r--nova/tests/api/openstack/compute/test_server_metadata.py8
-rw-r--r--nova/tests/compute/test_compute.py15
-rw-r--r--nova/tests/compute/test_rpcapi.py5
-rw-r--r--nova/tests/test_utils.py32
-rw-r--r--nova/tests/test_xenapi.py217
-rw-r--r--nova/utils.py18
-rw-r--r--nova/virt/driver.py11
-rw-r--r--nova/virt/xenapi/driver.py4
-rw-r--r--nova/virt/xenapi/vmops.py49
13 files changed, 386 insertions, 3 deletions
diff --git a/nova/compute/api.py b/nova/compute/api.py
index eb23a2412..e5b6fb1b3 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -1595,6 +1595,9 @@ class API(base.Base):
def delete_instance_metadata(self, context, instance, key):
"""Delete the given metadata item from an instance."""
self.db.instance_metadata_delete(context, instance['uuid'], key)
+ self.compute_rpcapi.change_instance_metadata(context,
+ instance=instance,
+ diff={key: ['-']})
@wrap_check_policy
def update_instance_metadata(self, context, instance,
@@ -1605,15 +1608,20 @@ class API(base.Base):
`metadata` argument will be deleted.
"""
+ orig = self.get_instance_metadata(context, instance)
if delete:
_metadata = metadata
else:
- _metadata = self.get_instance_metadata(context, instance)
+ _metadata = orig.copy()
_metadata.update(metadata)
self._check_metadata_properties_quota(context, _metadata)
self.db.instance_metadata_update(context, instance['uuid'],
_metadata, True)
+ diff = utils.diff_dict(orig, _metadata)
+ self.compute_rpcapi.change_instance_metadata(context,
+ instance=instance,
+ diff=diff)
return _metadata
def get_instance_faults(self, context, instances):
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index ecebf2d9c..e19a4ec90 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -235,7 +235,7 @@ def _get_additional_capabilities():
class ComputeManager(manager.SchedulerDependentManager):
"""Manages the running instances from creation to destruction."""
- RPC_API_VERSION = '1.2'
+ RPC_API_VERSION = '1.3'
def __init__(self, compute_driver=None, *args, **kwargs):
"""Load configuration options and connect to the hypervisor."""
@@ -1327,6 +1327,16 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
@checks_instance_lock
@wrap_instance_fault
+ def change_instance_metadata(self, context, instance_uuid, diff):
+ """Update the metadata published to the instance."""
+ instance_ref = self.db.instance_get_by_uuid(context, instance_uuid)
+ LOG.debug(_("Changing instance metadata according to %(diff)r") %
+ locals(), instance=instance_ref)
+ self.driver.change_instance_metadata(context, instance_ref, diff)
+
+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
+ @checks_instance_lock
+ @wrap_instance_fault
def confirm_resize(self, context, instance_uuid, migration_id):
"""Destroys the source instance."""
migration_ref = self.db.migration_get(context, migration_id)
diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py
index 332b8153e..e637c9f64 100644
--- a/nova/compute/rpcapi.py
+++ b/nova/compute/rpcapi.py
@@ -57,6 +57,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
1.0 - Initial version.
1.1 - Adds get_host_uptime()
1.2 - Adds check_can_live_migrate_[destination|source]
+ 1.3 - Adds change_instance_metadata()
'''
BASE_RPC_API_VERSION = '1.0'
@@ -375,6 +376,12 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
instance_uuid=instance['uuid']),
topic=_compute_topic(self.topic, ctxt, None, instance))
+ def change_instance_metadata(self, ctxt, instance, diff):
+ self.cast(ctxt, self.make_msg('change_instance_metadata',
+ instance_uuid=instance['uuid'], diff=diff),
+ topic=_compute_topic(self.topic, ctxt, None, instance),
+ version='1.3')
+
class SecurityGroupAPI(nova.openstack.common.rpc.proxy.RpcProxy):
'''Client side of the security group rpc API.
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 7b258c42d..527c10a61 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -1470,6 +1470,7 @@ def _build_instance_get(context, session=None):
options(joinedload_all('security_groups.rules')).\
options(joinedload('info_cache')).\
options(joinedload('metadata')).\
+ options(joinedload('system_metadata')).\
options(joinedload('instance_type'))
diff --git a/nova/tests/api/openstack/compute/test_server_metadata.py b/nova/tests/api/openstack/compute/test_server_metadata.py
index 37d54e09b..29eb60cad 100644
--- a/nova/tests/api/openstack/compute/test_server_metadata.py
+++ b/nova/tests/api/openstack/compute/test_server_metadata.py
@@ -18,6 +18,7 @@
import webob
from nova.api.openstack.compute import server_metadata
+from nova.compute import rpcapi as compute_rpcapi
import nova.db
from nova import exception
from nova import flags
@@ -85,6 +86,10 @@ def return_server_nonexistant(context, server_id):
raise exception.InstanceNotFound()
+def fake_change_instance_metadata(self, context, instance, diff):
+ pass
+
+
class ServerMetaDataTest(test.TestCase):
def setUp(self):
@@ -97,6 +102,9 @@ class ServerMetaDataTest(test.TestCase):
self.stubs.Set(nova.db, 'instance_metadata_get',
return_server_metadata)
+ self.stubs.Set(compute_rpcapi.ComputeAPI, 'change_instance_metadata',
+ fake_change_instance_metadata)
+
self.controller = server_metadata.Controller()
self.uuid = str(utils.gen_uuid())
self.url = '/v1.1/fake/servers/%s/metadata' % self.uuid
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 6dacd7459..f6bc0ee4f 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -3339,7 +3339,13 @@ class ComputeAPITestCase(BaseTestCase):
db.instance_destroy(c, instance4['uuid'])
def test_instance_metadata(self):
- """Test searching instances by state"""
+ meta_changes = [None]
+
+ def fake_change_instance_metadata(inst, ctxt, instance, diff):
+ meta_changes[0] = diff
+ self.stubs.Set(compute_rpcapi.ComputeAPI, 'change_instance_metadata',
+ fake_change_instance_metadata)
+
_context = context.get_admin_context()
instance = self._create_fake_instance({'metadata': {'key1': 'value1'}})
@@ -3350,16 +3356,23 @@ class ComputeAPITestCase(BaseTestCase):
{'key2': 'value2'})
metadata = self.compute_api.get_instance_metadata(_context, instance)
self.assertEqual(metadata, {'key1': 'value1', 'key2': 'value2'})
+ self.assertEqual(meta_changes, [{'key2': ['+', 'value2']}])
new_metadata = {'key2': 'bah', 'key3': 'value3'}
self.compute_api.update_instance_metadata(_context, instance,
new_metadata, delete=True)
metadata = self.compute_api.get_instance_metadata(_context, instance)
self.assertEqual(metadata, new_metadata)
+ self.assertEqual(meta_changes, [{
+ 'key1': ['-'],
+ 'key2': ['+', 'bah'],
+ 'key3': ['+', 'value3'],
+ }])
self.compute_api.delete_instance_metadata(_context, instance, 'key2')
metadata = self.compute_api.get_instance_metadata(_context, instance)
self.assertEqual(metadata, {'key3': 'value3'})
+ self.assertEqual(meta_changes, [{'key2': ['-']}])
db.instance_destroy(_context, instance['uuid'])
diff --git a/nova/tests/compute/test_rpcapi.py b/nova/tests/compute/test_rpcapi.py
index 40998b593..d1d633023 100644
--- a/nova/tests/compute/test_rpcapi.py
+++ b/nova/tests/compute/test_rpcapi.py
@@ -322,3 +322,8 @@ class ComputeRpcAPITestCase(test.TestCase):
def test_unrescue_instance(self):
self._test_compute_api('unrescue_instance', 'cast',
instance=self.fake_instance)
+
+ def test_change_instance_metadata(self):
+ self._test_compute_api('change_instance_metadata', 'cast',
+ instance=self.fake_instance, diff={},
+ version='1.3')
diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py
index 6506bde1c..24c2e04fb 100644
--- a/nova/tests/test_utils.py
+++ b/nova/tests/test_utils.py
@@ -884,3 +884,35 @@ class AuditPeriodTest(test.TestCase):
day=1,
month=6,
year=2011))
+
+
+class DiffDict(test.TestCase):
+ """Unit tests for diff_dict()"""
+
+ def test_no_change(self):
+ old = dict(a=1, b=2, c=3)
+ new = dict(a=1, b=2, c=3)
+ diff = utils.diff_dict(old, new)
+
+ self.assertEqual(diff, {})
+
+ def test_new_key(self):
+ old = dict(a=1, b=2, c=3)
+ new = dict(a=1, b=2, c=3, d=4)
+ diff = utils.diff_dict(old, new)
+
+ self.assertEqual(diff, dict(d=['+', 4]))
+
+ def test_changed_key(self):
+ old = dict(a=1, b=2, c=3)
+ new = dict(a=1, b=4, c=3)
+ diff = utils.diff_dict(old, new)
+
+ self.assertEqual(diff, dict(b=['+', 4]))
+
+ def test_removed_key(self):
+ old = dict(a=1, b=2, c=3)
+ new = dict(a=1, c=3)
+ diff = utils.diff_dict(old, new)
+
+ self.assertEqual(diff, dict(b=['-']))
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index dcb0bf4ed..df35d54ba 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -33,6 +33,7 @@ from nova import exception
from nova import flags
from nova.image import glance
from nova.openstack.common import importutils
+from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.openstack.common import timeutils
from nova import test
@@ -284,6 +285,11 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
stubs.stubout_image_service_download(self.stubs)
stubs.stubout_stream_disk(self.stubs)
+ def fake_inject_instance_metadata(self, instance, vm):
+ pass
+ self.stubs.Set(vmops.VMOps, 'inject_instance_metadata',
+ fake_inject_instance_metadata)
+
def tearDown(self):
super(XenAPIVMTestCase, self).tearDown()
fake_image.FakeImageService_reset()
@@ -511,6 +517,12 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
hostname="test", architecture="x86-64", instance_id=1,
check_injection=False,
create_record=True, empty_dns=False):
+ # Fake out inject_instance_metadata
+ def fake_inject_instance_metadata(self, instance, vm):
+ pass
+ self.stubs.Set(vmops.VMOps, 'inject_instance_metadata',
+ fake_inject_instance_metadata)
+
if create_record:
instance_values = {'id': instance_id,
'project_id': self.project_id,
@@ -943,6 +955,11 @@ class XenAPIMigrateInstance(stubs.XenAPITestBase):
stubs.stub_out_migration_methods(self.stubs)
stubs.stubout_get_this_vm_uuid(self.stubs)
+ def fake_inject_instance_metadata(self, instance, vm):
+ pass
+ self.stubs.Set(vmops.VMOps, 'inject_instance_metadata',
+ fake_inject_instance_metadata)
+
def test_resize_xenserver_6(self):
instance = db.instance_create(self.context, self.instance_values)
called = {'resize': False}
@@ -2186,3 +2203,203 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
self.assertRaises(NotImplementedError, self.conn.live_migration,
self.conn, None, None, None, recover_method)
self.assertTrue(recover_method.called, "recover_method.called")
+
+
+class XenAPIInjectMetadataTestCase(stubs.XenAPITestBase):
+ def setUp(self):
+ super(XenAPIInjectMetadataTestCase, self).setUp()
+ self.flags(xenapi_connection_url='test_url',
+ xenapi_connection_password='test_pass',
+ firewall_driver='nova.virt.xenapi.firewall.'
+ 'Dom0IptablesFirewallDriver')
+ stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
+ self.conn = xenapi_conn.XenAPIDriver(False)
+
+ self.xenstore = dict(persist={}, ephem={})
+
+ def fake_get_vm_opaque_ref(inst, instance):
+ self.assertEqual(instance, 'instance')
+ return 'vm_ref'
+
+ def fake_add_to_param_xenstore(inst, vm_ref, key, val):
+ self.assertEqual(vm_ref, 'vm_ref')
+ self.xenstore['persist'][key] = val
+
+ def fake_remove_from_param_xenstore(inst, vm_ref, key):
+ self.assertEqual(vm_ref, 'vm_ref')
+ if key in self.xenstore['persist']:
+ del self.xenstore['persist'][key]
+
+ def fake_write_to_xenstore(inst, instance, path, value, vm_ref=None):
+ self.assertEqual(instance, 'instance')
+ self.assertEqual(vm_ref, 'vm_ref')
+ self.xenstore['ephem'][path] = jsonutils.dumps(value)
+
+ def fake_delete_from_xenstore(inst, instance, path, vm_ref=None):
+ self.assertEqual(instance, 'instance')
+ self.assertEqual(vm_ref, 'vm_ref')
+ if path in self.xenstore['ephem']:
+ del self.xenstore['ephem'][path]
+
+ self.stubs.Set(vmops.VMOps, '_get_vm_opaque_ref',
+ fake_get_vm_opaque_ref)
+ self.stubs.Set(vmops.VMOps, '_add_to_param_xenstore',
+ fake_add_to_param_xenstore)
+ self.stubs.Set(vmops.VMOps, '_remove_from_param_xenstore',
+ fake_remove_from_param_xenstore)
+ self.stubs.Set(vmops.VMOps, '_write_to_xenstore',
+ fake_write_to_xenstore)
+ self.stubs.Set(vmops.VMOps, '_delete_from_xenstore',
+ fake_delete_from_xenstore)
+
+ def test_inject_instance_metadata(self):
+ class FakeMetaItem(object):
+ def __init__(self, key, value):
+ self.key = key
+ self.value = value
+
+ instance = dict(metadata=[FakeMetaItem("a", 1),
+ FakeMetaItem("b", 2),
+ FakeMetaItem("c", 3)],
+ system_metadata=[FakeMetaItem("sys_a", 1),
+ FakeMetaItem("sys_b", 2),
+ FakeMetaItem("sys_c", 3)])
+ self.conn._vmops.inject_instance_metadata(instance, 'vm_ref')
+
+ self.assertEqual(self.xenstore, {
+ 'persist': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ 'ephem': {},
+ })
+
+ def test_change_instance_metadata_add(self):
+ diff = dict(d=['+', 4])
+ self.xenstore = {
+ 'persist': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ 'ephem': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ }
+
+ self.conn._vmops.change_instance_metadata('instance', diff)
+
+ self.assertEqual(self.xenstore, {
+ 'persist': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/user-metadata/d': '4',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ 'ephem': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/user-metadata/d': '4',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ })
+
+ def test_change_instance_metadata_update(self):
+ diff = dict(b=['+', 4])
+ self.xenstore = {
+ 'persist': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ 'ephem': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ }
+
+ self.conn._vmops.change_instance_metadata('instance', diff)
+
+ self.assertEqual(self.xenstore, {
+ 'persist': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '4',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ 'ephem': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '4',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ })
+
+ def test_change_instance_metadata_delete(self):
+ diff = dict(b=['-'])
+ self.xenstore = {
+ 'persist': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ 'ephem': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/b': '2',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ }
+
+ self.conn._vmops.change_instance_metadata('instance', diff)
+
+ self.assertEqual(self.xenstore, {
+ 'persist': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ 'ephem': {
+ 'vm-data/user-metadata/a': '1',
+ 'vm-data/user-metadata/c': '3',
+ 'vm-data/system-metadata/sys_a': '1',
+ 'vm-data/system-metadata/sys_b': '2',
+ 'vm-data/system-metadata/sys_c': '3',
+ },
+ })
diff --git a/nova/utils.py b/nova/utils.py
index 9cfb6d06d..adb1cf45c 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -861,6 +861,24 @@ def subset_dict(dict_, keys):
return subset
+def diff_dict(orig, new):
+ """
+ Return a dict describing how to change orig to new. The keys
+ correspond to values that have changed; the value will be a list
+ of one or two elements. The first element of the list will be
+ either '+' or '-', indicating whether the key was updated or
+ deleted; if the key was updated, the list will contain a second
+ element, giving the updated value.
+ """
+ # Figure out what keys went away
+ result = dict((k, ['-']) for k in set(orig.keys()) - set(new.keys()))
+ # Compute the updates
+ for key, value in new.items():
+ if key not in orig or value != orig[key]:
+ result[key] = ['+', value]
+ return result
+
+
def check_isinstance(obj, cls):
"""Checks that obj is of type cls, and lets PyLint infer types."""
if isinstance(obj, cls):
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index 6c370ffbd..4e821eab2 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -528,6 +528,17 @@ class ComputeDriver(object):
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
+ def change_instance_metadata(self, context, instance, diff):
+ """
+ Applies a diff to the instance metadata.
+
+ This is an optional driver method which is used to publish
+ changes to the instance's metadata to the hypervisor. If the
+ hypervisor has no means of publishing the instance metadata to
+ the instance, then this method should not be implemented.
+ """
+ pass
+
def agent_update(self, instance, url, md5hash):
"""
Update agent on the specified instance.
diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py
index 2a86277e5..9a81311cb 100644
--- a/nova/virt/xenapi/driver.py
+++ b/nova/virt/xenapi/driver.py
@@ -214,6 +214,10 @@ class XenAPIDriver(driver.ComputeDriver):
"""
self._vmops.inject_file(instance, b64_path, b64_contents)
+ def change_instance_metadata(self, context, instance, diff):
+ """Apply a diff to the instance metadata."""
+ self._vmops.change_instance_metadata(instance, diff)
+
def destroy(self, instance, network_info, block_device_info=None):
"""Destroy VM instance"""
self._vmops.destroy(instance, network_info, block_device_info)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 83970e861..5dcc53b94 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -415,6 +415,8 @@ class VMOps(object):
hostname = self._generate_hostname(instance)
self.inject_hostname(instance, vm_ref, hostname)
+ self.inject_instance_metadata(instance, vm_ref)
+
return vm_ref
def _attach_disks(self, instance, disk_image_type, vm_ref, vdis):
@@ -841,6 +843,44 @@ class VMOps(object):
vm_ref = self._get_vm_opaque_ref(instance)
agent.inject_file(self._session, instance, vm_ref, path, contents)
+ def inject_instance_metadata(self, instance, vm_ref):
+ """Inject instance metadata into xenstore."""
+ def store_meta(topdir, data_list):
+ for item in data_list:
+ key = item.key
+ value = item.value or ''
+ self._add_to_param_xenstore(vm_ref, '%s/%s' % (topdir, key),
+ jsonutils.dumps(value))
+
+ # Store user metadata
+ store_meta('vm-data/user-metadata', instance['metadata'])
+
+ # Store system metadata
+ store_meta('vm-data/system-metadata', instance['system_metadata'])
+
+ def change_instance_metadata(self, instance, diff):
+ """Apply changes to instance metadata to xenstore."""
+ vm_ref = self._get_vm_opaque_ref(instance)
+ for key, change in diff.items():
+ location = 'vm-data/user-metadata/%s' % key
+ if change[0] == '-':
+ self._remove_from_param_xenstore(vm_ref, location)
+ try:
+ self._delete_from_xenstore(instance, location,
+ vm_ref=vm_ref)
+ except KeyError:
+ # catch KeyError for domid if instance isn't running
+ pass
+ elif change[0] == '+':
+ self._add_to_param_xenstore(vm_ref, location,
+ jsonutils.dumps(change[1]))
+ try:
+ self._write_to_xenstore(instance, location, change[1],
+ vm_ref=vm_ref)
+ except KeyError:
+ # catch KeyError for domid if instance isn't running
+ pass
+
def _find_root_vdi_ref(self, vm_ref):
"""Find and return the root vdi ref for a VM."""
if not vm_ref:
@@ -1328,6 +1368,15 @@ class VMOps(object):
vm_ref=vm_ref, path=path,
value=jsonutils.dumps(value))
+ def _delete_from_xenstore(self, instance, path, vm_ref=None):
+ """
+ Deletes the value from the xenstore record for the given VM at
+ the specified location. A XenAPIPlugin.PluginError will be
+ raised if any error is encountered in the delete process.
+ """
+ return self._make_plugin_call('xenstore.py', 'delete_record', instance,
+ vm_ref=vm_ref, path=path)
+
def _make_plugin_call(self, plugin, method, instance, vm_ref=None,
**addl_args):
"""