diff --git a/horizon/static/horizon/js/horizon.tabs.js b/horizon/static/horizon/js/horizon.tabs.js index 271e639373..f3a6247c12 100644 --- a/horizon/static/horizon/js/horizon.tabs.js +++ b/horizon/static/horizon/js/horizon.tabs.js @@ -10,6 +10,7 @@ horizon.tabs.initTabLoad = function (tab) { $(horizon.tabs._init_load_functions).each(function (index, f) { f(tab); }); + recompileAngularContent(); }; horizon.tabs.load_tab = function () { diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.js index 0976520d56..75387a8780 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.js @@ -362,6 +362,21 @@ } ); + var snapshotWatcher = $scope.$watchCollection( + function getSnapshots() { + return $scope.model.volumeSnapshots; + }, + function onSnapshotsChange() { + $scope.initPromise.then(function onInit() { + $scope.$applyAsync(function setDefaultSnapshot() { + if ($scope.launchContext.snapshotId) { + setSourceSnapshotWithId($scope.launchContext.snapshotId); + } + }); + }); + } + ); + // Explicitly remove watchers on desruction of this controller $scope.$on('$destroy', function() { newSpecWatcher(); @@ -369,6 +384,7 @@ bootSourceWatcher(); imagesWatcher(); volumeWatcher(); + snapshotWatcher(); }); // Initialize @@ -505,5 +521,14 @@ ctrl.currentBootSource = ctrl.bootSourcesOptions[2].type; } } + + function setSourceSnapshotWithId(id) { + var pre = findSourceById($scope.model.volumeSnapshots, id); + if (pre) { + changeBootSource(bootSourceTypes.VOLUME_SNAPSHOT, [pre]); + $scope.model.newInstanceSpec.source_type = ctrl.bootSourcesOptions[3]; + ctrl.currentBootSource = ctrl.bootSourcesOptions[3].type; + } + } } })(); diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js index 055928d48c..782af931be 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js @@ -51,7 +51,7 @@ images: [ { id: 'image-1' }, { id: 'image-2' } ], imageSnapshots: [], volumes: [ { id: 'volume-1' }, { id: 'volume-2' } ], - volumeSnapshots: [], + volumeSnapshots: [ {id: 'snapshot-2'} ], novaLimits: { maxTotalInstances: 10, totalInstancesUsed: 0 @@ -200,15 +200,26 @@ expect(ctrl.currentBootSource).toBe('volume'); }); + it('defaults source to snapshot-2 if launchContext.snapshotId = snapshot-2', function() { + scope.launchContext = { snapshotId: 'snapshot-2' }; + deferred.resolve(); + + $browser.defer.flush(); + + expect(ctrl.tableData.allocated[0]).toEqual({ id: 'snapshot-2' }); + expect(scope.model.newInstanceSpec.source_type.type).toBe('volume_snapshot'); + expect(ctrl.currentBootSource).toBe('volume_snapshot'); + }); + describe('Scope Functions', function() { describe('watchers', function () { it('establishes five watches', function() { - expect(scope.$watch.calls.count()).toBe(5); + expect(scope.$watch.calls.count()).toBe(6); }); it("establishes two watch collections", function () { - expect(scope.$watchCollection.calls.count()).toBe(2); + expect(scope.$watchCollection.calls.count()).toBe(3); }); }); diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py b/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py index 1f865483f3..7f2dbf2df4 100644 --- a/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py +++ b/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from django.conf import settings from django.core.urlresolvers import reverse from django.utils import html from django.utils.http import urlencode @@ -48,6 +49,29 @@ class LaunchSnapshot(volume_tables.LaunchVolume): return False +class LaunchSnapshotNG(LaunchSnapshot): + name = "launch_snapshot_ng" + verbose_name = _("Launch as Instance") + url = "horizon:project:volumes:snapshots_tab" + classes = ("btn-launch", ) + ajax = False + + def __init__(self, attrs=None, **kwargs): + kwargs['preempt'] = True + super(LaunchSnapshot, self).__init__(attrs, **kwargs) + + def get_link_url(self, datum): + url = reverse(self.url) + vol_id = self.table.get_object_id(datum) + ngclick = "modal.openLaunchInstanceWizard(" \ + "{successUrl: '%s', snapshotId: '%s'})" % (url, vol_id) + self.attrs.update({ + "ng-controller": "LaunchInstanceModalController as modal", + "ng-click": ngclick + }) + return "javascript:void(0);" + + class DeleteVolumeSnapshot(policy.PolicyTargetMixin, tables.DeleteAction): @staticmethod def action_present(count): @@ -156,8 +180,15 @@ class VolumeSnapshotsTable(volume_tables.VolumesTableBase): pagination_param = 'snapshot_marker' prev_pagination_param = 'prev_snapshot_marker' table_actions = (VolumeSnapshotsFilterAction, DeleteVolumeSnapshot,) - row_actions = (CreateVolumeFromSnapshot, LaunchSnapshot, - EditVolumeSnapshot, DeleteVolumeSnapshot) + + launch_actions = () + if getattr(settings, 'LAUNCH_INSTANCE_LEGACY_ENABLED', False): + launch_actions = (LaunchSnapshot,) + launch_actions + if getattr(settings, 'LAUNCH_INSTANCE_NG_ENABLED', True): + launch_actions = (LaunchSnapshotNG,) + launch_actions + + row_actions = ((CreateVolumeFromSnapshot,) + launch_actions + + (EditVolumeSnapshot, DeleteVolumeSnapshot)) row_class = UpdateRow status_columns = ("status",) permissions = [( diff --git a/openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumesnapshotspage.py b/openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumesnapshotspage.py index 1781b12a48..0cbab262e2 100644 --- a/openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumesnapshotspage.py +++ b/openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumesnapshotspage.py @@ -30,7 +30,14 @@ class VolumesnapshotsTable(tables.TableRegion): "name", "description", "snapshot_source", "type", "size") @tables.bind_table_action('delete') - def delete_volume_snapshot(self, delete_button): + def delete_volume_snapshots(self, delete_button): + """Batch Delete table action.""" + delete_button.click() + return forms.BaseFormRegion(self.driver, self.conf) + + @tables.bind_row_action('delete') + def delete_volume_snapshot(self, delete_button, row): + """Per-entity delete row action.""" delete_button.click() return forms.BaseFormRegion(self.driver, self.conf) @@ -77,8 +84,14 @@ class VolumesnapshotsPage(basepage.BaseNavigationPage): def delete_volume_snapshot(self, name): row = self._get_row_with_volume_snapshot_name(name) - row.mark() - confirm_form = self.volumesnapshots_table.delete_volume_snapshot() + confirm_form = self.volumesnapshots_table.delete_volume_snapshot(row) + confirm_form.submit() + + def delete_volume_snapshots(self, names): + for name in names: + row = self._get_row_with_volume_snapshot_name(name) + row.mark() + confirm_form = self.volumesnapshots_table.delete_volume_snapshots() confirm_form.submit() def is_volume_snapshot_deleted(self, name): diff --git a/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py b/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py index 41c8f98c30..9b1eab6ebe 100644 --- a/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py +++ b/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py @@ -104,13 +104,14 @@ class TestVolumeSnapshotsBasic(helpers.TestCase): items_per_page = 1 snapshot_names = ["{0}_{1}".format(self.VOLUME_SNAPSHOT_NAME, i) for i in range(count)] - for name in snapshot_names: + for i, name in enumerate(snapshot_names): volumes_snapshot_page = volumes_page.create_volume_snapshot( self.VOLUME_NAME, name) volumes_page.find_message_and_dismiss(messages.INFO) self.assertTrue( volumes_snapshot_page.is_volume_snapshot_available(name)) - volumes_snapshot_page.switch_to_volumes_tab() + if i < count - 1: + volumes_snapshot_page.switch_to_volumes_tab() first_page_definition = {'Next': True, 'Prev': False, 'Count': items_per_page, @@ -151,9 +152,10 @@ class TestVolumeSnapshotsBasic(helpers.TestCase): settings_page.find_message_and_dismiss(messages.SUCCESS) volumes_snapshot_page = self.volumes_snapshot_page + volumes_snapshot_page.delete_volume_snapshots(snapshot_names) + volumes_snapshot_page.find_message_and_dismiss(messages.SUCCESS) for name in snapshot_names: - volumes_snapshot_page.delete_volume_snapshot(name) - volumes_snapshot_page.find_message_and_dismiss(messages.SUCCESS) + volumes_snapshot_page.is_volume_snapshot_deleted(name) def tearDown(self): """Clean up: delete volume"""