Skip to content

Commit

Permalink
Fixes boot from volume without image
Browse files Browse the repository at this point in the history
Fixes Bug #1163566

I've also disabled booting from volume with an image specified
although Nova does support this, I've chosen to still disable it in
Horizon because it doesn't make much sense. Tihomir Trifonov also
noted this.

Change-Id: I17f3edbbb0cabe1cf415484f32016aab7a332ed4
  • Loading branch information
samos123 committed May 27, 2013
1 parent 24d9f14 commit 8f69a8d
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 25 deletions.
253 changes: 238 additions & 15 deletions openstack_dashboard/dashboards/project/instances/tests.py
Expand Up @@ -866,6 +866,80 @@ def test_launch_instance_get(self):
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_instance_post(self):
flavor = self.flavors.first()
image = self.images.first()
keypair = self.keypairs.first()
server = self.servers.first()
sec_group = self.security_groups.first()
customization_script = 'user data'
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]

api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
.AndReturn(self.keypairs.list())
api.nova.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
'status': 'active'}) \
.AndReturn([self.images.list(), False])
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
api.quantum.network_list(IsA(http.HttpRequest),
tenant_id=self.tenant.id,
shared=False) \
.AndReturn(self.networks.list()[:1])
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn([])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.nova.server_create(IsA(http.HttpRequest),
server.name,
image.id,
flavor.id,
keypair.name,
customization_script,
[sec_group.name],
None,
nics=nics,
instance_count=IsA(int),
admin_pass=u'')

self.mox.ReplayAll()

form_data = {'flavor': flavor.id,
'source_type': 'image_id',
'image_id': image.id,
'keypair': keypair.name,
'name': server.name,
'customization_script': customization_script,
'project_id': self.tenants.first().id,
'user_id': self.user.id,
'groups': sec_group.name,
'volume_type': '',
'network': self.networks.first().id,
'count': 1}
url = reverse('horizon:project:instances:launch')
res = self.client.post(url, form_data)

self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)

@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'server_create',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_instance_post_boot_from_volume_with_image(self):
flavor = self.flavors.first()
image = self.images.first()
keypair = self.keypairs.first()
Expand All @@ -875,6 +949,81 @@ def test_launch_instance_post(self):
customization_script = 'user data'
device_name = u'vda'
volume_choice = "%s:vol" % volume.id

api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn({})
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
'status': 'active'}) \
.AndReturn([[], False])
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([self.images.list(), False])
api.quantum.network_list(IsA(http.HttpRequest),
tenant_id=self.tenant.id,
shared=False) \
.AndReturn(self.networks.list()[:1])
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
.AndReturn(self.keypairs.list())
api.nova.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])

self.mox.ReplayAll()

form_data = {'flavor': flavor.id,
'source_type': 'image_id',
'image_id': image.id,
'keypair': keypair.name,
'name': server.name,
'customization_script': customization_script,
'project_id': self.tenants.first().id,
'user_id': self.user.id,
'groups': sec_group.name,
'volume_type': 'volume_id',
'volume_id': volume_choice,
'device_name': device_name,
'network': self.networks.first().id,
'count': 1,
'admin_pass': 'password',
'confirm_admin_pass': 'password'}
url = reverse('horizon:project:instances:launch')
res = self.client.post(url, form_data)

self.assertFormErrors(res, 1, "select an instance "
"source when booting from a "
"Volume. The Volume is your "
"source and should contain "
"the operating system.")
self.assertTemplateUsed(res, WorkflowView.template_name)

@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'server_create',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_instance_post_boot_from_volume(self):
flavor = self.flavors.first()
keypair = self.keypairs.first()
server = self.servers.first()
volume = self.volumes.first()
sec_group = self.security_groups.first()
customization_script = 'user data'
device_name = u'vda'
volume_choice = "%s:vol" % volume.id
block_device_mapping = {device_name: u"%s::0" % volume_choice}
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]

Expand Down Expand Up @@ -904,21 +1053,20 @@ def test_launch_instance_post(self):
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.nova.server_create(IsA(http.HttpRequest),
server.name,
image.id,
'',
flavor.id,
keypair.name,
customization_script,
[sec_group.name],
block_device_mapping,
nics=nics,
instance_count=IsA(int),
admin_pass='password')
admin_pass=u'')

self.mox.ReplayAll()

form_data = {'flavor': flavor.id,
'source_type': 'image_id',
'image_id': image.id,
'keypair': keypair.name,
'name': server.name,
'customization_script': customization_script,
Expand All @@ -929,9 +1077,7 @@ def test_launch_instance_post(self):
'volume_id': volume_choice,
'device_name': device_name,
'network': self.networks.first().id,
'count': 1,
'admin_pass': 'password',
'confirm_admin_pass': 'password'}
'count': 1}
url = reverse('horizon:project:instances:launch')
res = self.client.post(url, form_data)

