Add health monitor delete action

Adds the delete action to the health monitor details page.

Partially-Implements: blueprint horizon-lbaas-v2-ui
Change-Id: Id97db3daa284a6628bd67e8019ac9782f877290a
This commit is contained in:
Justin Pomeroy 2016-03-02 17:03:34 -06:00
parent 7616e4073d
commit 05f1bf8fd6
11 changed files with 462 additions and 9 deletions

View File

@ -685,3 +685,11 @@ class HealthMonitor(generic.View):
"""
lb = neutronclient(request).show_lbaas_healthmonitor(healthmonitor_id)
return lb.get('healthmonitor')
@rest_utils.ajax()
def delete(self, request, healthmonitor_id):
"""Delete a specific health monitor.
http://localhost/api/lbaas/healthmonitors/cc758c90-3d98-4ea1-af44-aab405c9c915
"""
neutronclient(request).delete_lbaas_healthmonitor(healthmonitor_id)

View File

@ -52,7 +52,8 @@
deletePool: deletePool,
getMembers: getMembers,
getMember: getMember,
getHealthMonitor: getHealthMonitor
getHealthMonitor: getHealthMonitor,
deleteHealthMonitor: deleteHealthMonitor
};
return service;
@ -356,5 +357,21 @@
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.deleteHealthMonitor
* @description
* Delete a single health monitor by ID
* @param {string} id
* @param {boolean} quiet
* Specifies the id of the health monitor to delete.
*/
function deleteHealthMonitor(id, quiet) {
var promise = apiService.delete('/api/lbaas/healthmonitors/' + id);
return quiet ? promise : promise.error(function () {
toastService.add('error', gettext('Unable to delete health monitor.'));
});
}
}
}());

View File

@ -134,6 +134,13 @@
error: 'Unable to retrieve health monitor.',
testInput: [ '1234' ]
},
{
func: 'deleteHealthMonitor',
method: 'delete',
path: '/api/lbaas/healthmonitors/1234',
error: 'Unable to delete health monitor.',
testInput: [ '1234' ]
},
{
func: 'createLoadBalancer',
method: 'post',
@ -214,6 +221,11 @@
expect(service.deletePool("whatever", true)).toBe("promise");
});
it('supresses the error if instructed for deleteHealthMonitor', function() {
spyOn(apiService, 'delete').and.returnValue("promise");
expect(service.deleteHealthMonitor("whatever", true)).toBe("promise");
});
});
})();

View File

@ -0,0 +1,117 @@
/*
* 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.healthmonitors')
.factory('horizon.dashboard.project.lbaasv2.healthmonitors.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.healthmonitors.actions.deleteService
* @description
* Brings up the delete health monitor confirmation modal dialog.
* On submit, deletes selected health monitor.
* 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 health monitor delete service.
*/
function deleteService(
$q, $location, $route, deleteModal, api, policy, gettext
) {
var loadbalancerId, listenerId, poolId, statePromise;
var context = {
labels: {
title: gettext('Confirm Delete Health Monitor'),
message: gettext('You have selected "%s". Please confirm your selection. Deleted health ' +
'monitors are not recoverable.'),
submit: gettext('Delete Health Monitor'),
success: gettext('Deleted health monitor: %s.'),
error: gettext('The following health monitor 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_, _poolId_, _statePromise_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
poolId = _poolId_;
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_health_monitor']] })
]);
}
function deleteItem(id) {
return api.deleteHealthMonitor(id, true);
}
function actionComplete(eventType) {
if (eventType === context.failedEvent) {
// Error, reload page
$route.reload();
} else {
// Success, go back to pool details page
var path = 'project/ngloadbalancersv2/' + loadbalancerId +
'/listeners/' + listenerId +
'/pools/' + poolId;
$location.path(path);
}
}
}
})();

View File

@ -0,0 +1,156 @@
/*
* 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 Health Monitor Delete Service', function() {
var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, monitor;
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_health_monitor']]});
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() {
monitor = { id: '1', name: 'HealthMonitor1' };
});
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: makePromise()
};
}
});
$provide.value('horizon.app.core.openstack-service-api.lbaasv2', {
deleteHealthMonitor: function() {
return makePromise();
}
});
$provide.value('$location', {
path: function() {
return '';
}
});
}));
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.healthmonitors.actions.delete');
service.init('1', '2', '3', 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 health monitor from load balancer in ACTIVE state', function() {
expect(allowed()).toBe(true);
});
it('should not allow deleting health monitor from load balancer in PENDING state', function() {
service.init('1', '2', '3', isActionable('pending'));
expect(allowed()).toBe(false);
});
it('should open the delete modal', function() {
spyOn(modal, 'open');
service.perform(monitor);
$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([monitor]);
expect(args[2]).toEqual(jasmine.objectContaining({
labels: jasmine.any(Object),
deleteEntity: jasmine.any(Function)
}));
expect(args[2].labels.title).toBe('Confirm Delete Health Monitor');
});
it('should pass function to modal that deletes the health monitor', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteHealthMonitor').and.callThrough();
service.perform(monitor);
$scope.$apply();
expect(lbaasv2Api.deleteHealthMonitor.calls.count()).toBe(1);
expect(lbaasv2Api.deleteHealthMonitor).toHaveBeenCalledWith('1', true);
});
it('should show message if any items fail to be deleted', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteHealthMonitor').and.returnValue(makePromise(true));
spyOn(toast, 'add');
service.perform(monitor);
$scope.$apply();
expect(modal.open).toHaveBeenCalled();
expect(lbaasv2Api.deleteHealthMonitor.calls.count()).toBe(1);
expect(toast.add).toHaveBeenCalledWith('error', 'The following health monitor could not ' +
'be deleted: HealthMonitor1.');
});
it('should return to pool details after delete', function() {
var path = 'project/ngloadbalancersv2/1/listeners/2/pools/3';
spyOn($location, 'path');
spyOn(toast, 'add');
service.perform(monitor);
$scope.$apply();
expect($location.path).toHaveBeenCalledWith(path);
expect(toast.add).toHaveBeenCalledWith('success', 'Deleted health monitor: HealthMonitor1.');
});
});
})();

View File

@ -0,0 +1,74 @@
/*
* 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.healthmonitors')
.factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions',
rowActions);
rowActions.$inject = [
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.delete'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions
*
* @description
* Provides the service for the health monitor row actions.
*
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param deleteService The LBaaS v2 health monitor delete service.
* @returns Health monitor row actions service object.
*/
function rowActions(gettext, loadBalancersService, deleteService) {
var loadBalancerIsActionable, loadbalancerId, listenerId, poolId;
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadbalancerId_, _listenerId_, _poolId_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
poolId = _poolId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId);
return service;
}
function actions() {
return [{
service: deleteService.init(loadbalancerId, listenerId, poolId, loadBalancerIsActionable),
template: {
text: gettext('Delete Health Monitor'),
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 Health Monitor 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.healthmonitors.actions.rowActions');
actions = rowActionsService.init('1', '2', '3').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 Health Monitor');
});
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 @@
HealthMonitorDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions',
'$routeParams'
];
@ -33,13 +34,18 @@
* Controller for the LBaaS v2 health monitor detail page.
*
* @param api The LBaaS v2 API service.
* @param rowActions The LBaaS v2 health monitor row actions service.
* @param $routeParams The angular $routeParams service.
* @returns undefined
*/
function HealthMonitorDetailController(api, $routeParams) {
function HealthMonitorDetailController(api, rowActions, $routeParams) {
var ctrl = this;
ctrl.actions = rowActions.init($routeParams.loadbalancerId,
$routeParams.listenerId,
$routeParams.poolId).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.http'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
@ -40,7 +52,7 @@
spyOn(lbaasv2API, 'getHealthMonitor').and.callFake(fakeAPI);
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('HealthMonitorDetailController', {
$routeParams: {
@ -57,7 +69,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');
expect(ctrl.healthmonitor).toBe('foo');

View File

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

View File

@ -17,7 +17,7 @@
'use strict';
describe('LBaaS v2 Pool Delete Service', function() {
var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, pool, path;
var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, pool;
function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(makePromise());
@ -72,7 +72,7 @@
});
$provide.value('$location', {
path: function() {
return path;
return '';
}
});
}));
@ -142,7 +142,6 @@
});
it('should return to listener details after delete', function() {
path = 'project/ngloadbalancersv2/1';
spyOn($location, 'path');
spyOn(toast, 'add');
service.perform(pool);