continue config-drive-v2, add openstack metadata api
This continues the work Michael did on blueprint config-drive-v2 Things added: * web service /openstack/2012-08-10 with: user_data meta_data.json * web service supporting /openstack/content Things changed: * Changed the name 'files' (/openstack/files) to be 'content' This is to be more generic and apply to future re-use of it for some purpose other than personalities (injected_files) * 'meta_data.json' and 'user_data' are used for openstack * 'user-data' (without .raw) in the ec2 config drive rendering * 'content_path' is a attribute of each of the entries in the 'files' its value is the path from the openstack metadata service root to where the content is located. This means that the user does not have to know anything explicit about '/content'. They just have to take the 'content_path' and append it to the top level entry point of metadata service. Explicit example: http://169.254.169.254/openstack/2012-08-10/meta_data.json has: files[{path: "/etc/passwd", content_path: "/content/0001"}] To get the file, user appends 'content_path' to http://169.254.169.254/openstack * network config (Template) is available in /content also, and the key that points to it is 'network_config'. This occurs only if there are network's that are "injected" (same behavior as previously) The meta_data.json now contains uuid: <instance uuid> meta: meta tags provided they will appear in a dict here files: This is a list of dicts, where each dict has 2 fields: * 'path' (the path provided) * 'content_id': the location in /content/ that will give the content public-keys: Dict of keyname : value network_config: This is a dict, containing 'content_path' and 'name' (network_config) hostname: the hostname name: the nova boot name TODO: * want to add other items into openstack metadata * public-hostname * IP addresses * keystone-endpoint * url of metadata service: ideally only show this if there is a web service available. the path seems hard coded at the moment to be http://169.254.169.254/openstack, but I do not know if there is a way to determine if one is present or not. * document config-drive-v2 Change-Id: I6b9097624a9c1a972e4851d79f6d557376f59d32
This commit is contained in:
@@ -29,6 +29,7 @@ from nova import db
|
||||
from nova import flags
|
||||
from nova import network
|
||||
from nova.openstack.common import cfg
|
||||
from nova.virt import netutils
|
||||
|
||||
|
||||
metadata_opts = [
|
||||
@@ -61,8 +62,14 @@ VERSIONS = [
|
||||
'2009-04-04',
|
||||
]
|
||||
|
||||
OPENSTACK_VERSIONS = ["2012-08-10"]
|
||||
|
||||
class InvalidMetadataEc2Version(Exception):
|
||||
CONTENT_DIR = "content"
|
||||
MD_JSON_NAME = "meta_data.json"
|
||||
UD_NAME = "user_data"
|
||||
|
||||
|
||||
class InvalidMetadataVersion(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@@ -73,8 +80,17 @@ class InvalidMetadataPath(Exception):
|
||||
class InstanceMetadata():
|
||||
"""Instance metadata."""
|
||||
|
||||
def __init__(self, instance, address=None):
|
||||
def __init__(self, instance, address=None, content=[], extra_md=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.
|
||||
|
||||
The user should then get a single instance and make multiple method
|
||||
calls on it.
|
||||
"""
|
||||
|
||||
self.instance = instance
|
||||
self.extra_md = extra_md
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
@@ -91,9 +107,9 @@ class InstanceMetadata():
|
||||
self.mappings = _format_instance_mapping(ctxt, instance)
|
||||
|
||||
if instance.get('user_data', None) is not None:
|
||||
self.userdata_b64 = base64.b64decode(instance['user_data'])
|
||||
self.userdata_raw = base64.b64decode(instance['user_data'])
|
||||
else:
|
||||
self.userdata_b64 = None
|
||||
self.userdata_raw = None
|
||||
|
||||
self.ec2_ids = {}
|
||||
|
||||
@@ -112,12 +128,45 @@ class InstanceMetadata():
|
||||
|
||||
self.address = address
|
||||
|
||||
# expose instance metadata.
|
||||
self.launch_metadata = {}
|
||||
for item in instance.get('metadata', []):
|
||||
self.launch_metadata[item['key']] = item['value']
|
||||
|
||||
self.uuid = instance.get('uuid')
|
||||
|
||||
self.content = {}
|
||||
self.files = []
|
||||
|
||||
# get network info, and the rendered network template
|
||||
ctxt = context.get_admin_context()
|
||||
network_info = network.API().get_instance_nw_info(ctxt, instance)
|
||||
|
||||
self.network_config = None
|
||||
cfg = netutils.get_injected_network_template(network_info)
|
||||
|
||||
if cfg:
|
||||
key = "%04i" % len(self.content)
|
||||
self.content[key] = cfg
|
||||
self.network_config = {"name": "network_config",
|
||||
'content_path': "/%s/%s" % (CONTENT_DIR, key)}
|
||||
|
||||
# 'content' is passed in from the configdrive code in
|
||||
# nova/virt/libvirt/driver.py. Thats how we get the injected files
|
||||
# (personalities) in. AFAIK they're not stored in the db at all,
|
||||
# so are not available later (web service metadata time).
|
||||
for (path, contents) in content:
|
||||
key = "%04i" % len(self.content)
|
||||
self.files.append({'path': path,
|
||||
'content_path': "/%s/%s" % (CONTENT_DIR, key)})
|
||||
self.content[key] = contents
|
||||
|
||||
def get_ec2_metadata(self, version):
|
||||
if version == "latest":
|
||||
version = VERSIONS[-1]
|
||||
|
||||
if version not in VERSIONS:
|
||||
raise InvalidMetadataEc2Version(version)
|
||||
raise InvalidMetadataVersion(version)
|
||||
|
||||
hostname = "%s.%s" % (self.instance['hostname'], FLAGS.dhcp_domain)
|
||||
floating_ips = self.ip_info['floating_ips']
|
||||
@@ -180,11 +229,83 @@ class InstanceMetadata():
|
||||
meta_data['instance-action'] = 'none'
|
||||
|
||||
data = {'meta-data': meta_data}
|
||||
if self.userdata_b64 is not None:
|
||||
data['user-data'] = self.userdata_b64
|
||||
if self.userdata_raw is not None:
|
||||
data['user-data'] = self.userdata_raw
|
||||
|
||||
return data
|
||||
|
||||
def get_ec2_item(self, path_tokens):
|
||||
# get_ec2_metadata returns dict without top level version
|
||||
data = self.get_ec2_metadata(path_tokens[0])
|
||||
return find_path_in_tree(data, path_tokens[1:])
|
||||
|
||||
def get_openstack_item(self, path_tokens):
|
||||
if path_tokens[0] == CONTENT_DIR:
|
||||
if len(path_tokens) == 1:
|
||||
raise KeyError("no listing for %s" % "/".join(path_tokens))
|
||||
if len(path_tokens) != 2:
|
||||
raise KeyError("Too many tokens for /%s" % CONTENT_DIR)
|
||||
return self.content[path_tokens[1]]
|
||||
|
||||
version = path_tokens[0]
|
||||
if version == "latest":
|
||||
version = OPENSTACK_VERSIONS[-1]
|
||||
|
||||
if version not in OPENSTACK_VERSIONS:
|
||||
raise InvalidMetadataVersion(version)
|
||||
|
||||
path = '/'.join(path_tokens[1:])
|
||||
|
||||
if len(path_tokens) == 1:
|
||||
# request for /version, give a list of what is available
|
||||
ret = [MD_JSON_NAME]
|
||||
if self.userdata_raw is not None:
|
||||
ret.append(UD_NAME)
|
||||
return ret
|
||||
|
||||
if path == UD_NAME:
|
||||
if self.userdata_raw is None:
|
||||
raise KeyError(path)
|
||||
return self.userdata_raw
|
||||
|
||||
if path != MD_JSON_NAME:
|
||||
raise KeyError(path)
|
||||
|
||||
# right now, the only valid path is metadata.json
|
||||
metadata = {}
|
||||
metadata['uuid'] = self.uuid
|
||||
|
||||
if self.launch_metadata:
|
||||
metadata['meta'] = self.launch_metadata
|
||||
|
||||
if self.files:
|
||||
metadata['files'] = self.files
|
||||
|
||||
if self.extra_md:
|
||||
metadata.update(self.extra_md)
|
||||
|
||||
if self.launch_metadata:
|
||||
metadata['meta'] = self.launch_metadata
|
||||
|
||||
if self.network_config:
|
||||
metadata['network_config'] = self.network_config
|
||||
|
||||
if self.instance['key_name']:
|
||||
metadata['public_keys'] = {
|
||||
self.instance['key_name']: self.instance['key_data']
|
||||
}
|
||||
|
||||
metadata['hostname'] = "%s.%s" % (self.instance['hostname'],
|
||||
FLAGS.dhcp_domain)
|
||||
|
||||
metadata['name'] = self.instance['display_name']
|
||||
|
||||
data = {
|
||||
MD_JSON_NAME: json.dumps(metadata),
|
||||
}
|
||||
|
||||
return data[path]
|
||||
|
||||
def _check_version(self, required, requested):
|
||||
return VERSIONS.index(requested) >= VERSIONS.index(required)
|
||||
|
||||
@@ -194,40 +315,46 @@ class InstanceMetadata():
|
||||
else:
|
||||
path = os.path.normpath(path)
|
||||
|
||||
if path == "/":
|
||||
return VERSIONS + ["latest"]
|
||||
# fix up requests, prepending /ec2 to anything that does not match
|
||||
path_tokens = path.split('/')[1:]
|
||||
if path_tokens[0] not in ("ec2", "openstack"):
|
||||
if path_tokens[0] == "":
|
||||
# request for /
|
||||
path_tokens = ["ec2"]
|
||||
else:
|
||||
path_tokens = ["ec2"] + path_tokens
|
||||
path = "/" + "/".join(path_tokens)
|
||||
|
||||
items = path.split('/')[1:]
|
||||
# all values of 'path' input starts with '/' and have no trailing /
|
||||
|
||||
# specifically handle the top level request
|
||||
if len(path_tokens) == 1:
|
||||
if path_tokens[0] == "openstack":
|
||||
versions = OPENSTACK_VERSIONS + ["latest"]
|
||||
else:
|
||||
versions = VERSIONS + ["latest"]
|
||||
return versions
|
||||
|
||||
try:
|
||||
md = self.get_ec2_metadata(items[0])
|
||||
except InvalidMetadataEc2Version:
|
||||
raise InvalidMetadataPath(path)
|
||||
|
||||
data = md
|
||||
for i in range(1, len(items)):
|
||||
if isinstance(data, dict) or isinstance(data, list):
|
||||
if items[i] in data:
|
||||
data = data[items[i]]
|
||||
else:
|
||||
raise InvalidMetadataPath(path)
|
||||
if path_tokens[0] == "openstack":
|
||||
data = self.get_openstack_item(path_tokens[1:])
|
||||
else:
|
||||
if i != len(items) - 1:
|
||||
raise InvalidMetadataPath(path)
|
||||
data = data[items[i]]
|
||||
data = self.get_ec2_item(path_tokens[1:])
|
||||
except (InvalidMetadataVersion, KeyError):
|
||||
raise InvalidMetadataPath(path)
|
||||
|
||||
return data
|
||||
|
||||
def metadata_for_config_drive(self, injected_files):
|
||||
def metadata_for_config_drive(self):
|
||||
"""Yields (path, value) tuples for metadata elements."""
|
||||
# EC2 style metadata
|
||||
for version in VERSIONS:
|
||||
for version in VERSIONS + ["latest"]:
|
||||
if version in FLAGS.config_drive_skip_versions.split(' '):
|
||||
continue
|
||||
|
||||
data = self.get_ec2_metadata(version)
|
||||
if 'user-data' in data:
|
||||
filepath = os.path.join('ec2', version, 'userdata.raw')
|
||||
filepath = os.path.join('ec2', version, 'user-data')
|
||||
yield (filepath, data['user-data'])
|
||||
del data['user-data']
|
||||
|
||||
@@ -236,20 +363,19 @@ class InstanceMetadata():
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
filepath = os.path.join('ec2', version, 'metadata.json')
|
||||
filepath = os.path.join('ec2', version, 'meta-data.json')
|
||||
yield (filepath, json.dumps(data['meta-data']))
|
||||
|
||||
filepath = os.path.join('ec2', 'latest', 'metadata.json')
|
||||
yield (filepath, json.dumps(data['meta-data']))
|
||||
for version in OPENSTACK_VERSIONS + ["latest"]:
|
||||
path = 'openstack/%s/%s' % (version, MD_JSON_NAME)
|
||||
yield (path, self.lookup(path))
|
||||
|
||||
# Openstack style metadata
|
||||
# TODO(mikal): refactor this later
|
||||
files = []
|
||||
for path in injected_files:
|
||||
files.append({'path': path,
|
||||
'content': injected_files[path]})
|
||||
yield ('openstack/2012-08-10/files.json', json.dumps(files))
|
||||
yield ('openstack/latest/files.json', json.dumps(files))
|
||||
path = 'openstack/%s/%s' % (version, UD_NAME)
|
||||
if self.userdata_raw is not None:
|
||||
yield (path, self.lookup(path))
|
||||
|
||||
for (cid, content) in self.content.iteritems():
|
||||
yield ('%s/%s/%s' % ("openstack", CONTENT_DIR, cid), content)
|
||||
|
||||
|
||||
def get_metadata_by_address(address):
|
||||
@@ -327,3 +453,18 @@ def ec2_md_print(data):
|
||||
return '\n'.join(data)
|
||||
else:
|
||||
return str(data)
|
||||
|
||||
|
||||
def find_path_in_tree(data, path_tokens):
|
||||
# given a dict/list tree, and a path in that tree, return data found there.
|
||||
for i in range(0, len(path_tokens)):
|
||||
if isinstance(data, dict) or isinstance(data, list):
|
||||
if path_tokens[i] in data:
|
||||
data = data[path_tokens[i]]
|
||||
else:
|
||||
raise KeyError("/".join(path_tokens[0:i]))
|
||||
else:
|
||||
if i != len(path_tokens) - 1:
|
||||
raise KeyError("/".join(path_tokens[0:i]))
|
||||
data = data[path_tokens[i]]
|
||||
return data
|
||||
|
||||
@@ -412,7 +412,6 @@ class LibvirtConnTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LibvirtConnTestCase, self).setUp()
|
||||
libvirt_driver._late_load_cheetah()
|
||||
self.flags(fake_call=True)
|
||||
self.user_id = 'fake'
|
||||
self.project_id = 'fake'
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
import base64
|
||||
from copy import copy
|
||||
import json
|
||||
import re
|
||||
|
||||
import webob
|
||||
@@ -32,6 +33,7 @@ from nova import exception
|
||||
from nova import flags
|
||||
from nova import network
|
||||
from nova import test
|
||||
from nova.tests import fake_network
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
@@ -55,7 +57,9 @@ INSTANCES = (
|
||||
'fixed_ips': [],
|
||||
'root_device_name': '/dev/sda1',
|
||||
'info_cache': {'network_info': []},
|
||||
'hostname': 'test'},
|
||||
'hostname': 'test.novadomain',
|
||||
'display_name': 'my_displayname',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -63,7 +67,8 @@ def return_non_existing_address(*args, **kwarg):
|
||||
raise exception.NotFound()
|
||||
|
||||
|
||||
def fake_InstanceMetadata(stubs, inst_data, address=None, sgroups=None):
|
||||
def fake_InstanceMetadata(stubs, inst_data, address=None,
|
||||
sgroups=None, content=[], extra_md={}):
|
||||
|
||||
if sgroups is None:
|
||||
sgroups = [{'name': 'default'}]
|
||||
@@ -72,7 +77,8 @@ def fake_InstanceMetadata(stubs, inst_data, address=None, sgroups=None):
|
||||
return sgroups
|
||||
|
||||
stubs.Set(api, 'security_group_get_by_instance', sg_get)
|
||||
return base.InstanceMetadata(inst_data, address=address)
|
||||
return base.InstanceMetadata(inst_data, address=address,
|
||||
content=content, extra_md=extra_md)
|
||||
|
||||
|
||||
def fake_request(stubs, mdinst, relpath, address="127.0.0.1",
|
||||
@@ -103,6 +109,8 @@ class MetadataTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(MetadataTestCase, self).setUp()
|
||||
self.instance = INSTANCES[0]
|
||||
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
|
||||
spectacular=True)
|
||||
|
||||
def test_user_data(self):
|
||||
inst = copy(self.instance)
|
||||
@@ -181,8 +189,7 @@ class MetadataTestCase(test.TestCase):
|
||||
|
||||
def test_pubkey(self):
|
||||
md = fake_InstanceMetadata(self.stubs, copy(self.instance))
|
||||
data = md.get_ec2_metadata(version='2009-04-04')
|
||||
pubkey_ent = data['meta-data']['public-keys']
|
||||
pubkey_ent = md.lookup("/2009-04-04/meta-data/public-keys")
|
||||
|
||||
self.assertEqual(base.ec2_md_print(pubkey_ent),
|
||||
"0=%s" % self.instance['key_name'])
|
||||
@@ -193,21 +200,26 @@ class MetadataTestCase(test.TestCase):
|
||||
inst = copy(self.instance)
|
||||
inst['ramdisk_id'] = 'ari-853667c0'
|
||||
md = fake_InstanceMetadata(self.stubs, inst)
|
||||
data = md.get_ec2_metadata(version='latest')
|
||||
data = md.lookup("/latest/meta-data/ramdisk-id")
|
||||
|
||||
self.assertTrue(data['meta-data']['ramdisk-id'] is not None)
|
||||
self.assertTrue(re.match('ari-[0-9a-f]{8}',
|
||||
data['meta-data']['ramdisk-id']))
|
||||
self.assertTrue(data is not None)
|
||||
self.assertTrue(re.match('ari-[0-9a-f]{8}', data))
|
||||
|
||||
def test_image_type_kernel(self):
|
||||
inst = copy(self.instance)
|
||||
inst['kernel_id'] = 'aki-c2e26ff2'
|
||||
md = fake_InstanceMetadata(self.stubs, inst)
|
||||
data = md.get_ec2_metadata(version='2009-04-04')
|
||||
data = md.lookup("/2009-04-04/meta-data/kernel-id")
|
||||
|
||||
self.assertTrue(data['meta-data']['kernel-id'] is not None)
|
||||
self.assertTrue(re.match('aki-[0-9a-f]{8}',
|
||||
data['meta-data']['kernel-id']))
|
||||
self.assertTrue(re.match('aki-[0-9a-f]{8}', data))
|
||||
|
||||
self.assertEqual(
|
||||
md.lookup("/ec2/2009-04-04/meta-data/kernel-id"), data)
|
||||
|
||||
del inst['kernel_id']
|
||||
md = fake_InstanceMetadata(self.stubs, inst)
|
||||
self.assertRaises(base.InvalidMetadataPath,
|
||||
md.lookup, "/2009-04-04/meta-data/kernel-id")
|
||||
|
||||
def test_check_version(self):
|
||||
inst = copy(self.instance)
|
||||
@@ -222,12 +234,110 @@ class MetadataTestCase(test.TestCase):
|
||||
self.assertTrue(md._check_version('2009-04-04', '2009-04-04'))
|
||||
|
||||
|
||||
class OpenstackMetadataTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(OpenstackMetadataTestCase, self).setUp()
|
||||
self.instance = INSTANCES[0]
|
||||
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
|
||||
spectacular=True)
|
||||
|
||||
def test_top_level_listing(self):
|
||||
# request for /openstack/<version>/ should show metadata.json
|
||||
inst = copy(self.instance)
|
||||
mdinst = fake_InstanceMetadata(self.stubs, inst)
|
||||
|
||||
listing = mdinst.lookup("/openstack/")
|
||||
|
||||
result = mdinst.lookup("/openstack")
|
||||
|
||||
# trailing / should not affect anything
|
||||
self.assertEqual(result, mdinst.lookup("/openstack"))
|
||||
|
||||
# the 'content' should not show up in directory listing
|
||||
self.assertTrue(base.CONTENT_DIR not in result)
|
||||
self.assertTrue('2012-08-10' in result)
|
||||
self.assertTrue('latest' in result)
|
||||
|
||||
def test_version_content_listing(self):
|
||||
# request for /openstack/<version>/ should show metadata.json
|
||||
inst = copy(self.instance)
|
||||
mdinst = fake_InstanceMetadata(self.stubs, inst)
|
||||
|
||||
listing = mdinst.lookup("/openstack/2012-08-10")
|
||||
self.assertTrue("meta_data.json" in listing)
|
||||
|
||||
def test_metadata_json(self):
|
||||
inst = copy(self.instance)
|
||||
content = [
|
||||
('/etc/my.conf', "content of my.conf"),
|
||||
('/root/hello', "content of /root/hello"),
|
||||
]
|
||||
|
||||
mdinst = fake_InstanceMetadata(self.stubs, inst,
|
||||
content=content)
|
||||
mdjson = mdinst.lookup("/openstack/2012-08-10/meta_data.json")
|
||||
mdjson = mdinst.lookup("/openstack/latest/meta_data.json")
|
||||
|
||||
mddict = json.loads(mdjson)
|
||||
|
||||
self.assertEqual(mddict['uuid'], self.instance['uuid'])
|
||||
self.assertTrue('files' in mddict)
|
||||
|
||||
self.assertTrue('public_keys' in mddict)
|
||||
self.assertEqual(mddict['public_keys'][self.instance['key_name']],
|
||||
self.instance['key_data'])
|
||||
|
||||
# verify that each of the things we put in content
|
||||
# resulted in an entry in 'files', that their content
|
||||
# there is as expected, and that /content lists them.
|
||||
for (path, content) in content:
|
||||
fent = [f for f in mddict['files'] if f['path'] == path]
|
||||
self.assertTrue((len(fent) == 1))
|
||||
fent = fent[0]
|
||||
found = mdinst.lookup("/openstack%s" % fent['content_path'])
|
||||
self.assertEqual(found, content)
|
||||
|
||||
def test_extra_md(self):
|
||||
# make sure extra_md makes it through to metadata
|
||||
inst = copy(self.instance)
|
||||
extra = {'foo': 'bar', 'mylist': [1, 2, 3],
|
||||
'mydict': {"one": 1, "two": 2}}
|
||||
mdinst = fake_InstanceMetadata(self.stubs, inst, extra_md=extra)
|
||||
|
||||
mdjson = mdinst.lookup("/openstack/2012-08-10/meta_data.json")
|
||||
mddict = json.loads(mdjson)
|
||||
|
||||
for key, val in extra.iteritems():
|
||||
self.assertEqual(mddict[key], val)
|
||||
|
||||
def test_userdata(self):
|
||||
inst = copy(self.instance)
|
||||
mdinst = fake_InstanceMetadata(self.stubs, inst)
|
||||
|
||||
userdata_found = mdinst.lookup("/openstack/2012-08-10/user_data")
|
||||
self.assertEqual(USER_DATA_STRING, userdata_found)
|
||||
|
||||
# since we had user-data in this instance, it should be in listing
|
||||
self.assertTrue('user_data' in mdinst.lookup("/openstack/2012-08-10"))
|
||||
|
||||
del inst['user_data']
|
||||
mdinst = fake_InstanceMetadata(self.stubs, inst)
|
||||
|
||||
# since this instance had no user-data it should not be there.
|
||||
self.assertFalse('user-data' in mdinst.lookup("/openstack/2012-08-10"))
|
||||
|
||||
self.assertRaises(base.InvalidMetadataPath,
|
||||
mdinst.lookup, "/openstack/2012-08-10/user_data")
|
||||
|
||||
|
||||
class MetadataHandlerTestCase(test.TestCase):
|
||||
"""Test that metadata is returning proper values."""
|
||||
|
||||
def setUp(self):
|
||||
super(MetadataHandlerTestCase, self).setUp()
|
||||
|
||||
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
|
||||
spectacular=True)
|
||||
self.instance = INSTANCES[0]
|
||||
self.mdinst = fake_InstanceMetadata(self.stubs, self.instance,
|
||||
address=None, sgroups=None)
|
||||
|
||||
@@ -49,11 +49,8 @@ FLAGS.register_opts(configdrive_opts)
|
||||
|
||||
|
||||
class ConfigDriveBuilder(object):
|
||||
def __init__(self, instance=None):
|
||||
self.instance = instance
|
||||
def __init__(self, instance_md=None):
|
||||
self.imagefile = None
|
||||
self.injected = {}
|
||||
self.next_inject_id = 1
|
||||
|
||||
# TODO(mikal): I don't think I can use utils.tempdir here, because
|
||||
# I need to have the directory last longer than the scope of this
|
||||
@@ -61,43 +58,21 @@ class ConfigDriveBuilder(object):
|
||||
self.tempdir = tempfile.mkdtemp(dir=FLAGS.config_drive_tempdir,
|
||||
prefix='cd_gen_')
|
||||
|
||||
def _add_file(self, path, data, inject=False):
|
||||
if inject:
|
||||
path_id = '%03d' % self.next_inject_id
|
||||
path = 'openstack/files/%s' % path_id
|
||||
self.injected[path] = path_id
|
||||
self.next_inject_id += 1
|
||||
if instance_md is not None:
|
||||
self.add_instance_metadata(instance_md)
|
||||
|
||||
def _add_file(self, path, data):
|
||||
filepath = os.path.join(self.tempdir, path)
|
||||
dirname = os.path.dirname(filepath)
|
||||
virtutils.ensure_tree(dirname)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(data)
|
||||
|
||||
def add_instance_metadata(self):
|
||||
inst_md = instance_metadata.InstanceMetadata(self.instance)
|
||||
for (path, value) in inst_md.metadata_for_config_drive(self.injected):
|
||||
def add_instance_metadata(self, instance_md):
|
||||
for (path, value) in instance_md.metadata_for_config_drive():
|
||||
self._add_file(path, value)
|
||||
LOG.debug(_('Added %(filepath)s to config drive'),
|
||||
{'filepath': path}, instance=self.instance)
|
||||
|
||||
def inject_data(self, key, net, metadata, admin_pass, files):
|
||||
if key:
|
||||
self._add_file('key', key, inject=True)
|
||||
if net:
|
||||
self._add_file('net', net, inject=True)
|
||||
if metadata:
|
||||
self._add_file('metadata', metadata, inject=True)
|
||||
if admin_pass:
|
||||
self._add_file('adminpass', admin_pass, inject=True)
|
||||
if files:
|
||||
files_struct = []
|
||||
for (path, contents) in files:
|
||||
files.append[{'path': path,
|
||||
'encoding': 'base64',
|
||||
'data': base64.b64encode(contents),
|
||||
}]
|
||||
self._add_file('files', json.dumps(files_struct), inject=True)
|
||||
{'filepath': path})
|
||||
|
||||
def _make_iso9660(self, path):
|
||||
utils.execute('genisoimage',
|
||||
|
||||
@@ -55,6 +55,7 @@ from eventlet import tpool
|
||||
from lxml import etree
|
||||
from xml.dom import minidom
|
||||
|
||||
from nova.api.metadata import base as instance_metadata
|
||||
from nova import block_device
|
||||
from nova.compute import instance_types
|
||||
from nova.compute import power_state
|
||||
@@ -78,9 +79,9 @@ from nova.virt.libvirt import firewall
|
||||
from nova.virt.libvirt import imagebackend
|
||||
from nova.virt.libvirt import imagecache
|
||||
from nova.virt.libvirt import utils as libvirt_utils
|
||||
from nova.virt import netutils
|
||||
|
||||
libvirt = None
|
||||
Template = None
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@@ -246,14 +247,6 @@ MIN_LIBVIRT_VERSION = (0, 9, 6)
|
||||
MIN_LIBVIRT_HOST_CPU_VERSION = (0, 9, 10)
|
||||
|
||||
|
||||
def _late_load_cheetah():
|
||||
global Template
|
||||
if Template is None:
|
||||
t = __import__('Cheetah.Template', globals(), locals(),
|
||||
['Template'], -1)
|
||||
Template = t.Template
|
||||
|
||||
|
||||
def _get_eph_disk(ephemeral):
|
||||
return 'disk.eph' + str(ephemeral['num'])
|
||||
|
||||
@@ -267,8 +260,6 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
if libvirt is None:
|
||||
libvirt = __import__('libvirt')
|
||||
|
||||
_late_load_cheetah()
|
||||
|
||||
self._host_state = None
|
||||
self._initiator = None
|
||||
self._wrapped_conn = None
|
||||
@@ -1349,48 +1340,6 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
key = str(instance['key_data'])
|
||||
else:
|
||||
key = None
|
||||
net = None
|
||||
|
||||
nets = []
|
||||
ifc_template = open(FLAGS.injected_network_template).read()
|
||||
ifc_num = -1
|
||||
have_injected_networks = False
|
||||
for (network_ref, mapping) in network_info:
|
||||
ifc_num += 1
|
||||
|
||||
if not network_ref['injected']:
|
||||
continue
|
||||
|
||||
have_injected_networks = True
|
||||
address = mapping['ips'][0]['ip']
|
||||
netmask = mapping['ips'][0]['netmask']
|
||||
address_v6 = None
|
||||
gateway_v6 = None
|
||||
netmask_v6 = None
|
||||
if FLAGS.use_ipv6:
|
||||
address_v6 = mapping['ip6s'][0]['ip']
|
||||
netmask_v6 = mapping['ip6s'][0]['netmask']
|
||||
gateway_v6 = mapping['gateway_v6']
|
||||
net_info = {'name': 'eth%d' % ifc_num,
|
||||
'address': address,
|
||||
'netmask': netmask,
|
||||
'gateway': mapping['gateway'],
|
||||
'broadcast': mapping['broadcast'],
|
||||
'dns': ' '.join(mapping['dns']),
|
||||
'address_v6': address_v6,
|
||||
'gateway_v6': gateway_v6,
|
||||
'netmask_v6': netmask_v6}
|
||||
nets.append(net_info)
|
||||
|
||||
if have_injected_networks:
|
||||
net = str(Template(ifc_template,
|
||||
searchList=[{'interfaces': nets,
|
||||
'use_ipv6': FLAGS.use_ipv6}]))
|
||||
|
||||
# Config drive
|
||||
cdb = None
|
||||
if using_config_drive:
|
||||
cdb = configdrive.ConfigDriveBuilder(instance=instance)
|
||||
|
||||
# File injection
|
||||
metadata = instance.get('metadata')
|
||||
@@ -1398,39 +1347,17 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
if not FLAGS.libvirt_inject_password:
|
||||
admin_pass = None
|
||||
|
||||
if any((key, net, metadata, admin_pass, files)):
|
||||
if not using_config_drive:
|
||||
# If we're not using config_drive, inject into root fs
|
||||
injection_path = image('disk').path
|
||||
img_id = instance['image_ref']
|
||||
|
||||
for injection in ('metadata', 'key', 'net', 'admin_pass',
|
||||
'files'):
|
||||
if locals()[injection]:
|
||||
LOG.info(_('Injecting %(injection)s into image'
|
||||
' %(img_id)s'), locals(), instance=instance)
|
||||
try:
|
||||
disk.inject_data(injection_path,
|
||||
key, net, metadata, admin_pass, files,
|
||||
partition=target_partition,
|
||||
use_cow=FLAGS.use_cow_images)
|
||||
|
||||
except Exception as e:
|
||||
# This could be a windows image, or a vmdk format disk
|
||||
LOG.warn(_('Ignoring error injecting data into image '
|
||||
'%(img_id)s (%(e)s)') % locals(),
|
||||
instance=instance)
|
||||
|
||||
else:
|
||||
# We're using config_drive, so put the files there instead
|
||||
cdb.inject_data(key, net, metadata, admin_pass, files)
|
||||
net = netutils.get_injected_network_template(network_info)
|
||||
|
||||
# Config drive
|
||||
if using_config_drive:
|
||||
# NOTE(mikal): Render the config drive. We can't add instance
|
||||
# metadata here until after file injection, as the file injection
|
||||
# creates state the openstack metadata relies on.
|
||||
cdb.add_instance_metadata()
|
||||
extra_md = {}
|
||||
if admin_pass:
|
||||
extra_md['admin_pass'] = admin_pass
|
||||
|
||||
inst_md = instance_metadata.InstanceMetadata(instance,
|
||||
content=files, extra_md=extra_md)
|
||||
cdb = configdrive.ConfigDriveBuilder(instance_md=inst_md)
|
||||
try:
|
||||
configdrive_path = basepath(fname='disk.config')
|
||||
LOG.info(_('Creating config drive at %(path)s'),
|
||||
@@ -1439,6 +1366,28 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
finally:
|
||||
cdb.cleanup()
|
||||
|
||||
elif any((key, net, metadata, admin_pass, files)):
|
||||
# If we're not using config_drive, inject into root fs
|
||||
injection_path = image('disk').path
|
||||
img_id = instance['image_ref']
|
||||
|
||||
for injection in ('metadata', 'key', 'net', 'admin_pass',
|
||||
'files'):
|
||||
if locals()[injection]:
|
||||
LOG.info(_('Injecting %(injection)s into image'
|
||||
' %(img_id)s'), locals(), instance=instance)
|
||||
try:
|
||||
disk.inject_data(injection_path,
|
||||
key, net, metadata, admin_pass, files,
|
||||
partition=target_partition,
|
||||
use_cow=FLAGS.use_cow_images)
|
||||
|
||||
except Exception as e:
|
||||
# This could be a windows image, or a vmdk format disk
|
||||
LOG.warn(_('Ignoring error injecting data into image '
|
||||
'%(img_id)s (%(e)s)') % locals(),
|
||||
instance=instance)
|
||||
|
||||
if FLAGS.libvirt_type == 'lxc':
|
||||
disk.setup_container(basepath('disk'),
|
||||
container_dir=container_dir,
|
||||
|
||||
@@ -28,6 +28,18 @@ from nova import flags
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
flags.DECLARE('injected_network_template', 'nova.virt.disk.api')
|
||||
|
||||
Template = None
|
||||
|
||||
|
||||
def _late_load_cheetah():
|
||||
global Template
|
||||
if Template is None:
|
||||
t = __import__('Cheetah.Template', globals(), locals(),
|
||||
['Template'], -1)
|
||||
Template = t.Template
|
||||
|
||||
|
||||
def get_net_and_mask(cidr):
|
||||
net = netaddr.IPNetwork(cidr)
|
||||
@@ -42,3 +54,64 @@ def get_net_and_prefixlen(cidr):
|
||||
def get_ip_version(cidr):
|
||||
net = netaddr.IPNetwork(cidr)
|
||||
return int(net.version)
|
||||
|
||||
|
||||
def get_injected_network_template(network_info, use_ipv6=FLAGS.use_ipv6,
|
||||
template=FLAGS.injected_network_template):
|
||||
"""
|
||||
return a rendered network template for the given network_info
|
||||
|
||||
:param network_info:
|
||||
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
|
||||
|
||||
Note: this code actually depends on the legacy network_info, but will
|
||||
convert the type itself if necessary.
|
||||
"""
|
||||
|
||||
# the code below depends on the legacy 'network_info'
|
||||
if hasattr(network_info, 'legacy'):
|
||||
network_info = network_info.legacy()
|
||||
|
||||
nets = []
|
||||
ifc_num = -1
|
||||
have_injected_networks = False
|
||||
|
||||
for (network_ref, mapping) in network_info:
|
||||
ifc_num += 1
|
||||
|
||||
if not network_ref['injected']:
|
||||
continue
|
||||
|
||||
have_injected_networks = True
|
||||
address = mapping['ips'][0]['ip']
|
||||
netmask = mapping['ips'][0]['netmask']
|
||||
address_v6 = None
|
||||
gateway_v6 = None
|
||||
netmask_v6 = None
|
||||
if use_ipv6:
|
||||
address_v6 = mapping['ip6s'][0]['ip']
|
||||
netmask_v6 = mapping['ip6s'][0]['netmask']
|
||||
gateway_v6 = mapping['gateway_v6']
|
||||
net_info = {'name': 'eth%d' % ifc_num,
|
||||
'address': address,
|
||||
'netmask': netmask,
|
||||
'gateway': mapping['gateway'],
|
||||
'broadcast': mapping['broadcast'],
|
||||
'dns': ' '.join(mapping['dns']),
|
||||
'address_v6': address_v6,
|
||||
'gateway_v6': gateway_v6,
|
||||
'netmask_v6': netmask_v6}
|
||||
nets.append(net_info)
|
||||
|
||||
if have_injected_networks is False:
|
||||
return None
|
||||
|
||||
if not template:
|
||||
return None
|
||||
|
||||
_late_load_cheetah()
|
||||
|
||||
ifc_template = open(template).read()
|
||||
return str(Template(ifc_template,
|
||||
searchList=[{'interfaces': nets,
|
||||
'use_ipv6': use_ipv6}]))
|
||||
|
||||
Reference in New Issue
Block a user