Expand All @@ -941,12 +1087,13 @@ def test_launch_instance_post(self):
@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list',
api.nova: ('server_create',
'flavor_list',
'keypair_list',
'security_group_list',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_instance_post_no_images_available(self):
def test_launch_instance_post_no_images_available_boot_from_volume(self):
flavor = self.flavors.first()
keypair = self.keypairs.first()
server = self.servers.first()
Expand All @@ -955,6 +1102,82 @@ def test_launch_instance_post_no_images_available(self):
customization_script = 'user data'
device_name = u'vda'
volume_choice = "%s:vol" % volume.id
block_device_mapping = {device_name: u"%s::0" % volume_choice}
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]

api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
.AndReturn(self.keypairs.list())
api.nova.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
'status': 'active'}) \
.AndReturn([self.images.list(), False])
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False])
api.quantum.network_list(IsA(http.HttpRequest),
tenant_id=self.tenant.id,
shared=False) \
.AndReturn(self.networks.list()[:1])
api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])

api.nova.server_create(IsA(http.HttpRequest),
server.name,
'',
flavor.id,
keypair.name,
customization_script,
[sec_group.name],
block_device_mapping,
nics=nics,
instance_count=IsA(int),
admin_pass=u'')

self.mox.ReplayAll()

form_data = {'flavor': flavor.id,
'source_type': 'image_id',
'image_id': '',
'keypair': keypair.name,
'name': server.name,
'customization_script': customization_script,
'project_id': self.tenants.first().id,
'user_id': self.user.id,
'groups': sec_group.name,
'network': self.networks.first().id,
'volume_type': 'volume_id',
'volume_id': volume_choice,
'device_name': device_name,
'count': 1}
url = reverse('horizon:project:instances:launch')
res = self.client.post(url, form_data)

self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)

@test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',),
cinder: ('volume_list',
'volume_snapshot_list',)})
def test_launch_instance_post_no_images_available(self):
flavor = self.flavors.first()
keypair = self.keypairs.first()
server = self.servers.first()
sec_group = self.security_groups.first()
customization_script = 'user data'

api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
Expand All @@ -981,7 +1204,7 @@ def test_launch_instance_post_no_images_available(self):
api.nova.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
.AndReturn([])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])

self.mox.ReplayAll()
Expand All @@ -995,16 +1218,16 @@ def test_launch_instance_post_no_images_available(self):
'project_id': self.tenants.first().id,
'user_id': self.user.id,
'groups': sec_group.name,
'volume_type': 'volume_id',
'volume_id': volume_choice,
'device_name': device_name,
'volume_type': '',
'count': 1}
url = reverse('horizon:project:instances:launch')
res = self.client.post(url, form_data)

self.assertFormErrors(res, 1, 'There are no image sources available; '
'you must first create an image before '
'attempting to launch an instance.')
self.assertFormErrors(res, 1, "There are no image sources "
"available; you must first "
"create an image before "
"attemtping to launch an "
"instance.")
self.assertTemplateUsed(res, WorkflowView.template_name)

@test.create_stubs({api.glance: ('image_list_detailed',),
Expand Down
Expand Up @@ -199,21 +199,30 @@ def clean(self):
source = cleaned_data['source_type']
# There should always be at least one image_id choice, telling the user
# that there are "No Images Available" so we check for 2 here...
if source == 'image_id' and not \
filter(lambda x: x[0] != '', self.fields['image_id'].choices):
raise forms.ValidationError(_("There are no image sources "
"available; you must first create "
"an image before attempting to "
"launch an instance."))
if not cleaned_data[source]:
raise forms.ValidationError(_("Please select an option for the "
"instance source."))
volume_type = self.data.get('volume_type', None)
if volume_type: # Boot from volume
if cleaned_data[source]:
raise forms.ValidationError(_("You can't select an instance "
"source when booting from a "
"Volume. The Volume is your "
"source and should contain "
"the operating system."))
else: # Boot from image / image_snapshot
if source == 'image_id' and not \
filter(lambda x: x[0] != '', self.fields['image_id'].choices):
raise forms.ValidationError(_("There are no image sources "
"available; you must first "
"create an image before "
"attemtping to launch an "
"instance."))
elif not cleaned_data[source]:
raise forms.ValidationError(_("Please select an option for the"
" instance source."))

# Prevent launching multiple instances with the same volume.
# TODO(gabriel): is it safe to launch multiple instances with
# a snapshot since it should be cloned to new volumes?
count = cleaned_data.get('count', 1)
volume_type = self.data.get('volume_type', None)
if volume_type and count > 1:
msg = _('Launching multiple instances is only supported for '
'images and instance snapshots.')
Expand Down

0 comments on commit 8f69a8d

Please sign in to comment.