Invalid instance_snapshot causes KeyError

When creating a nova instance, if the instance_snapshot_id passed in is
invalid, a KeyError exception results.  This likely happens when another
window or perhaps a command line tool deletes the snapshot before the
page is posted back.  Memoize decorator fix fixes an error which masks
the error listed above, enabling better debugging.

Closes-Bug: 1386826
Change-Id: I195b4df84da3bd360b155db9ae733c34575ebe7e
This commit is contained in:
Mike Hagedorn 2014-10-28 15:28:30 -04:00
parent e659dc37ed
commit 59067e8e26
3 changed files with 96 additions and 2 deletions

View File

@ -94,7 +94,7 @@ def memoized(func):
# we can't cache anything and simply always call the decorated
# function.
warnings.warn(
"The key %r is not hashable and cannot be memoized." % key,
"The key %r is not hashable and cannot be memoized." % (key,),
UnhashableKeyWarning, 2)
value = func(*args, **kwargs)
return value

View File

@ -2021,6 +2021,98 @@ class InstanceTests(helpers.TestCase):
self.test_launch_instance_post_no_images_available(
test_with_profile=True)
@helpers.create_stubs({
api.glance: ('image_list_detailed',),
api.neutron: ('network_list',
'profile_list',
'port_create',),
api.nova: ('extension_supported',
'flavor_list',
'keypair_list',
'availability_zone_list',
'server_create',),
api.network: ('security_group_list',),
cinder: ('volume_list',
'volume_snapshot_list',),
quotas: ('tenant_quota_usages',)})
def test_launch_instance_post_boot_from_snapshot(
self,
test_with_profile=False,
):
flavor = self.flavors.first()
keypair = self.keypairs.first()
server = self.servers.first()
avail_zone = self.availability_zones.first()
quota_usages = self.quota_usages.first()
api.nova.extension_supported('BlockDeviceMappingV2Boot',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
'status': 'active'}) \
.AndReturn([[], False, False])
api.glance.image_list_detailed(
IsA(http.HttpRequest),
filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \
.AndReturn([[], False, False])
api.neutron.network_list(IsA(http.HttpRequest),
tenant_id=self.tenant.id,
shared=False) \
.AndReturn(self.networks.list()[:1])
api.neutron.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.network.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
api.nova.availability_zone_list(IsA(http.HttpRequest)) \
.AndReturn(self.availability_zones.list())
api.nova.extension_supported('DiskConfig',
IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.extension_supported('ConfigDrive',
IsA(http.HttpRequest)).AndReturn(True)
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn([])
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(quota_usages)
self.mox.ReplayAll()
bad_snapshot_id = 'a-bogus-id'
form_data = {'flavor': flavor.id,
'source_type': 'instance_snapshot_id',
'instance_snapshot_id': bad_snapshot_id,
'keypair': keypair.name,
'name': server.name,
'script_source': 'raw',
'availability_zone': avail_zone.zoneName,
'network': self.networks.first().id,
'volume_id': '',
'volume_snapshot_id': '',
'image_id': '',
'device_name': 'vda',
'count': 1,
'profile': '',
'customization_script': ''}
url = reverse('horizon:project:instances:launch')
res = self.client.post(url, form_data)
self.assertFormErrors(res, 1, "You must select a snapshot.")
@helpers.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',
'profile_list',),

View File

@ -290,7 +290,9 @@ class SetInstanceDetailsAction(workflows.Action):
[msg])
elif source_type == 'instance_snapshot_id':
if not cleaned_data['instance_snapshot_id']:
# using the array form of get blows up with KeyError
# if instance_snapshot_id is nil
if not cleaned_data.get('instance_snapshot_id'):
msg = _("You must select a snapshot.")
self._errors['instance_snapshot_id'] = self.error_class([msg])