Merge "New style vendordata support."

This commit is contained in:
Jenkins 2016-07-27 15:30:56 +00:00 committed by Gerrit Code Review
commit 0b1a9cc7f8
11 changed files with 588 additions and 29 deletions

View File

@ -0,0 +1 @@
TODO mikal

View File

@ -28,12 +28,16 @@ import six
from nova.api.ec2 import ec2utils
from nova.api.metadata import password
from nova.api.metadata import vendordata
from nova.api.metadata import vendordata_dynamic
from nova.api.metadata import vendordata_json
from nova import availability_zones as az
from nova import block_device
from nova.cells import opts as cells_opts
from nova.cells import rpcapi as cells_rpcapi
import nova.conf
from nova import context
from nova.i18n import _LI, _LW
from nova import network
from nova.network.security_group import openstack_driver
from nova import objects
@ -56,18 +60,26 @@ VERSIONS = [
'2009-04-04',
]
# NOTE(mikal): think of these strings as version numbers. They traditionally
# correlate with OpenStack release dates, with all the changes for a given
# release bundled into a single version. Note that versions in the future are
# hidden from the listing, but can still be requested explicitly, which is
# required for testing purposes. We know this isn't great, but its inherited
# from EC2, which this needs to be compatible with.
FOLSOM = '2012-08-10'
GRIZZLY = '2013-04-04'
HAVANA = '2013-10-17'
LIBERTY = '2015-10-15'
NEWTON = '2016-06-30'
NEWTON_ONE = '2016-06-30'
NEWTON_TWO = '2016-10-06'
OPENSTACK_VERSIONS = [
FOLSOM,
GRIZZLY,
HAVANA,
LIBERTY,
NEWTON,
NEWTON_ONE,
NEWTON_TWO,
]
VERSION = "version"
@ -75,6 +87,7 @@ CONTENT = "content"
CONTENT_DIR = "content"
MD_JSON_NAME = "meta_data.json"
VD_JSON_NAME = "vendor_data.json"
VD2_JSON_NAME = "vendor_data2.json"
NW_JSON_NAME = "network_data.json"
UD_NAME = "user_data"
PASS_NAME = "password"
@ -96,7 +109,8 @@ class InstanceMetadata(object):
"""Instance metadata."""
def __init__(self, instance, address=None, content=None, extra_md=None,
network_info=None, vd_driver=None, network_metadata=None):
network_info=None, vd_driver=None, network_metadata=None,
request_context=None):
"""Creation of this object should basically cover all time consuming
collection. Methods after that should not cause time delays due to
network operations or lengthy cpu operations.
@ -182,6 +196,19 @@ class InstanceMetadata(object):
self.route_configuration = None
# NOTE(mikal): the decision to not pass extra_md here like we
# do to the StaticJSON driver is deliberate. extra_md will
# contain the admin password for the instance, and we shouldn't
# pass that to external services.
self.vendordata_providers = {
'StaticJSON': vendordata_json.JsonFileVendorData(
instance=instance, address=address,
extra_md=extra_md, network_info=network_info),
'DynamicJSON': vendordata_dynamic.DynamicVendorData(
instance=instance, address=address,
network_info=network_info, context=request_context)
}
def _route_configuration(self):
if self.route_configuration:
return self.route_configuration
@ -189,6 +216,7 @@ class InstanceMetadata(object):
path_handlers = {UD_NAME: self._user_data,
PASS_NAME: self._password,
VD_JSON_NAME: self._vendor_data,
VD2_JSON_NAME: self._vendor_data2,
MD_JSON_NAME: self._metadata_as_json,
NW_JSON_NAME: self._network_data,
VERSION: self._handle_version,
@ -342,7 +370,7 @@ class InstanceMetadata(object):
if self._check_os_version(LIBERTY, version):
metadata['project_id'] = self.instance.project_id
if self._check_os_version(NEWTON, version):
if self._check_os_version(NEWTON_ONE, version):
metadata['devices'] = self._get_device_metadata()
self.set_mimetype(MIME_TYPE_APPLICATION_JSON)
@ -425,6 +453,8 @@ class InstanceMetadata(object):
ret.append(VD_JSON_NAME)
if self._check_os_version(LIBERTY, version):
ret.append(NW_JSON_NAME)
if self._check_os_version(NEWTON_TWO, version):
ret.append(VD2_JSON_NAME)
return ret
@ -446,7 +476,43 @@ class InstanceMetadata(object):
def _vendor_data(self, version, path):
if self._check_os_version(HAVANA, version):
self.set_mimetype(MIME_TYPE_APPLICATION_JSON)
return jsonutils.dump_as_bytes(self.vddriver.get())
# NOTE(mikal): backwards compatability... If the deployer has
# specified providers, and one of those providers is StaticJSON,
# then do that thing here. Otherwise, if the deployer has
# specified an old style driver here, then use that. This second
# bit can be removed once old style vendordata is fully deprecated
# and removed.
if (CONF.vendordata_providers and
'StaticJSON' in CONF.vendordata_providers):
return jsonutils.dump_as_bytes(
self.vendordata_providers['StaticJSON'].get())
else:
# TODO(mikal): when we removed the old style vendordata
# drivers, we need to remove self.vddriver as well.
return jsonutils.dump_as_bytes(self.vddriver.get())
raise KeyError(path)
def _vendor_data2(self, version, path):
if self._check_os_version(NEWTON_TWO, version):
self.set_mimetype(MIME_TYPE_APPLICATION_JSON)
j = {}
for provider in CONF.vendordata_providers:
if provider == 'StaticJSON':
j['static'] = self.vendordata_providers['StaticJSON'].get()
else:
values = self.vendordata_providers[provider].get()
for key in list(values):
if key in j:
LOG.warning(_LW('Removing duplicate metadata key: '
'%s'), key, instance=self.instance)
del values[key]
j.update(values)
return jsonutils.dump_as_bytes(j)
raise KeyError(path)
def _check_version(self, required, requested, versions=VERSIONS):
@ -490,7 +556,7 @@ class InstanceMetadata(object):
if OPENSTACK_VERSIONS != versions:
LOG.debug("future versions %s hidden in version list",
[v for v in OPENSTACK_VERSIONS
if v not in versions])
if v not in versions], instance=self.instance)
versions += ["latest"]
else:
versions = VERSIONS + ["latest"]
@ -544,6 +610,11 @@ class InstanceMetadata(object):
path = 'openstack/%s/%s' % (version, NW_JSON_NAME)
yield (path, self.lookup(path))
if self._check_version(NEWTON_TWO, version,
ALL_OPENSTACK_VERSIONS):
path = 'openstack/%s/%s' % (version, VD2_JSON_NAME)
yield (path, self.lookup(path))
for (cid, content) in six.iteritems(self.content):
yield ('%s/%s/%s' % ("openstack", CONTENT_DIR, cid), content)
@ -578,24 +649,11 @@ class RouteConfiguration(object):
return path_handler(version, path)
class VendorDataDriver(object):
"""The base VendorData Drivers should inherit from."""
def __init__(self, *args, **kwargs):
"""Init method should do all expensive operations."""
self._data = {}
def get(self):
"""Return a dictionary of primitives to be rendered in metadata
:return: A dictionary or primitives.
"""
return self._data
def get_metadata_by_address(address):
ctxt = context.get_admin_context()
fixed_ip = network.API().get_fixed_ip_by_address(ctxt, address)
LOG.info(_LI('Fixed IP %(ip)s translates to instance UUID %(uuid)s'),
{'ip': address, 'uuid': fixed_ip['instance_uuid']})
return get_metadata_by_instance_id(fixed_ip['instance_uuid'],
address,
@ -653,3 +711,8 @@ def find_path_in_tree(data, path_tokens):
raise KeyError("/".join(path_tokens[0:i]))
data = data[path_tokens[i]]
return data
# NOTE(mikal): this alias is to stop old style vendordata plugins from breaking
# post refactor. It should be removed when we finish deprecating those plugins.
VendorDataDriver = vendordata.VendorDataDriver

View File

@ -133,7 +133,7 @@ class MetadataRequestHandler(wsgi.Application):
try:
meta_data = self.get_metadata_by_remote_address(remote_address)
except Exception:
LOG.exception(_LE('Failed to get metadata for IP: %s'),
LOG.exception(_LE('Failed to get metadata for IP %s'),
remote_address)
msg = _('An unknown error has occurred. '
'Please try your request again.')
@ -141,7 +141,7 @@ class MetadataRequestHandler(wsgi.Application):
explanation=six.text_type(msg))
if meta_data is None:
LOG.error(_LE('Failed to get metadata for IP: %s'),
LOG.error(_LE('Failed to get metadata for IP %s: no metadata'),
remote_address)
return meta_data

View File

@ -0,0 +1,30 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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.
class VendorDataDriver(object):
"""The base VendorData Drivers should inherit from."""
def __init__(self, *args, **kwargs):
"""Init method should do all expensive operations."""
self._data = {}
def get(self):
"""Return a dictionary of primitives to be rendered in metadata
:return: A dictionary of primitives.
"""
return self._data

View File

@ -0,0 +1,124 @@
# Copyright 2016 Rackspace Australia
# All Rights Reserved.
#
# 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.
"""Render vendordata as stored fetched from REST microservices."""
import requests
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import excutils
from nova.api.metadata import vendordata
import nova.conf
from nova.i18n import _LW
CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)
def generate_identity_headers(context, status='Confirmed'):
return {
'X-Auth-Token': getattr(context, 'auth_token', None),
'X-User-Id': getattr(context, 'user', None),
'X-Project-Id': getattr(context, 'tenant', None),
'X-Roles': ','.join(getattr(context, 'roles', [])),
'X-Identity-Status': status,
}
class DynamicVendorData(vendordata.VendorDataDriver):
def __init__(self, context=None, instance=None, address=None,
network_info=None):
# NOTE(mikal): address and network_info are unused, but can't be
# removed / renamed as this interface is shared with the static
# JSON plugin.
self.context = context
self.instance = instance
def _do_request(self, service_name, url):
try:
body = {'project-id': self.instance.project_id,
'instance-id': self.instance.uuid,
'image-id': self.instance.image_ref,
'user-data': self.instance.user_data,
'hostname': self.instance.hostname,
'metadata': self.instance.metadata}
headers = {'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'openstack-nova-vendordata'}
if self.context:
headers.update(generate_identity_headers(self.context))
# SSL verification
verify = url.startswith('https://')
if verify and CONF.vendordata_dynamic_ssl_certfile:
verify = CONF.vendordata_dynamic_ssl_certfile
timeout = (CONF.vendordata_dynamic_connect_timeout,
CONF.vendordata_dynamic_read_timeout)
res = requests.request('POST', url, data=jsonutils.dumps(body),
headers=headers, verify=verify,
timeout=timeout)
if res.status_code in (requests.codes.OK,
requests.codes.CREATED,
requests.codes.ACCEPTED,
requests.codes.NO_CONTENT):
# TODO(mikal): Use the Cache-Control response header to do some
# sensible form of caching here.
return jsonutils.loads(res.text)
return {}
except (TypeError, ValueError, requests.exceptions.RequestException,
requests.exceptions.SSLError) as e:
with excutils.save_and_reraise_exception():
LOG.warning(_LW('Error from dynamic vendordata service '
'%(service_name)s at %(url)s: %(error)s'),
{'service_name': service_name,
'url': url,
'error': e},
instance=self.instance)
def get(self):
j = {}
for target in CONF.vendordata_dynamic_targets:
# NOTE(mikal): a target is composed of the following:
# name@url
# where name is the name to use in the metadata handed to
# instances, and url is the URL to fetch it from
if target.find('@') == -1:
LOG.warning(_LW('Vendordata target %(target)s lacks a name. '
'Skipping'),
{'target': target}, instance=self.instance)
continue
tokens = target.split('@')
name = tokens[0]
url = '@'.join(tokens[1:])
if name in j:
LOG.warning(_LW('Vendordata already contains an entry named '
'%(target)s. Skipping'),
{'target': target}, instance=self.instance)
continue
j[name] = self._do_request(name, url)
return j

View File

@ -20,7 +20,7 @@ import errno
from oslo_log import log as logging
from oslo_serialization import jsonutils
from nova.api.metadata import base
from nova.api.metadata import vendordata
import nova.conf
from nova.i18n import _LW
@ -28,7 +28,7 @@ CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)
class JsonFileVendorData(base.VendorDataDriver):
class JsonFileVendorData(vendordata.VendorDataDriver):
def __init__(self, *args, **kwargs):
super(JsonFileVendorData, self).__init__(*args, **kwargs)
data = {}

View File

@ -123,6 +123,117 @@ request. The value should be the full dot-separated path to the class to use.
* Related options:
None
"""),
cfg.ListOpt('vendordata_providers',
default=[],
help="""
A list of vendordata providers.
vendordata providers are how deployers can provide metadata via configdrive and
metadata that is specific to their deployment. There are currently two
supported providers: StaticJSON and DynamicJSON.
StaticJSON reads a JSON file configured by the flag vendordata_jsonfile_path
and places the JSON from that file into vendor_data.json and vendor_data2.json.
DynamicJSON is configured via the vendordata_dynamic_targets flag, which is
documented separately. For each of the endpoints specified in that flag, a
section is added to the vendor_data2.json.
For more information on the requirements for implementing a vendordata
dynamic endpoint, please see the vendordata.rst file in the nova developer
reference.
* Possible values:
A list of vendordata providers, with StaticJSON and DynamicJSON being
current options.
* Services that use this:
``nova-api``
* Related options:
vendordata_dynamic_targets
vendordata_dynamic_ssl_certfile
vendordata_dynamic_connect_timeout
vendordata_dynamic_read_timeout
"""),
cfg.ListOpt('vendordata_dynamic_targets',
default=[],
help="""
A list of targets for the dynamic vendordata provider. These targets are of
the form <name>@<url>.
The dynamic vendordata provider collects metadata by contacting external REST
services and querying them for information about the instance. This behaviour
is documented in the vendordata.rst file in the nova developer reference.
"""),
cfg.StrOpt('vendordata_dynamic_ssl_certfile',
default='',
help="""
Path to an optional certificate file or CA bundle to verify dynamic vendordata
REST services ssl certificates against.
* Possible values:
An empty string, or a path to a valid certificate file
* Services that use this:
``nova-api``
* Related options:
vendordata_providers
vendordata_dynamic_targets
vendordata_dynamic_connect_timeout
vendordata_dynamic_read_timeout
"""),
cfg.IntOpt('vendordata_dynamic_connect_timeout',
default=5,
min=3,
help="""
Maximum wait time for an external REST service to connect.
* Possible values:
Any integer with a value greater than three (the TCP packet retransmission
timeout). Note that instance start may be blocked during this wait time,
so this value should be kept small.
* Services that use this:
``nova-api``
* Related options:
vendordata_providers
vendordata_dynamic_targets
vendordata_dynamic_ssl_certfile
vendordata_dynamic_read_timeout
"""),
cfg.IntOpt('vendordata_dynamic_read_timeout',
default=5,
help="""
Maximum wait time for an external REST service to return data once connected.
* Possible values:
Any integer. Note that instance start is blocked during this wait time,
so this value should be kept small.
* Services that use this:
``nova-api``
* Related options:
vendordata_providers
vendordata_dynamic_targets
vendordata_dynamic_ssl_certfile
vendordata_dynamic_connect_timeout
"""),
cfg.IntOpt("metadata_cache_expiration",
default=15,

View File

@ -0,0 +1,176 @@
# Copyright 2016 Rackspace Australia
# All Rights Reserved.
#
# 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.
import fixtures
import requests
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from nova import context
from nova import objects
from nova import test
from nova.tests import fixtures as nova_fixtures
LOG = logging.getLogger(__name__)
class fake_result(object):
def __init__(self, result):
self.status_code = 200
self.text = jsonutils.dumps(result)
real_request = requests.request
def fake_request(method, url, **kwargs):
if url.startswith('http://127.0.0.1:123'):
return fake_result({'a': 1, 'b': 'foo'})
if url.startswith('http://127.0.0.1:124'):
return fake_result({'c': 3})
if url.startswith('http://127.0.0.1:125'):
return fake_result(jsonutils.loads(kwargs.get('data', '{}')))
return real_request(method, url, **kwargs)
class MetadataTest(test.TestCase):
def setUp(self):
super(MetadataTest, self).setUp()
self.api_fixture = self.useFixture(nova_fixtures.OSMetadataServer())
self.md_url = self.api_fixture.md_url
ctxt = context.RequestContext('fake', 'fake')
flavor = objects.Flavor(
id=1, name='flavor1', memory_mb=256, vcpus=1, root_gb=1,
ephemeral_gb=1, flavorid='1', swap=0, rxtx_factor=1.0,
vcpu_weight=1, disabled=False, is_public=True, extra_specs={},
projects=[])
instance = objects.Instance(ctxt, flavor=flavor, vcpus=1,
memory_mb=256, root_gb=0, ephemeral_gb=0,
project_id='fake')
instance.create()
# NOTE(mikal): We could create a network and a fixed IP here, but it
# turns out to be heaps of fiddly boiler plate code, so let's just
# fake it and hope mriedem doesn't notice.
def fake_get_fixed_ip_by_address(self, ctxt, address):
return {'instance_uuid': instance.uuid}
self.useFixture(
fixtures.MonkeyPatch(
'nova.network.api.API.get_fixed_ip_by_address',
fake_get_fixed_ip_by_address))
def fake_get_ip_info_for_instance_from_nw_info(nw_info):
return {'fixed_ips': ['127.0.0.2'],
'fixed_ip6s': [],
'floating_ips': []}
self.useFixture(
fixtures.MonkeyPatch(
'nova.api.ec2.ec2utils.get_ip_info_for_instance_from_nw_info',
fake_get_ip_info_for_instance_from_nw_info))
def test_lookup_metadata_root_url(self):
res = requests.request('GET', self.md_url, timeout=5)
self.assertEqual(200, res.status_code)
def test_lookup_metadata_openstack_url(self):
url = '%sopenstack' % self.md_url
res = requests.request('GET', url, timeout=5,
headers={'X-Forwarded-For': '127.0.0.2'})
self.assertEqual(200, res.status_code)
def test_lookup_metadata_data_url(self):
url = '%sopenstack/latest/meta_data.json' % self.md_url
res = requests.request('GET', url, timeout=5)
self.assertEqual(200, res.status_code)
def test_lookup_external_service(self):
self.flags(
vendordata_providers=['StaticJSON', 'DynamicJSON'],
vendordata_dynamic_targets=[
'testing@http://127.0.0.1:123',
'hamster@http://127.0.0.1:123'
]
)
self.useFixture(fixtures.MonkeyPatch('requests.request',
fake_request))
url = '%sopenstack/2016-10-06/vendor_data2.json' % self.md_url
res = requests.request('GET', url, timeout=5)
self.assertEqual(200, res.status_code)
j = jsonutils.loads(res.text)
self.assertEqual({}, j['static'])
self.assertEqual(1, j['testing']['a'])
self.assertEqual('foo', j['testing']['b'])
self.assertEqual(1, j['hamster']['a'])
self.assertEqual('foo', j['hamster']['b'])
def test_lookup_external_service_no_overwrite(self):
self.flags(
vendordata_providers=['DynamicJSON'],
vendordata_dynamic_targets=[
'testing@http://127.0.0.1:123',
'testing@http://127.0.0.1:124'
]
)
self.useFixture(fixtures.MonkeyPatch('requests.request',
fake_request))
url = '%sopenstack/2016-10-06/vendor_data2.json' % self.md_url
res = requests.request('GET', url, timeout=5)
self.assertEqual(200, res.status_code)
j = jsonutils.loads(res.text)
self.assertNotIn('static', j)
self.assertEqual(1, j['testing']['a'])
self.assertEqual('foo', j['testing']['b'])
self.assertNotIn('c', j['testing'])
def test_lookup_external_service_passes_data(self):
# Much of the data we pass to the REST service is missing because of
# the way we've created the fake instance, but we should at least try
# and ensure we're passing _some_ data through to the external REST
# service.
self.flags(
vendordata_providers=['DynamicJSON'],
vendordata_dynamic_targets=[
'testing@http://127.0.0.1:125'
]
)
self.useFixture(fixtures.MonkeyPatch('requests.request',
fake_request))
url = '%sopenstack/2016-10-06/vendor_data2.json' % self.md_url
res = requests.request('GET', url, timeout=5)
self.assertEqual(200, res.status_code)
j = jsonutils.loads(res.text)
self.assertIn('instance-id', j['testing'])
self.assertTrue(uuidutils.is_uuid_like(j['testing']['instance-id']))
self.assertIn('hostname', j['testing'])
self.assertEqual('fake', j['testing']['project-id'])
self.assertIn('metadata', j['testing'])
self.assertIn('image-id', j['testing'])
self.assertIn('user-data', j['testing'])

View File

@ -20,7 +20,9 @@ import base64
import copy
import hashlib
import hmac
import os
import re
import requests
try:
import cPickle as pickle
@ -36,6 +38,7 @@ import webob
from nova.api.metadata import base
from nova.api.metadata import handler
from nova.api.metadata import password
from nova.api.metadata import vendordata
from nova import block_device
from nova.compute import flavors
from nova.conductor import api as conductor_api
@ -52,6 +55,7 @@ from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import fake_block_device
from nova.tests.unit import fake_network
from nova.tests import uuidsentinel as uuids
from nova import utils
from nova.virt import netutils
CONF = cfg.CONF
@ -441,10 +445,16 @@ class MetadataTestCase(test.TestCase):
'openstack/2016-06-30/user_data',
'openstack/2016-06-30/vendor_data.json',
'openstack/2016-06-30/network_data.json',
'openstack/2016-10-06/meta_data.json',
'openstack/2016-10-06/user_data',
'openstack/2016-10-06/vendor_data.json',
'openstack/2016-10-06/network_data.json',
'openstack/2016-10-06/vendor_data2.json',
'openstack/latest/meta_data.json',
'openstack/latest/user_data',
'openstack/latest/vendor_data.json',
'openstack/latest/network_data.json',
'openstack/latest/vendor_data2.json',
]
actual_paths = []
for (path, value) in inst_md.metadata_for_config_drive():
@ -525,7 +535,7 @@ class MetadataTestCase(test.TestCase):
expected_metadata['random_seed'] = FAKE_SEED
if md._check_os_version(base.LIBERTY, os_version):
expected_metadata['project_id'] = instance.project_id
if md._check_os_version(base.NEWTON, os_version):
if md._check_os_version(base.NEWTON_ONE, os_version):
expected_metadata['devices'] = fake_metadata_dicts()
mock_cells_keypair.return_value = keypair
@ -790,12 +800,16 @@ class OpenStackMetadataTestCase(test.TestCase):
result = mdinst.lookup("/openstack/2013-04-04")
self.assertNotIn('vendor_data.json', result)
# verify that 2016-10-06 has the vendor_data2.json file
result = mdinst.lookup("/openstack/2016-10-06")
self.assertIn('vendor_data2.json', result)
def test_vendor_data_response(self):
inst = self.instance.obj_clone()
mydata = {'mykey1': 'value1', 'mykey2': 'value2'}
class myVdriver(base.VendorDataDriver):
class myVdriver(vendordata.VendorDataDriver):
def __init__(self, *args, **kwargs):
super(myVdriver, self).__init__(*args, **kwargs)
data = mydata.copy()
@ -820,6 +834,45 @@ class OpenStackMetadataTestCase(test.TestCase):
for k, v in mydata.items():
self.assertEqual(vd[k], v)
@mock.patch.object(requests, 'request')
def test_vendor_data_response_vendordata2(self, request_mock):
request_mock.return_value.status_code = requests.codes.OK
request_mock.return_value.text = '{"color": "blue"}'
with utils.tempdir() as tmpdir:
jsonfile = os.path.join(tmpdir, 'test.json')
with open(jsonfile, 'w') as f:
f.write(jsonutils.dumps({'ldap': '10.0.0.1',
'ad': '10.0.0.2'}))
self.flags(vendordata_providers=['StaticJSON', 'DynamicJSON'],
vendordata_jsonfile_path=jsonfile,
vendordata_dynamic_targets=[
'web@http://fake.com/foobar']
)
inst = self.instance.obj_clone()
mdinst = fake_InstanceMetadata(self, inst)
# verify that 2013-10-17 has the vendor_data.json file
vdpath = "/openstack/2013-10-17/vendor_data.json"
vd = jsonutils.loads(mdinst.lookup(vdpath))
self.assertEqual('10.0.0.1', vd.get('ldap'))
self.assertEqual('10.0.0.2', vd.get('ad'))
# verify that 2016-10-06 works as well
vdpath = "/openstack/2016-10-06/vendor_data.json"
vd = jsonutils.loads(mdinst.lookup(vdpath))
self.assertEqual('10.0.0.1', vd.get('ldap'))
self.assertEqual('10.0.0.2', vd.get('ad'))
# verify the new format as well
vdpath = "/openstack/2016-10-06/vendor_data2.json"
vd = jsonutils.loads(mdinst.lookup(vdpath))
self.assertEqual('10.0.0.1', vd['static'].get('ldap'))
self.assertEqual('10.0.0.2', vd['static'].get('ad'))
self.assertEqual('blue', vd['web'].get('color'))
def test_network_data_presence(self):
inst = self.instance.obj_clone()
mdinst = fake_InstanceMetadata(self, inst)

View File

@ -16543,7 +16543,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
instance_metadata.InstanceMetadata.__init__(mox.IgnoreArg(),
content=mox.IgnoreArg(),
extra_md=mox.IgnoreArg(),
network_info=mox.IgnoreArg())
network_info=mox.IgnoreArg(),
request_context=mox.IgnoreArg())
image_meta = objects.ImageMeta.from_dict(
{'id': uuids.image_id, 'name': 'fake'})
self.drvr._get_guest_xml(mox.IgnoreArg(), instance,

View File

@ -3158,7 +3158,7 @@ class LibvirtDriver(driver.ComputeDriver):
inst_md = instance_metadata.InstanceMetadata(
instance, content=files, extra_md=extra_md,
network_info=network_info)
network_info=network_info, request_context=context)
cdb = configdrive.ConfigDriveBuilder(instance_md=inst_md)
with cdb: