Add pool delete action

Adds the pool delete action to the pool details page.

Partially-Implements: blueprint horizon-lbaas-v2-ui
Change-Id: Ic3ffa9eca363ce73b20841775c3dce1612fafb07
This commit is contained in:
Justin Pomeroy 2016-03-02 13:24:33 -06:00
parent 48ae48f04f
commit a7f1b762ed
11 changed files with 455 additions and 5 deletions

View File

@ -576,6 +576,14 @@ class Pool(generic.View):
lb = neutronclient(request).show_lbaas_pool(pool_id)
return lb.get('pool')
@rest_utils.ajax()
def delete(self, request, pool_id):
"""Delete a specific pool.
http://localhost/api/lbaas/pools/cc758c90-3d98-4ea1-af44-aab405c9c915
"""
neutronclient(request).delete_lbaas_pool(pool_id)
@urls.register
class Members(generic.View):

View File

@ -47,6 +47,7 @@
editListener: editListener,
deleteListener: deleteListener,
getPool: getPool,
deletePool: deletePool,
getMembers: getMembers,
getMember: getMember,
getHealthMonitor: getHealthMonitor
@ -248,6 +249,22 @@
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.deletePool
* @description
* Delete a single pool by ID
* @param {string} id
* @param {boolean} quiet
* Specifies the id of the pool to delete.
*/
function deletePool(id, quiet) {
var promise = apiService.delete('/api/lbaas/pools/' + id);
return quiet ? promise : promise.error(function () {
toastService.add('error', gettext('Unable to delete pool.'));
});
}
// Members
/**

View File

@ -97,6 +97,13 @@
error: 'Unable to retrieve pool.',
testInput: [ '1234' ]
},
{
func: 'deletePool',
method: 'delete',
path: '/api/lbaas/pools/1234',
error: 'Unable to delete pool.',
testInput: [ '1234' ]
},
{
func: 'getMembers',
method: 'get',
@ -177,6 +184,11 @@
expect(service.deleteListener("whatever", true)).toBe("promise");
});
it('supresses the error if instructed for deletePool', function() {
spyOn(apiService, 'delete').and.returnValue("promise");
expect(service.deletePool("whatever", true)).toBe("promise");
});
});
})();

View File

@ -80,7 +80,7 @@
$q = $injector.get('$q');
toast = $injector.get('horizon.framework.widgets.toast.service');
service = $injector.get('horizon.dashboard.project.lbaasv2.listeners.actions.delete');
service.init('1');
service.init('1', makePromise());
}));
it('should have the "allowed" and "perform" functions', function() {
@ -96,6 +96,11 @@
expect(allowed()).toBe(true);
});
it('should not allow deleting listener from load balancer in a PENDING state', function() {
service.init('1', makePromise(true));
expect(allowed()).toBe(false);
});
it('should open the delete modal', function() {
spyOn(modal, 'open');
service.perform(items[0]);

View File

@ -0,0 +1,112 @@
/*
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2.pools')
.factory('horizon.dashboard.project.lbaasv2.pools.actions.delete', deleteService);
deleteService.$inject = [
'$q',
'$location',
'$route',
'horizon.framework.widgets.modal.deleteModalService',
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.pools.actions.deleteService
* @description
* Brings up the delete pool confirmation modal dialog.
* On submit, deletes selected pool.
* On cancel, does nothing.
* @param $q The angular service for promises.
* @param $location The angular $location service.
* @param $route The angular $route service.
* @param deleteModal The horizon delete modal service.
* @param api The LBaaS v2 API service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
* @returns The load balancers table delete service.
*/
function deleteService($q, $location, $route, deleteModal, api, policy, gettext) {
var loadbalancerId, listenerId, statePromise;
var context = {
labels: {
title: gettext('Confirm Delete Pool'),
message: gettext('You have selected "%s". Please confirm your selection. Deleted pools ' +
'are not recoverable.'),
submit: gettext('Delete Pool'),
success: gettext('Deleted pool: %s.'),
error: gettext('The following pool could not be deleted: %s.')
},
deleteEntity: deleteItem,
successEvent: 'success',
failedEvent: 'error'
};
var service = {
perform: perform,
allowed: allowed,
init: init
};
return service;
//////////////
function init(_loadbalancerId_, _listenerId_, _statePromise_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
statePromise = _statePromise_;
return service;
}
function perform(item) {
deleteModal.open({ $emit: actionComplete }, [item], context);
}
function allowed(/*item*/) {
return $q.all([
statePromise,
// This rule is made up and should therefore always pass. I assume at some point there
// will be a valid rule similar to this that we will want to use.
policy.ifAllowed({ rules: [['neutron', 'delete_pool']] })
]);
}
function deleteItem(id) {
return api.deletePool(id, true);
}
function actionComplete(eventType) {
if (eventType === context.failedEvent) {
// Error, reload page
$route.reload();
} else {
// Success, go back to listener details page
var path = 'project/ngloadbalancersv2/' + loadbalancerId + '/listeners/' + listenerId;
$location.path(path);
}
}
}
})();

View File

@ -0,0 +1,155 @@
/*
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
describe('LBaaS v2 Pool Delete Service', function() {
var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, pool, path;
function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(makePromise());
var promise = service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
$scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'delete_pool']]});
return allowed;
}
function makePromise(reject) {
var def = $q.defer();
def[reject ? 'reject' : 'resolve']();
return def.promise;
}
function isActionable(id) {
if (id === 'active') {
return $q.when();
} else {
return $q.reject();
}
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(function() {
pool = { id: '1', name: 'Pool1' };
});
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: makePromise()
};
}
});
$provide.value('horizon.app.core.openstack-service-api.lbaasv2', {
deletePool: function() {
return makePromise();
}
});
$provide.value('$location', {
path: function() {
return path;
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
modal = $injector.get('horizon.framework.widgets.modal.deleteModalService');
$scope = $injector.get('$rootScope').$new();
$location = $injector.get('$location');
$q = $injector.get('$q');
toast = $injector.get('horizon.framework.widgets.toast.service');
service = $injector.get('horizon.dashboard.project.lbaasv2.pools.actions.delete');
service.init('1', '2', isActionable('active'));
$scope.$apply();
}));
it('should have the "allowed" and "perform" functions', function() {
expect(service.allowed).toBeDefined();
expect(service.perform).toBeDefined();
});
it('should allow deleting pool from load balancer in ACTIVE state', function() {
expect(allowed()).toBe(true);
});
it('should not allow deleting pool from load balancer in a PENDING state', function() {
service.init('1', '2', isActionable('pending'));
expect(allowed()).toBe(false);
});
it('should open the delete modal', function() {
spyOn(modal, 'open');
service.perform(pool);
$scope.$apply();
expect(modal.open.calls.count()).toBe(1);
var args = modal.open.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toEqual({ $emit: jasmine.any(Function) });
expect(args[1]).toEqual([pool]);
expect(args[2]).toEqual(jasmine.objectContaining({
labels: jasmine.any(Object),
deleteEntity: jasmine.any(Function)
}));
expect(args[2].labels.title).toBe('Confirm Delete Pool');
});
it('should pass function to modal that deletes the pool', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deletePool').and.callThrough();
service.perform(pool);
$scope.$apply();
expect(lbaasv2Api.deletePool.calls.count()).toBe(1);
expect(lbaasv2Api.deletePool).toHaveBeenCalledWith('1', true);
});
it('should show message if any items fail to be deleted', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deletePool').and.returnValue(makePromise(true));
spyOn(toast, 'add');
service.perform(pool);
$scope.$apply();
expect(modal.open).toHaveBeenCalled();
expect(lbaasv2Api.deletePool.calls.count()).toBe(1);
expect(toast.add).toHaveBeenCalledWith('error', 'The following pool could not ' +
'be deleted: Pool1.');
});
it('should return to listener details after delete', function() {
path = 'project/ngloadbalancersv2/1';
spyOn($location, 'path');
spyOn(toast, 'add');
service.perform(pool);
$scope.$apply();
expect($location.path).toHaveBeenCalledWith('project/ngloadbalancersv2/1/listeners/2');
expect(toast.add).toHaveBeenCalledWith('success', 'Deleted pool: Pool1.');
});
});
})();

View File

@ -0,0 +1,73 @@
/*
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2.pools')
.factory('horizon.dashboard.project.lbaasv2.pools.actions.rowActions',
rowActions);
rowActions.$inject = [
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.pools.actions.delete'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.pools.actions.rowActions
*
* @description
* Provides the service for the pool row actions.
*
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param deleteService The LBaaS v2 pools delete service.
* @returns Pool row actions service object.
*/
function rowActions(gettext, loadBalancersService, deleteService) {
var loadBalancerIsActionable, loadbalancerId, listenerId;
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadbalancerId_, _listenerId_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId);
return service;
}
function actions() {
return [{
service: deleteService.init(loadbalancerId, listenerId, loadBalancerIsActionable),
template: {
text: gettext('Delete Pool'),
type: 'delete'
}
}];
}
}
})();

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
describe('LBaaS v2 Pools Row Actions Service', function() {
var actions;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function ($injector) {
var rowActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.pools.actions.rowActions');
actions = rowActionsService.init('1', '2').actions();
var loadbalancerService = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.service');
spyOn(loadbalancerService, 'isActionable').and.returnValue(true);
}));
it('should define correct table row actions', function() {
expect(actions.length).toBe(1);
expect(actions[0].template.text).toBe('Delete Pool');
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
});
})();

View File

@ -22,6 +22,7 @@
PoolDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.pools.actions.rowActions',
'$routeParams',
'horizon.framework.util.i18n.gettext'
];
@ -34,12 +35,13 @@
* Controller for the LBaaS v2 pool detail page.
*
* @param api The LBaaS v2 API service.
* @param rowActions The LBaaS v2 pool row actions service.
* @param $routeParams The angular $routeParams service.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function PoolDetailController(api, $routeParams, gettext) {
function PoolDetailController(api, rowActions, $routeParams, gettext) {
var ctrl = this;
ctrl.loadBalancerAlgorithm = {
@ -48,6 +50,8 @@
'SOURCE_IP': gettext('Source IP')
};
ctrl.actions = rowActions.init($routeParams.loadbalancerId, $routeParams.listenerId).actions;
init();
////////////////////////////////

View File

@ -27,10 +27,22 @@
};
}
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: loadbalancer });
}
};
}
///////////////////////
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
@ -39,7 +51,7 @@
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getPool').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
var controller = $injector.get('$controller');
ctrl = controller('PoolDetailController', {
$routeParams: {
@ -54,7 +66,7 @@
expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId');
expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId');
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId');
expect(ctrl.loadbalancer).toBe('foo');
expect(ctrl.loadbalancer).toEqual({provisioning_status: 'ACTIVE'});
expect(ctrl.listener).toBe('foo');
expect(ctrl.pool).toBe('foo');
});

View File

@ -5,6 +5,8 @@
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="active">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.pool"
ng-if="ctrl.pool" class="actions_column pull-right"></actions>
</ol>
<p ng-if="::ctrl.pool.description">{$ ::ctrl.pool.description $}</p>
</div>