Allow setting config drive option when launching instance

This patch adds a "Configuration Drive" checkbox to the Advanced tab
of the Launch Instance workflow, if the config drive extension is
supported. This allows setting the config drive option when launching
an instance.

Change-Id: Ib4e4e18251f4f024a61dabb646cda79a7c88582d
Closes-Bug: #1366842
This commit is contained in:
Justin Pomeroy 2014-09-08 16:52:06 -05:00
parent 9d4d2740b1
commit 69e6676588
4 changed files with 97 additions and 17 deletions

View File

@ -535,7 +535,7 @@ def server_create(request, name, image, flavor, key_name, user_data,
security_groups, block_device_mapping=None,
block_device_mapping_v2=None, nics=None,
availability_zone=None, instance_count=1, admin_pass=None,
disk_config=None, meta=None):
disk_config=None, config_drive=None, meta=None):
return Server(novaclient(request).servers.create(
name, image, flavor, userdata=user_data,
security_groups=security_groups,
@ -543,7 +543,8 @@ def server_create(request, name, image, flavor, key_name, user_data,
block_device_mapping_v2=block_device_mapping_v2,
nics=nics, availability_zone=availability_zone,
min_count=instance_count, admin_pass=admin_pass,
disk_config=disk_config, meta=meta), request)
disk_config=disk_config, config_drive=config_drive,
meta=meta), request)
def server_delete(request, instance):

View File

@ -1,3 +1,2 @@
{% load i18n %}
<p>{% blocktrans %}Automatic: Entire disk is single partition and automatically resizes.{% endblocktrans %}</p>
<p>{% blocktrans %}Manual: Faster build times but requires manual partitioning.{% endblocktrans %}</p>
<p>{% blocktrans %}Specify advanced options to use when launching an instance.{% endblocktrans %}</p>

View File

@ -1209,6 +1209,7 @@ class InstanceTests(helpers.TestCase):
custom_flavor_sort=None,
only_one_network=False,
disk_config=True,
config_drive=True,
test_with_profile=False):
image = self.images.first()
@ -1245,6 +1246,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(disk_config)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(config_drive)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -1335,6 +1338,12 @@ class InstanceTests(helpers.TestCase):
else:
self.assertNotContains(res, disk_config_field_label)
config_drive_field_label = 'Configuration Drive'
if config_drive:
self.assertContains(res, config_drive_field_label)
else:
self.assertNotContains(res, config_drive_field_label)
@django.test.utils.override_settings(
OPENSTACK_HYPERVISOR_FEATURES={'can_set_password': False})
def test_launch_instance_get_without_password(self):
@ -1346,6 +1355,9 @@ class InstanceTests(helpers.TestCase):
def test_launch_instance_get_no_disk_config_supported(self):
self.test_launch_instance_get(disk_config=False)
def test_launch_instance_get_no_config_drive_supported(self):
self.test_launch_instance_get(config_drive=False)
@django.test.utils.override_settings(
CREATE_INSTANCE_FLAVOR_SORT={
'key': 'id',
@ -1402,6 +1414,7 @@ class InstanceTests(helpers.TestCase):
block_device_mapping_v2=True,
only_one_network=False,
disk_config=True,
config_drive=True,
test_with_profile=False):
api.nova.extension_supported('BlockDeviceMappingV2Boot',
IsA(http.HttpRequest)) \
@ -1437,6 +1450,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(disk_config)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(config_drive)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -1491,6 +1506,7 @@ class InstanceTests(helpers.TestCase):
quotas: ('tenant_quota_usages',)})
def test_launch_instance_post(self,
disk_config=True,
config_drive=True,
test_with_profile=False):
flavor = self.flavors.first()
image = self.images.first()
@ -1543,6 +1559,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(disk_config)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(config_drive)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn([])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -1550,6 +1568,10 @@ class InstanceTests(helpers.TestCase):
disk_config_value = u'AUTO'
else:
disk_config_value = None
if config_drive:
config_drive_value = True
else:
config_drive_value = None
api.nova.server_create(IsA(http.HttpRequest),
server.name,
image.id,
@ -1563,7 +1585,8 @@ class InstanceTests(helpers.TestCase):
availability_zone=avail_zone.zoneName,
instance_count=IsA(int),
admin_pass=u'',
disk_config=disk_config_value)
disk_config=disk_config_value,
config_drive=config_drive_value)
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -1586,6 +1609,8 @@ class InstanceTests(helpers.TestCase):
'count': 1}
if disk_config:
form_data['disk_config'] = 'AUTO'
if config_drive:
form_data['config_drive'] = True
if test_with_profile:
form_data['profile'] = self.policy_profiles.first().id
url = reverse('horizon:project:instances:launch')
@ -1597,6 +1622,9 @@ class InstanceTests(helpers.TestCase):
def test_launch_instance_post_no_disk_config_supported(self):
self.test_launch_instance_post(disk_config=False)
def test_launch_instance_post_no_config_drive_supported(self):
self.test_launch_instance_post(config_drive=False)
@helpers.update_settings(
OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'})
def test_launch_instance_post_with_profile(self):
@ -1673,6 +1701,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -1689,7 +1719,8 @@ class InstanceTests(helpers.TestCase):
availability_zone=avail_zone.zoneName,
instance_count=IsA(int),
admin_pass=u'',
disk_config=u'AUTO')
disk_config=u'AUTO',
config_drive=True)
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(quota_usages)
@ -1710,7 +1741,8 @@ class InstanceTests(helpers.TestCase):
'device_name': device_name,
'network': self.networks.first().id,
'count': 1,
'disk_config': 'AUTO'}
'disk_config': 'AUTO',
'config_drive': True}
if test_with_profile:
form_data['profile'] = self.policy_profiles.first().id
url = reverse('horizon:project:instances:launch')
@ -1799,6 +1831,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -1818,7 +1852,8 @@ class InstanceTests(helpers.TestCase):
availability_zone=avail_zone.zoneName,
instance_count=IsA(int),
admin_pass=u'',
disk_config='MANUAL')
disk_config='MANUAL',
config_drive=True)
self.mox.ReplayAll()
@ -1837,7 +1872,8 @@ class InstanceTests(helpers.TestCase):
'volume_id': volume_choice,
'device_name': device_name,
'count': 1,
'disk_config': 'MANUAL'}
'disk_config': 'MANUAL',
'config_drive': True}
if test_with_profile:
form_data['profile'] = self.policy_profiles.first().id
url = reverse('horizon:project:instances:launch')
@ -1905,6 +1941,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
@ -1987,6 +2025,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -2078,6 +2118,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_list(IgnoreArg()).AndReturn(self.volumes.list())
api.nova.server_create(IsA(http.HttpRequest),
server.name,
@ -2092,7 +2134,8 @@ class InstanceTests(helpers.TestCase):
availability_zone=avail_zone.zoneName,
instance_count=IsA(int),
admin_pass='password',
disk_config='AUTO') \
disk_config='AUTO',
config_drive=False) \
.AndRaise(self.exceptions.keystone)
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(quota_usages)
@ -2118,7 +2161,8 @@ class InstanceTests(helpers.TestCase):
'count': 1,
'admin_pass': 'password',
'confirm_admin_pass': 'password',
'disk_config': 'AUTO'}
'disk_config': 'AUTO',
'config_drive': False}
if test_with_profile:
form_data['profile'] = self.policy_profiles.first().id
url = reverse('horizon:project:instances:launch')
@ -2190,6 +2234,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -2288,6 +2334,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -2403,6 +2451,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -2529,6 +2579,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -2725,6 +2777,8 @@ class InstanceTests(helpers.TestCase):
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -2845,6 +2899,9 @@ class InstanceTests(helpers.TestCase):
self.assertTemplateUsed(res, views.WorkflowView.template_name)
config_drive_field_label = 'Configuration Drive'
self.assertNotContains(res, config_drive_field_label)
@helpers.create_stubs({api.nova: ('server_get',
'flavor_list',)})
def test_instance_resize_get_server_get_exception(self):

