Config-Drive happiness, minus smoketest
This commit is contained in:
12 changed files with 265 additions and 22 deletions
1
Authors
1
Authors
@@ -16,6 +16,7 @@ Chiradeep Vittal <chiradeep@cloud.com>
Chmouel Boudjnah <chmouel@chmouel.com>
Chris Behrens <cbehrens@codestud.com>
Christian Berendt <berendt@b1-systems.de>
Christopher MacGown <chris@pistoncloud.com>
Chuck Short <zulcss@ubuntu.com>
Cory Wright <corywright@gmail.com>
Dan Prince <dan.prince@rackspace.com>
@@ -96,6 +96,7 @@ class CreateInstanceHelper(object):
locals())
raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
config_drive = body['server'].get('config_drive')
personality = body['server'].get('personality')
injected_files = []
@@ -130,6 +131,7 @@ class CreateInstanceHelper(object):
extra_values = {
'instance_type': inst_type,
'image_ref': image_href,
'config_drive': config_drive,
'password': password}
return (extra_values,
@@ -148,7 +150,8 @@ class CreateInstanceHelper(object):
zone_blob=zone_blob,
reservation_id=reservation_id,
min_count=min_count,
max_count=max_count))
max_count=max_count,
config_drive=config_drive,))
except quota.QuotaError as error:
self._handle_quota_error(error)
except exception.ImageNotFound as error:
@@ -160,6 +163,8 @@ class CreateInstanceHelper(object):
def _handle_quota_error(self, error):
"""
Reraise quota errors as api-specific http exceptions
"""
if error.code == "OnsetFileLimitExceeded":
expl = _("Personality file limit exceeded")
@@ -164,6 +164,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_extra(self, response, inst):
self._build_links(response, inst)
self._build_config_drive(response, inst)
def _build_links(self, response, inst):
href = self.generate_href(inst["id"])
@@ -182,6 +183,9 @@ class ViewBuilderV11(ViewBuilder):
response["server"]["links"] = links
def _build_config_drive(self, response, inst):
response['server']['config_drive'] = inst.get('config_drive')
def generate_href(self, server_id):
"""Create an url that refers to a specific server id."""
return os.path.join(self.base_url, "servers", str(server_id))
@@ -150,7 +150,7 @@ class API(base.Base):
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata={},
injected_files=None, admin_password=None, zone_blob=None,
reservation_id=None):
reservation_id=None, config_drive=None,):
"""Verify all the input parameters regardless of the provisioning
strategy being performed."""
@@ -181,6 +181,11 @@ class API(base.Base):
(image_service, image_id) = nova.image.get_image_service(image_href)
image = image_service.show(context, image_id)
config_drive_id = None
if config_drive and config_drive is not True:
# config_drive is volume id
config_drive, config_drive_id = None, config_drive
os_type = None
if 'properties' in image and 'os_type' in image['properties']:
os_type = image['properties']['os_type']
@@ -208,6 +213,8 @@ class API(base.Base):
image_service.show(context, kernel_id)
if ramdisk_id:
image_service.show(context, ramdisk_id)
if config_drive_id:
image_service.show(context, config_drive_id)
self.ensure_default_security_group(context)
@@ -226,6 +233,8 @@ class API(base.Base):
'image_ref': image_href,
'kernel_id': kernel_id or '',
'ramdisk_id': ramdisk_id or '',
'config_drive_id': config_drive_id or '',
'config_drive': config_drive or '',
'state': 0,
'state_description': 'scheduling',
'user_id': context.user_id,
@@ -397,7 +406,8 @@ class API(base.Base):
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata={},
injected_files=None, admin_password=None, zone_blob=None,
reservation_id=None, block_device_mapping=None):
reservation_id=None, block_device_mapping=None,
config_drive=None):
"""Provision the instances by passing the whole request to
the Scheduler for execution. Returns a Reservation ID
related to the creation of all of these instances."""
@@ -409,7 +419,7 @@ class API(base.Base):
key_name, key_data, security_group,
availability_zone, user_data, metadata,
injected_files, admin_password, zone_blob,
reservation_id)
reservation_id, config_drive)
self._ask_scheduler_to_create_instance(context, base_options,
instance_type, zone_blob,
@@ -426,7 +436,8 @@ class API(base.Base):
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata={},
injected_files=None, admin_password=None, zone_blob=None,
reservation_id=None, block_device_mapping=None):
reservation_id=None, block_device_mapping=None,
config_drive=None,):
"""
Provision the instances by sending off a series of single
instance requests to the Schedulers. This is fine for trival
@@ -447,7 +458,7 @@ class API(base.Base):
key_name, key_data, security_group,
availability_zone, user_data, metadata,
injected_files, admin_password, zone_blob,
reservation_id)
reservation_id, config_drive)
block_device_mapping = block_device_mapping or []
instances = []
@@ -0,0 +1,43 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
#
# 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.
from sqlalchemy import Column, Integer, MetaData, String, Table
from nova import utils
meta = MetaData()
instances = Table("instances", meta,
Column("id", Integer(), primary_key=True, nullable=False))
config_drive_column = Column("config_drive", String(255)) # matches image_ref
def upgrade(migrate_engine):
meta.bind = migrate_engine
instances.create_column(config_drive_column)
rows = migrate_engine.execute(instances.select())
for row in rows:
instance_config_drive = None # pre-existing instances don't have one.
migrate_engine.execute(instances.update()\
.where(instances.c.id == row[0])\
.values(config_drive=instance_config_drive))
def downgrade(migrate_engine):
meta.bind = migrate_engine
instances.drop_column(config_drive_column)
@@ -238,6 +238,7 @@ class Instance(BASE, NovaBase):
uuid = Column(String(36))
root_device_name = Column(String(255))
config_drive = Column(String(255))
# TODO(vish): see Ewan's email about state improvements, probably
# should be in a driver base class or some such
@@ -41,6 +41,7 @@ class SimpleScheduler(chance.ChanceScheduler):
def _schedule_instance(self, context, instance_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest running instances."""
instance_ref = db.instance_get(context, instance_id)
if (instance_ref['availability_zone']
and ':' in instance_ref['availability_zone']
@@ -775,11 +775,14 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 400)
self.assertTrue(res.body.find('marker param') > -1)
def _setup_for_create_instance(self):
def _setup_for_create_instance(self, instance_db_stub=None):
"""Shared implementation for tests below that create instance"""
def instance_create(context, inst):
return {'id': 1, 'display_name': 'server_test',
'uuid': FAKE_UUID}
if instance_db_stub:
return instance_db_stub
else:
return {'id': 1, 'display_name': 'server_test',
'uuid': FAKE_UUID,}
def server_update(context, id, params):
return instance_create(context, id)
@@ -997,6 +1000,132 @@ class ServersTest(test.TestCase):
self.assertEqual(flavor_ref, server['flavorRef'])
self.assertEqual(image_href, server['imageRef'])
def test_create_instance_with_config_drive_v1_1(self):
db_stub = {'id': 100, 'display_name': 'config_drive_test',
'uuid': FAKE_UUID, 'config_drive': True}
self._setup_for_create_instance(instance_db_stub=db_stub)
image_href = 'http://localhost/v1.1/images/2'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'config_drive_test',
'imageRef': image_href,
'flavorRef': flavor_ref,
'metadata': {
'hello': 'world',
'open': 'stack',
},
'personality': {},
'config_drive': True,
},
}
req = webob.Request.blank('/v1.1/servers')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
server = json.loads(res.body)['server']
self.assertEqual(100, server['id'])
self.assertTrue(server['config_drive'])
def test_create_instance_with_config_drive_as_id_v1_1(self):
db_stub = {'id': 100, 'display_name': 'config_drive_test',
'uuid': FAKE_UUID, 'config_drive': 2}
self._setup_for_create_instance(instance_db_stub=db_stub)
image_href = 'http://localhost/v1.1/images/2'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'config_drive_test',
'imageRef': image_href,
'flavorRef': flavor_ref,
'metadata': {
'hello': 'world',
'open': 'stack',
},
'personality': {},
'config_drive': 2,
},
}
req = webob.Request.blank('/v1.1/servers')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
server = json.loads(res.body)['server']
self.assertEqual(100, server['id'])
self.assertTrue(server['config_drive'])
self.assertEqual(2, server['config_drive'])
def test_create_instance_with_bad_config_drive_v1_1(self):
db_stub = {'id': 100, 'display_name': 'config_drive_test',
'uuid': FAKE_UUID, 'config_drive': 'asdf'}
self._setup_for_create_instance(instance_db_stub=db_stub)
image_href = 'http://localhost/v1.1/images/2'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'config_drive_test',
'imageRef': image_href,
'flavorRef': flavor_ref,
'metadata': {
'hello': 'world',
'open': 'stack',
},
'personality': {},
'config_drive': 'asdf',
},
}
req = webob.Request.blank('/v1.1/servers')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_create_instance_without_config_drive_v1_1(self):
db_stub = {'id': 100, 'display_name': 'config_drive_test',
'uuid': FAKE_UUID, 'config_drive': None}
self._setup_for_create_instance(instance_db_stub=db_stub)
image_href = 'http://localhost/v1.1/images/2'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'config_drive_test',
'imageRef': image_href,
'flavorRef': flavor_ref,
'metadata': {
'hello': 'world',
'open': 'stack',
},
'personality': {},
'config_drive': True,
},
}
req = webob.Request.blank('/v1.1/servers')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
server = json.loads(res.body)['server']
self.assertEqual(100, server['id'])
self.assertFalse(server['config_drive'])
def test_create_instance_v1_1_bad_href(self):
self._setup_for_create_instance()
@@ -162,6 +162,20 @@ class ComputeTestCase(test.TestCase):
db.security_group_destroy(self.context, group['id'])
db.instance_destroy(self.context, ref[0]['id'])
def test_create_instance_associates_config_drive(self):
"""Make sure create associates a config drive."""
instance_id = self._create_instance(params={'config_drive': True,})
try:
self.compute.run_instance(self.context, instance_id)
instances = db.instance_get_all(context.get_admin_context())
instance = instances[0]
self.assertTrue(instance.config_drive)
finally:
db.instance_destroy(self.context, instance_id)
def test_default_hostname_generator(self):
cases = [(None, 'server_1'), ('Hello, Server!', 'hello_server'),
('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello')]
@@ -55,6 +55,13 @@
<target dir='/'/>
</filesystem>
#else
#if $getVar('config', False)
<disk type='file'>
<driver type='${driver_type}' />
<source file='${basepath}/disk.config' />
<target dev='${disk_prefix}z' bus='${disk_bus}' />
</disk>
#end if
#if $getVar('rescue', False)
<disk type='file'>
<driver type='${driver_type}'/>
@@ -123,6 +123,8 @@ flags.DEFINE_string('qemu_img', 'qemu-img',
'binary to use for qemu-img commands')
flags.DEFINE_bool('start_guests_on_host_boot', False,
'Whether to restart guests when the host reboots')
flags.DEFINE_string('default_local_format', None,
'Default filesystem format for local drives')
def get_connection(read_only):
@@ -586,6 +588,8 @@ class LibvirtConnection(driver.ComputeDriver):
block_device_mapping = block_device_mapping or []
self.firewall_driver.setup_basic_filtering(instance, network_info)
self.firewall_driver.prepare_instance_filter(instance, network_info)
# This is where things actually get built.
self._create_image(instance, xml, network_info=network_info,
block_device_mapping=block_device_mapping)
domain = self._create_new_domain(xml)
@@ -763,10 +767,15 @@ class LibvirtConnection(driver.ComputeDriver):
if size:
disk.extend(target, size)
def _create_local(self, target, local_gb):
def _create_local(self, target, local_size, prefix='G', fs_format=None):
"""Create a blank image of specified size"""
utils.execute('truncate', target, '-s', "%dG" % local_gb)
# TODO(vish): should we format disk by default?
if not fs_format:
fs_format = FLAGS.default_local_format
utils.execute('truncate', target, '-s', "%d%c" % (local_size, prefix))
if fs_format:
utils.execute('mkfs', '-t', fs_format, target)
def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None,
network_info=None, block_device_mapping=None):
@@ -859,6 +868,18 @@ class LibvirtConnection(driver.ComputeDriver):
if FLAGS.libvirt_type == 'lxc':
target_partition = None
else:
if inst['config_drive_id']:
fname = '%08x' % int(inst['config_drive_id'])
self._cache_image(fn=self._fetch_image,
target=basepath('config'),
fname=fname,
image_id=inst['config_drive_id'],
user=user,
project=project)
elif inst['config_drive']:
self._create_local(basepath('config'), 64, prefix="M",
fs_format='msdos') # 64MB
if inst['key_data']:
key = str(inst['key_data'])
@@ -903,17 +924,23 @@ class LibvirtConnection(driver.ComputeDriver):
searchList=[{'interfaces': nets,
'use_ipv6': FLAGS.use_ipv6}]))
if key or net:
if any(key, net, inst['metadata']):
inst_name = inst['name']
img_id = inst.image_ref
if key:
LOG.info(_('instance %(inst_name)s: injecting key into'
' image %(img_id)s') % locals())
if net:
LOG.info(_('instance %(inst_name)s: injecting net into'
' image %(img_id)s') % locals())
if inst['config_drive']: # Should be True or None by now.
injection_path = basepath('config')
img_id = 'config-drive'
else:
injection_path = basepath('disk')
img_id = inst.image_ref
for injection in ('metadata', 'key', 'net'):
if locals()[injection]:
LOG.info(_('instance %(inst_name)s: injecting '
'%(injection)s into image %(img_id)s'
% locals()))
try:
disk.inject_data(basepath('disk'), key, net,
disk.inject_data(injection_path, key, net, inst['metadata'],
partition=target_partition,
nbd=FLAGS.use_cow_images)
@@ -59,7 +59,7 @@ function run_tests {
ERRSIZE=`wc -l run_tests.log | awk '{print \1ドル}'`
if [ "$ERRSIZE" -lt "40" ];
then
cat run_tests.log
echo cat run_tests.log
fi
fi
return $RESULT
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.