Merge "Show snapshots list correctly when launching instance"
This commit is contained in:
commit
799d543206
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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user