View File

@ -663,11 +663,18 @@ class SetNetwork(workflows.Step):
class SetAdvancedAction(workflows.Action):
disk_config = forms.ChoiceField(label=_("Disk Partition"),
required=False)
disk_config = forms.ChoiceField(label=_("Disk Partition"), required=False,
help_text=_("Automatic: The entire disk is a single partition and "
"automatically resizes. Manual: Results in faster build "
"times but requires manual partitioning."))
config_drive = forms.BooleanField(label=_("Configuration Drive"),
required=False, help_text=_("Configure OpenStack to write metadata to "
"a special configuration drive that "
"attaches to the instance when it boots."))
def __init__(self, request, *args, **kwargs):
super(SetAdvancedAction, self).__init__(request, *args, **kwargs)
def __init__(self, request, context, *args, **kwargs):
super(SetAdvancedAction, self).__init__(request, context,
*args, **kwargs)
try:
if not api.nova.extension_supported("DiskConfig", request):
del self.fields['disk_config']
@ -676,6 +683,12 @@ class SetAdvancedAction(workflows.Action):
config_choices = [("AUTO", _("Automatic")),
("MANUAL", _("Manual"))]
self.fields['disk_config'].choices = config_choices
# Only show the Config Drive option for the Launch Instance
# workflow (not Resize Instance) and only if the extension
# is supported.
if context.get('workflow_slug') != 'launch_instance' or (
not api.nova.extension_supported("ConfigDrive", request)):
del self.fields['config_drive']
except Exception:
exceptions.handle(request, _('Unable to retrieve extensions '
'information.'))
@ -688,7 +701,16 @@ class SetAdvancedAction(workflows.Action):
class SetAdvanced(workflows.Step):
action_class = SetAdvancedAction
contributes = ("disk_config",)
contributes = ("disk_config", "config_drive",)
def prepare_action_context(self, request, context):
context = super(SetAdvanced, self).prepare_action_context(request,
context)
# Add the workflow slug to the context so that we can tell which
# workflow is being used when creating the action. This step is
# used by both the Launch Instance and Resize Instance workflows.
context['workflow_slug'] = self.workflow.slug
return context
class LaunchInstance(workflows.Workflow):
@ -788,7 +810,8 @@ class LaunchInstance(workflows.Workflow):
availability_zone=avail_zone,
instance_count=int(context['count']),
admin_pass=context['admin_pass'],
disk_config=context.get('disk_config'))
disk_config=context.get('disk_config'),
config_drive=context.get('config_drive'))
return True
except Exception:
exceptions.handle(request)