Commit 06ed719b authored by Lucas Palm's avatar Lucas Palm

Add the Angular LBaaS V2 'Update Member List' Table Action

This change adds the Update Member list table action to the pool members
table.  This wizard allows you to Add or Remove members from the current list
of members associated with a particular pool.

Partially-Implements: blueprint horizon-lbaas-v2-ui
Change-Id: I640fdf2597e4a16191dbce8c20450c7c0dc882f0
parent 1240c01a
......@@ -171,16 +171,15 @@ def add_member(request, **kwargs):
"""
data = request.DATA
members = data.get('members')
pool_id = kwargs.get('pool_id')
if kwargs.get('members_to_add'):
members_to_add = kwargs['members_to_add']
index = [members.index(member) for member in members
if member['id'] == members_to_add[0]][0]
pool_id = data['pool'].get('id')
loadbalancer_id = data.get('loadbalancer_id')
else:
index = kwargs.get('index')
pool_id = kwargs.get('pool_id')
loadbalancer_id = kwargs.get('loadbalancer_id')
member = members[index]
......@@ -203,7 +202,8 @@ def add_member(request, **kwargs):
kwargs = {'callback_kwargs': {
'existing_members': kwargs.get('existing_members'),
'members_to_add': members_to_add,
'members_to_delete': kwargs.get('members_to_delete')}}
'members_to_delete': kwargs.get('members_to_delete'),
'pool_id': pool_id}}
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
elif len(members) > index:
args = (request, loadbalancer_id, add_member)
......@@ -224,7 +224,7 @@ def remove_member(request, **kwargs):
"""
data = request.DATA
loadbalancer_id = data.get('loadbalancer_id')
pool_id = data['pool']['id']
pool_id = kwargs.get('pool_id')
if kwargs.get('members_to_delete'):
members_to_delete = kwargs['members_to_delete']
......@@ -299,21 +299,18 @@ def update_pool(request, **kwargs):
# Assemble the lists of member id's to add and remove, if any exist
tenant_id = request.user.project_id
new_members = data.get('members', [])
request_member_data = data.get('members', [])
existing_members = neutronclient(request).list_lbaas_members(
pool_id, tenant_id=tenant_id).get('members')
new_member_ids = [member['id'] for member in new_members]
existing_member_ids = [member['id'] for member in existing_members]
members_to_add = [member_id for member_id in new_member_ids
if member_id not in existing_member_ids]
members_to_delete = [member_id for member_id in existing_member_ids
if member_id not in new_member_ids]
(members_to_add, members_to_delete) = get_members_to_add_remove(
request_member_data, existing_members)
if members_to_add or members_to_delete:
args = (request, loadbalancer_id, update_member_list)
kwargs = {'callback_kwargs': {'existing_members': existing_members,
'members_to_add': members_to_add,
'members_to_delete': members_to_delete}}
'members_to_delete': members_to_delete,
'pool_id': pool_id}}
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
elif data.get('monitor'):
args = (request, loadbalancer_id, update_monitor)
......@@ -355,25 +352,38 @@ def update_member_list(request, **kwargs):
"""
data = request.DATA
loadbalancer_id = data.get('loadbalancer_id')
pool_id = kwargs.get('pool_id')
existing_members = kwargs.get('existing_members')
members_to_add = kwargs.get('members_to_add')
members_to_delete = kwargs.get('members_to_delete')
if members_to_add:
if members_to_delete:
kwargs = {'existing_members': existing_members,
'members_to_add': members_to_add,
'members_to_delete': members_to_delete}
add_member(request, **kwargs)
elif members_to_delete:
'members_to_delete': members_to_delete,
'pool_id': pool_id}
remove_member(request, **kwargs)
elif members_to_add:
kwargs = {'existing_members': existing_members,
'members_to_add': members_to_add,
'members_to_delete': members_to_delete}
remove_member(request, **kwargs)
'members_to_delete': members_to_delete,
'pool_id': pool_id}
add_member(request, **kwargs)
elif data.get('monitor'):
args = (request, loadbalancer_id, update_monitor)
thread.start_new_thread(poll_loadbalancer_status, args)
def get_members_to_add_remove(request_member_data, existing_members):
new_member_ids = [member['id'] for member in request_member_data]
existing_member_ids = [member['id'] for member in existing_members]
members_to_add = [member_id for member_id in new_member_ids
if member_id not in existing_member_ids]
members_to_delete = [member_id for member_id in existing_member_ids
if member_id not in new_member_ids]
return members_to_add, members_to_delete
def add_floating_ip_info(request, loadbalancers):
"""Add floating IP address info to each load balancer.
......@@ -653,6 +663,26 @@ class Members(generic.View):
tenant_id=tenant_id)
return {'items': result.get('members')}
@rest_utils.ajax()
def put(self, request, pool_id):
"""Update the list of members for the current project.
"""
# Assemble the lists of member id's to add and remove, if any exist
tenant_id = request.user.project_id
request_member_data = request.DATA.get('members', [])
existing_members = neutronclient(request).list_lbaas_members(
pool_id, tenant_id=tenant_id).get('members')
(members_to_add, members_to_delete) = get_members_to_add_remove(
request_member_data, existing_members)
if members_to_add or members_to_delete:
kwargs = {'existing_members': existing_members,
'members_to_add': members_to_add,
'members_to_delete': members_to_delete,
'pool_id': pool_id}
update_member_list(request, **kwargs)
@urls.register
class Member(generic.View):
......
......@@ -56,7 +56,8 @@
getHealthMonitor: getHealthMonitor,
deleteHealthMonitor: deleteHealthMonitor,
createHealthMonitor: createHealthMonitor,
editHealthMonitor: editHealthMonitor
editHealthMonitor: editHealthMonitor,
updateMemberList: updateMemberList
};
return service;
......@@ -345,8 +346,6 @@
});
}
// Health Monitors
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.editMember
* @description
......@@ -364,6 +363,23 @@
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.updateMemberList
* @description
* Update the list of pool members by adding or removing the necessary members.
* @param {string} poolId
* Specifies the id of the pool the members belong to.
* @param {object} spec
* Specifies the data used to update the member list.
*/
function updateMemberList(poolId, spec) {
return apiService.put('/api/lbaas/pools/' + poolId + '/members/', spec)
.error(function () {
toastService.add('error', gettext('Unable to update member list.'));
});
}
// Health Monitors
/**
......
......@@ -219,6 +219,14 @@
error: 'Unable to create health monitor.',
data: { name: 'healthmonitor-1' },
testInput: [ { name: 'healthmonitor-1' } ]
},
{
func: 'updateMemberList',
method: 'put',
path: '/api/lbaas/pools/1234/members/',
error: 'Unable to update member list.',
data: { name: 'member-1' },
testInput: [ '1234', { name: 'member-1' } ]
}
];
......
/*
* 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.members')
.factory('horizon.dashboard.project.lbaasv2.members.actions.batchActions',
tableBatchActions);
tableBatchActions.$inject = [
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.actions.update-member-list'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.pools.actions.batchActions
*
* @description
* Provides the service for the Members table batch actions.
*
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param updateMemberListService The LBaaS v2 update member list service.
* @returns Members table batch actions service object.
*/
function tableBatchActions(
gettext, loadBalancersService, updateMemberListService
) {
var loadBalancerIsActionable, loadBalancerId;
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadBalancerId_) {
loadBalancerId = _loadBalancerId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadBalancerId);
return service;
}
function actions() {
return [{
service: updateMemberListService.init(loadBalancerIsActionable).update,
template: {
text: gettext('Add/Remove Pool Members')
}
}];
}
}
})();
/*
* 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 Members Table Batch 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(module(function($provide) {
var response = {
data: {
id: '1'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$modal', modal);
}));
beforeEach(inject(function ($injector) {
var batchActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.members.actions.batchActions');
actions = batchActionsService.actions();
}));
it('should define correct table batch actions', function() {
expect(actions.length).toBe(1);
expect(actions[0].template.text).toBe('Add/Remove Pool Members');
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
});
})();
/*
* 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.members')
.factory('horizon.dashboard.project.lbaasv2.members.actions.update-member-list',
updateMemberListService);
updateMemberListService.$inject = [
'$q',
'$route',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.listeners.actions.updateMemberListService
* @description
* Provides the service for updating the list of pool members.
* @param $q The angular service for promises.
* @param $route The angular $route service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
* @returns The load balancers members update member list service.
*/
function updateMemberListService(
$q, $route, workflowModal, policy, gettext
) {
var statePromise;
var updateList = workflowModal.init({
controller: 'UpdateMemberListWizardController',
message: gettext('The pool members have been updated.'),
handle: onUpdate,
allowed: allowed
});
var service = {
init: init,
update: updateList
};
return service;
//////////////
function init(_statePromise_) {
statePromise = _statePromise_;
return service;
}
function allowed(/*item*/) {
return $q.all([
statePromise,
policy.ifAllowed({ rules: [['neutron', 'update_member_list']] })
]);
}
function onUpdate(/*response*/) {
$route.reload();
}
}
})();
/*
* 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 Update Member List Action Service', function() {
var scope, $q, $route, policy, init, updateMemberListService, defer;
function allowed() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = updateMemberListService.update.allowed();
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_member_list']]});
return allowed;
}
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(module(function($provide) {
var response = {
data: {
id: '9012'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$modal', modal);
}));
beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$q = $injector.get('$q');
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
$route = $injector.get('$route');
updateMemberListService = $injector.get(
'horizon.dashboard.project.lbaasv2.members.actions.update-member-list');
init = updateMemberListService.init;
defer = $q.defer();
}));
it('should define the correct service properties', function() {
expect(updateMemberListService.init).toBeDefined();
expect(updateMemberListService.update).toBeDefined();
});
it('should have the "allowed" and "perform" functions', function() {
expect(updateMemberListService.update.allowed).toBeDefined();
expect(updateMemberListService.update.perform).toBeDefined();
});
it('should allow editing a pool under an ACTIVE load balancer', function() {
defer.resolve();
init(defer.promise);
expect(allowed()).toBe(true);
});
it('should not allow editing a pool under an NON-ACTIVE load balancer', function() {
defer.reject();
init(defer.promise);
expect(allowed()).toBe(false);
});
it('should redirect after edit', function() {
spyOn($route, 'reload').and.callThrough();
updateMemberListService.update.perform();
expect($route.reload).toHaveBeenCalled();
});
});
})();
/*
* 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.members')
.controller('UpdateMemberListWizardController', UpdateMemberListWizardController);
UpdateMemberListWizardController.$inject = [
'$scope',
'$routeParams',
'horizon.dashboard.project.lbaasv2.workflow.model',
'horizon.dashboard.project.lbaasv2.workflow.workflow',
'horizon.framework.util.i18n.gettext'
];
function UpdateMemberListWizardController($scope, $routeParams, model, workflowService, gettext) {
var loadbalancerId = $routeParams.loadbalancerId;
var poolId = $routeParams.poolId;
var scope = $scope;
scope.model = model;
scope.submit = scope.model.submit;
scope.workflow = workflowService(
gettext('Add/Remove Pool Members'),
'fa fa-pencil',
['members']
);
scope.model.initialize('members', false, loadbalancerId, poolId);
}
})();
/*
* 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 Update Member List Wizard Controller', function() {
var ctrl, workflowSpy, scope;
var model = {
submit: function() {
return 'updated';
},
initialize: angular.noop
};
var workflow = 'foo';
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) {
workflowSpy = jasmine.createSpy('workflow').and.returnValue(workflow);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.workflow', workflowSpy);
}));
beforeEach(inject(function ($controller, $injector) {
scope = $injector.get('$rootScope').$new();
spyOn(model, 'initialize').and.callThrough();
ctrl = $controller('UpdateMemberListWizardController', {
$scope: scope,
$routeParams: {loadbalancerId: 'loadbalancerId',
poolId: 'poolId'}});
}));
it('defines the controller', function() {
expect(ctrl).toBeDefined();
});
it('calls initialize on the given model', function() {
expect(model.initialize).toHaveBeenCalledWith('members', false, 'loadbalancerId', 'poolId');
});
it('sets scope.workflow to the given workflow', function() {
expect(scope.workflow).toBe(workflow);
});
it('initializes workflow with correct properties', function() {
expect(workflowSpy).toHaveBeenCalledWith('Add/Remove Pool Members',
'fa fa-pencil', ['members']);
});
it('defines scope.submit', function() {
expect(scope.submit).toBe(model.submit);
expect(scope.submit()).toBe('updated');
});
});
})();
......@@ -23,6 +23,7 @@
MembersTableController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.members.actions.rowActions',
'horizon.dashboard.project.lbaasv2.members.actions.batchActions',
'$routeParams'
];
......@@ -35,11 +36,12 @@
*
* @param api The LBaaS V2 service API.
* @param rowActions The pool members row actions service.
* @param batchActions The members batch actions service.
* @param $routeParams The angular $routeParams service.
* @returns undefined
*/
function MembersTableController(api, rowActions, $routeParams) {
function MembersTableController(api, rowActions, batchActions, $routeParams) {
var ctrl = this;
ctrl.items = [];
......@@ -49,6 +51,7 @@
ctrl.listenerId = $routeParams.listenerId;
ctrl.poolId = $routeParams.poolId;
ctrl.rowActions = rowActions.init(ctrl.loadbalancerId, ctrl.poolId);