Merge "Show snapshots list correctly when launching instance"

This commit is contained in:
Zuul 2018-11-06 23:03:35 +00:00 committed by Gerrit Code Review
commit 799d543206
7 changed files with 189 additions and 17 deletions
openstack_dashboard
dashboards/project
images
instances
static/dashboard/project/workflow/launch-instance
test/test_data

@ -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',