Show snapshots list correctly when launching instance
In launch instance modal, when a user selects 'Instance Snapshots', not all snapshots are listed. The snapshots which are created from an instance with new volume or an instance created from volume or volume snapshot don't have 'image_type' but 'block_device_mapping'. So, judging only by image_type is not enough. Change-Id: I7e175b6a7260ca3d82560427a8f742f8cfa35565 Closes-Bug: #1627619
This commit is contained in:
parent
fa3f856f02
commit
5f4057f8b5
|
@ -13,6 +13,7 @@
|
|||
# under the License.
|
||||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import defaultfilters as filters
|
||||
|
@ -245,7 +246,14 @@ def get_image_name(image):
|
|||
|
||||
|
||||
def get_image_type(image):
|
||||
return getattr(image, "properties", {}).get("image_type", "image")
|
||||
if not hasattr(image, 'properties'):
|
||||
return 'image'
|
||||
if image.properties.get('image_type'):
|
||||
return image.properties.get('image_type')
|
||||
if image.properties.get('block_device_mapping'):
|
||||
block_device_mapping = image.properties.get('block_device_mapping')
|
||||
return json.loads(block_device_mapping)[0].get('source_type')
|
||||
return 'image'
|
||||
|
||||
|
||||
def get_format(image):
|
||||
|
|
|
@ -111,7 +111,7 @@ class ImagesAndSnapshotsTests(BaseImagesTestCase):
|
|||
self.assertTemplateUsed(res, INDEX_TEMPLATE)
|
||||
self.assertIn('images_table', res.context)
|
||||
snaps = res.context['images_table']
|
||||
self.assertEqual(len(snaps.get_rows()), 3)
|
||||
self.assertEqual(len(snaps.get_rows()), 4)
|
||||
|
||||
row_actions = snaps.get_row_actions(snaps.data[0])
|
||||
|
||||
|
|
|
@ -80,8 +80,10 @@ class InstanceTestBase(helpers.ResetImageAPIVersionMixin,
|
|||
super(InstanceTestBase, self).setUp()
|
||||
if api.glance.VERSIONS.active < 2:
|
||||
self.versioned_images = self.images
|
||||
self.versioned_snapshots = self.snapshots
|
||||
else:
|
||||
self.versioned_images = self.imagesV2
|
||||
self.versioned_snapshots = self.snapshotsV2
|
||||
|
||||
|
||||
class InstanceTableTestMixin(object):
|
||||
|
@ -2263,6 +2265,111 @@ class InstanceLaunchInstanceTests(InstanceTestBase,
|
|||
def test_launch_instance_get_with_only_one_network(self):
|
||||
self.test_launch_instance_get(only_one_network=True)
|
||||
|
||||
@helpers.create_mocks({api.nova: ('extension_supported',
|
||||
'is_feature_available',
|
||||
'flavor_list',
|
||||
'keypair_list',
|
||||
'server_group_list',
|
||||
'availability_zone_list',),
|
||||
cinder: ('volume_snapshot_list',
|
||||
'volume_list',),
|
||||
api.neutron: ('network_list',
|
||||
'port_list_with_trunk_types',
|
||||
'security_group_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_launch_instance_get_images_snapshots(self,
|
||||
block_device_mapping_v2=True,
|
||||
only_one_network=False,
|
||||
disk_config=True,
|
||||
config_drive=True):
|
||||
self._mock_extension_supported({
|
||||
'BlockDeviceMappingV2Boot': block_device_mapping_v2,
|
||||
'DiskConfig': disk_config,
|
||||
'ConfigDrive': config_drive,
|
||||
'ServerGroups': True,
|
||||
})
|
||||
self.mock_volume_list.return_value = []
|
||||
self.mock_volume_snapshot_list.return_value = []
|
||||
self._mock_glance_image_list_detailed(self.versioned_images.list() +
|
||||
self.versioned_snapshots.list())
|
||||
self.mock_network_list.side_effect = [
|
||||
self.networks.list()[:1],
|
||||
[] if only_one_network else self.networks.list()[1:],
|
||||
self.networks.list()[:1],
|
||||
self.networks.list()[1:],
|
||||
]
|
||||
self.mock_port_list_with_trunk_types.return_value = self.ports.list()
|
||||
self.mock_server_group_list.return_value = self.server_groups.list()
|
||||
self.mock_tenant_quota_usages.return_value = self.limits['absolute']
|
||||
self._mock_nova_lists()
|
||||
|
||||
url = reverse('horizon:project:instances:launch')
|
||||
res = self.client.get(url)
|
||||
|
||||
image_sources = (res.context_data['workflow'].steps[0].
|
||||
action.fields['image_id'].choices)
|
||||
|
||||
snapshot_sources = (res.context_data['workflow'].steps[0].
|
||||
action.fields['instance_snapshot_id'].choices)
|
||||
|
||||
images = [image.id for image in self.versioned_images.list()]
|
||||
snapshots = [s.id for s in self.versioned_snapshots.list()]
|
||||
|
||||
image_sources_ids = []
|
||||
snapshot_sources_ids = []
|
||||
for image in image_sources:
|
||||
self.assertTrue(image[0] in images or image[0] == '')
|
||||
if image[0] != '':
|
||||
image_sources_ids.append(image[0])
|
||||
|
||||
for image in images:
|
||||
self.assertIn(image, image_sources_ids)
|
||||
|
||||
for snapshot in snapshot_sources:
|
||||
self.assertTrue(snapshot[0] in snapshots or snapshot[0] == '')
|
||||
if snapshot[0] != '':
|
||||
snapshot_sources_ids.append(snapshot[0])
|
||||
|
||||
for snapshot in snapshots:
|
||||
self.assertIn(snapshot, snapshot_sources_ids)
|
||||
|
||||
self._check_extension_supported({
|
||||
'BlockDeviceMappingV2Boot': 1,
|
||||
'DiskConfig': 1,
|
||||
'ConfigDrive': 1,
|
||||
'ServerGroups': 1,
|
||||
})
|
||||
self.mock_volume_list.assert_has_calls([
|
||||
mock.call(helpers.IsHttpRequest(),
|
||||
search_opts=VOLUME_SEARCH_OPTS),
|
||||
mock.call(helpers.IsHttpRequest(),
|
||||
search_opts=VOLUME_BOOTABLE_SEARCH_OPTS),
|
||||
])
|
||||
self.mock_volume_snapshot_list.assert_called_once_with(
|
||||
helpers.IsHttpRequest(),
|
||||
search_opts=SNAPSHOT_SEARCH_OPTS)
|
||||
self._check_glance_image_list_detailed(count=5)
|
||||
self.mock_network_list.assert_has_calls([
|
||||
mock.call(helpers.IsHttpRequest(),
|
||||
tenant_id=self.tenant.id, shared=False),
|
||||
mock.call(helpers.IsHttpRequest(), shared=True),
|
||||
mock.call(helpers.IsHttpRequest(),
|
||||
tenant_id=self.tenant.id, shared=False),
|
||||
mock.call(helpers.IsHttpRequest(), shared=True),
|
||||
])
|
||||
self.assertEqual(4, self.mock_network_list.call_count)
|
||||
self.mock_port_list_with_trunk_types.assert_has_calls(
|
||||
[mock.call(helpers.IsHttpRequest(),
|
||||
network_id=net.id, tenant_id=self.tenant.id)
|
||||
for net in self.networks.list()])
|
||||
self.mock_server_group_list.assert_called_once_with(
|
||||
helpers.IsHttpRequest())
|
||||
self.mock_tenant_quota_usages.assert_called_once_with(
|
||||
helpers.IsHttpRequest(),
|
||||
targets=('instances', 'cores', 'ram', 'volumes', 'gigabytes'))
|
||||
self._check_nova_lists(flavor_count=2)
|
||||
|
||||
@helpers.create_mocks({api.nova: ('extension_supported',
|
||||
'is_feature_available',
|
||||
'flavor_list',
|
||||
|
|
|
@ -41,6 +41,8 @@ from openstack_dashboard.api import cinder
|
|||
from openstack_dashboard.api import nova
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from openstack_dashboard.dashboards.project.images.images \
|
||||
import tables as image_tables
|
||||
from openstack_dashboard.dashboards.project.images \
|
||||
import utils as image_utils
|
||||
from openstack_dashboard.dashboards.project.instances \
|
||||
|
@ -439,7 +441,7 @@ class SetInstanceDetailsAction(workflows.Action):
|
|||
context.get('project_id'),
|
||||
self._images_cache)
|
||||
for image in images:
|
||||
if image.properties.get("image_type", '') != "snapshot":
|
||||
if image_tables.get_image_type(image) != "snapshot":
|
||||
image.bytes = getattr(
|
||||
image, 'virtual_size', None) or image.size
|
||||
image.volume_size = max(
|
||||
|
@ -461,7 +463,7 @@ class SetInstanceDetailsAction(workflows.Action):
|
|||
self._images_cache)
|
||||
choices = [(image.id, image.name)
|
||||
for image in images
|
||||
if image.properties.get("image_type", '') == "snapshot"]
|
||||
if image_tables.get_image_type(image) == "snapshot"]
|
||||
if choices:
|
||||
choices.sort(key=operator.itemgetter(1))
|
||||
choices.insert(0, ("", _("Select Instance Snapshot")))
|
||||
|
|
|
@ -653,11 +653,21 @@
|
|||
return bootSourceTypes.NON_BOOTABLE_IMAGE_TYPES.indexOf(image.container_format) < 0;
|
||||
}
|
||||
|
||||
function getImageType(image) {
|
||||
if (image === null || !angular.isDefined(image.properties) ||
|
||||
!(angular.isDefined(image.properties.image_type) ||
|
||||
angular.isDefined(image.properties.block_device_mapping))) {
|
||||
return 'image';
|
||||
}
|
||||
return image.properties.image_type ||
|
||||
angular.fromJson(image.properties.block_device_mapping)[0].source_type ||
|
||||
'image';
|
||||
}
|
||||
|
||||
function onGetImages(data) {
|
||||
model.images.length = 0;
|
||||
push.apply(model.images, data.data.items.filter(function (image) {
|
||||
return isBootableImageType(image) &&
|
||||
(!image.properties || image.properties.image_type !== 'snapshot');
|
||||
return isBootableImageType(image) && getImageType(image) !== 'snapshot';
|
||||
}));
|
||||
addAllowedBootSource(model.images, bootSourceTypes.IMAGE, gettext('Image'));
|
||||
}
|
||||
|
@ -665,8 +675,7 @@
|
|||
function onGetSnapshots(data) {
|
||||
model.imageSnapshots.length = 0;
|
||||
push.apply(model.imageSnapshots, data.data.items.filter(function (image) {
|
||||
return isBootableImageType(image) &&
|
||||
(image.properties && image.properties.image_type === 'snapshot');
|
||||
return isBootableImageType(image) && getImageType(image) === 'snapshot';
|
||||
}));
|
||||
|
||||
addAllowedBootSource(
|
||||
|
|
|
@ -137,8 +137,12 @@
|
|||
{container_format: 'ari', properties: {}},
|
||||
{container_format: 'ami', properties: {}},
|
||||
{container_format: 'raw', properties: {}},
|
||||
{container_format: 'ami', properties: {image_type: 'snapshot'}},
|
||||
{container_format: 'raw', properties: {image_type: 'snapshot'}}
|
||||
{container_format: 'ami', properties: {image_type: 'image'}},
|
||||
{container_format: 'raw', properties: {image_type: 'image'}},
|
||||
{container_format: 'ami', properties: {
|
||||
block_device_mapping: '[{"source_type": "snapshot"}]'}},
|
||||
{container_format: 'raw', properties: {
|
||||
block_device_mapping: '[{"source_type": "snapshot"}]'}}
|
||||
];
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
@ -183,12 +187,16 @@
|
|||
$provide.value('horizon.app.core.openstack-service-api.glance', {
|
||||
getImages: function() {
|
||||
var images = [
|
||||
{ container_format: 'aki', properties: {} },
|
||||
{ container_format: 'ari', properties: {} },
|
||||
{ container_format: 'ami', properties: {} },
|
||||
{ container_format: 'raw', properties: {} },
|
||||
{ container_format: 'ami', properties: { image_type: 'snapshot' } },
|
||||
{ container_format: 'raw', properties: { image_type: 'snapshot' } }
|
||||
{container_format: 'aki', properties: {} },
|
||||
{container_format: 'ari', properties: {} },
|
||||
{container_format: 'ami', properties: {} },
|
||||
{container_format: 'raw', properties: {} },
|
||||
{container_format: 'ami', properties: {image_type: 'image'}},
|
||||
{container_format: 'raw', properties: {image_type: 'image'}},
|
||||
{container_format: 'ami', properties: {
|
||||
block_device_mapping: '[{"source_type": "snapshot"}]'}},
|
||||
{container_format: 'raw', properties: {
|
||||
block_device_mapping: '[{"source_type": "snapshot"}]'}}
|
||||
];
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
@ -422,7 +430,7 @@
|
|||
expect(model.initialized).toBe(true);
|
||||
expect(model.newInstanceSpec).toBeDefined();
|
||||
|
||||
expect(model.images.length).toBe(2);
|
||||
expect(model.images.length).toBe(4);
|
||||
expect(model.imageSnapshots.length).toBe(2);
|
||||
expect(model.availabilityZones.length).toBe(3); // 2 + 1 for 'nova pick'
|
||||
expect(model.flavors.length).toBe(2);
|
||||
|
|
|
@ -52,6 +52,7 @@ def data(TEST):
|
|||
TEST.snapshots = utils.TestDataContainer()
|
||||
TEST.metadata_defs = utils.TestDataContainer()
|
||||
TEST.imagesV2 = utils.TestDataContainer()
|
||||
TEST.snapshotsV2 = utils.TestDataContainer()
|
||||
|
||||
# Snapshots
|
||||
snapshot_dict = {'name': u'snapshot',
|
||||
|
@ -78,12 +79,26 @@ def data(TEST):
|
|||
'properties': {'image_type': u'snapshot'},
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
snapshot_dict_with_volume = {'name': u'snapshot 2',
|
||||
'container_format': u'ami',
|
||||
'id': 6,
|
||||
'status': "queued",
|
||||
'owner': TEST.tenant.id,
|
||||
'properties': {
|
||||
'block_device_mapping':
|
||||
'[{"source_type": "snapshot"}]'},
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
|
||||
snapshot = images.Image(images.ImageManager(None), snapshot_dict)
|
||||
TEST.snapshots.add(api.glance.Image(snapshot))
|
||||
snapshot = images.Image(images.ImageManager(None), snapshot_dict_no_owner)
|
||||
TEST.snapshots.add(api.glance.Image(snapshot))
|
||||
snapshot = images.Image(images.ImageManager(None), snapshot_dict_queued)
|
||||
TEST.snapshots.add(api.glance.Image(snapshot))
|
||||
snapshot = images.Image(images.ImageManager(None),
|
||||
snapshot_dict_with_volume)
|
||||
TEST.snapshots.add(api.glance.Image(snapshot))
|
||||
|
||||
# Images
|
||||
image_dict = {'id': '007e7d55-fe1e-4c5c-bf08-44b4a4964822',
|
||||
|
@ -318,6 +333,29 @@ def data(TEST):
|
|||
apiresource = APIResourceV2(fixture)
|
||||
TEST.imagesV2.add(api.glance.Image(apiresource))
|
||||
|
||||
snapshot_v2_dict = {
|
||||
'checksum': None,
|
||||
'container_format': 'novaImage',
|
||||
'created_at': '2018-02-26T22:50:56Z',
|
||||
'disk_format': None,
|
||||
'block_device_mapping': '[{"source_type": "snapshot"}]',
|
||||
'file': '/v2/images/c701226a-aa32-4064-bd36-e85a3dcc61aa/file',
|
||||
'id': 'c701226a-aa32-4064-bd36-e85a3dcc61aa',
|
||||
'locations': [],
|
||||
'min_disk': 30,
|
||||
'min_ram': 0,
|
||||
'name': 'snpashot_with_volume',
|
||||
'owner': TEST.tenant.id,
|
||||
'protected': True,
|
||||
'size': 2 * 1024 ** 3,
|
||||
'status': "active",
|
||||
'tags': ['empty_image'],
|
||||
'updated_at': '2018-02-26T22:50:56Z',
|
||||
'virtual_size': None,
|
||||
'visibility': 'public'
|
||||
}
|
||||
TEST.snapshotsV2.add(api.glance.Image(APIResourceV2(snapshot_v2_dict)))
|
||||
|
||||
metadef_dict = {
|
||||
'namespace': 'namespace_1',
|
||||
'display_name': 'Namespace 1',
|
||||
|
|
Loading…
Reference in New Issue