Show the member status properties

This change adds the 'Operating Status' and 'Provisioning Status'
properties to the member attributes that are visible in the
members table and detail pages.  The member statuses are taken
from the Load Balancer status tree API, which returns all child
resources with their corresponding statuses for a particular Load
Balancer.

Closes-Bug: #1563444
Change-Id: Icd812bdc8774c3dd4522ff923229bfecaeb1b9dd
This commit is contained in:
Lucas Palm 2017-07-03 17:51:56 +08:00 committed by Jacky Hu
parent 20fde209c2
commit d57d7a4ebd
12 changed files with 316 additions and 17 deletions

View File

@ -428,6 +428,23 @@ class LoadBalancers(generic.View):
return create_loadbalancer(request)
@urls.register
class LoadBalancerStatusTree(generic.View):
"""API for retrieving the resource status tree for a single load balancer.
"""
url_regex = r'lbaas/loadbalancers/(?P<loadbalancer_id>[^/]+)/statuses/$'
@rest_utils.ajax()
def get(self, request, loadbalancer_id):
"""Get the status tree for a specific load balancer.
http://localhost/api/lbaas/loadbalancers/cc758c90-3d98-4ea1-af44-aab405c9c915/statuses
"""
return neutronclient(request).retrieve_loadbalancer_status(
loadbalancer_id)
@urls.register
class LoadBalancer(generic.View):
"""API for retrieving, updating, and deleting a single load balancer.

View File

@ -41,6 +41,7 @@
deleteLoadBalancer: deleteLoadBalancer,
createLoadBalancer: createLoadBalancer,
editLoadBalancer: editLoadBalancer,
getLoadBalancerStatusTree: getLoadBalancerStatusTree,
getListeners: getListeners,
getListener: getListener,
createListener: createListener,
@ -147,6 +148,21 @@
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getLoadBalancerStatusTree
* @description
* Get the status tree for a load balancer
* @param {string} id
* Specifies the id of the load balancer to request the status tree for.
*/
function getLoadBalancerStatusTree(id) {
return apiService.get('/api/lbaas/loadbalancers/' + id + '/statuses/')
.error(function () {
toastService.add('error', gettext('Unable to retrieve load balancer status tree.'));
});
}
// Listeners
/**

View File

@ -59,6 +59,13 @@
error: 'Unable to delete load balancer.',
testInput: [ '1234' ]
},
{
func: 'getLoadBalancerStatusTree',
method: 'get',
path: '/api/lbaas/loadbalancers/1234/statuses/',
error: 'Unable to retrieve load balancer status tree.',
testInput: [ '1234' ]
},
{
func: 'getListeners',
method: 'get',

View File

@ -46,6 +46,7 @@
var provisioningStatus = {
ACTIVE: gettext('Active'),
INACTIVE: gettext('Inactive'),
PENDING_CREATE: gettext('Pending Create'),
PENDING_UPDATE: gettext('Pending Update'),
PENDING_DELETE: gettext('Pending Delete'),

View File

@ -23,7 +23,9 @@
MemberDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.members.actions.rowActions',
'$routeParams'
'$routeParams',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.service'
];
/**
@ -36,20 +38,29 @@
* @param api The LBaaS v2 API service.
* @param rowActions The pool members row actions service.
* @param $routeParams The angular $routeParams service.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param membersService The LBaaS v2 members service.
* @returns undefined
*/
function MemberDetailController(api, rowActions, $routeParams) {
function MemberDetailController(
api, rowActions, $routeParams, loadBalancersService, membersService
) {
var ctrl = this;
ctrl.actions = rowActions.init($routeParams.loadbalancerId, $routeParams.poolId).actions;
ctrl.loadbalancerId = $routeParams.loadbalancerId;
ctrl.listenerId = $routeParams.listenerId;
ctrl.poolId = $routeParams.poolId;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
init();
////////////////////////////////
function init() {
api.getMember($routeParams.poolId, $routeParams.memberId).success(set('member'));
api.getMember($routeParams.poolId, $routeParams.memberId).success(memberSuccess);
api.getPool($routeParams.poolId).success(set('pool'));
api.getListener($routeParams.listenerId).success(set('listener'));
api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer'));
@ -61,6 +72,15 @@
}, property);
}
function memberSuccess(response) {
ctrl.member = response;
membersService.associateMemberStatuses(
ctrl.loadbalancerId,
ctrl.listenerId,
ctrl.poolId,
[ctrl.member]);
}
}
})();

View File

@ -17,7 +17,7 @@
'use strict';
describe('LBaaS v2 Member Detail Controller', function() {
var lbaasv2API, ctrl, actions;
var controller, lbaasv2API, membersService, ctrl, actions;
function fakeAPI() {
return {
@ -60,13 +60,15 @@
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
actions = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.rowActions');
membersService = $injector.get('horizon.dashboard.project.lbaasv2.members.service');
spyOn(lbaasv2API, 'getMember').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
actions = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.rowActions');
spyOn(actions, 'init').and.callThrough();
var controller = $injector.get('$controller');
spyOn(membersService, 'associateMemberStatuses');
controller = $injector.get('$controller');
ctrl = controller('MemberDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
@ -82,17 +84,23 @@
expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId');
expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId');
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId');
expect(ctrl.loadbalancer).toEqual({ provisioning_status: 'ACTIVE' });
expect(ctrl.listener).toBe('foo');
expect(ctrl.pool).toBe('foo');
expect(ctrl.member).toBe('foo');
});
it('should have actions', function() {
it('should initialize the controller properties correctly', function() {
expect(ctrl.loadbalancerId).toBeDefined();
expect(ctrl.listenerId).toBeDefined();
expect(ctrl.poolId).toBeDefined();
expect(ctrl.operatingStatus).toBeDefined();
expect(ctrl.provisioningStatus).toBeDefined();
expect(ctrl.actions).toBe('member-actions');
expect(actions.init).toHaveBeenCalledWith('loadbalancerId', 'poolId');
});
it('should invoke the "associateMemberStatuses" method', function() {
expect(membersService.associateMemberStatuses).toHaveBeenCalledWith(
ctrl.loadbalancerId, ctrl.listenerId, ctrl.poolId, [ctrl.member]);
});
});
})();

View File

@ -19,6 +19,10 @@
<dd>{$ ::ctrl.member.protocol_port $}</dd>
<dt translate>Weight</dt>
<dd>{$ ctrl.member.weight $}</dd>
<dt translate>Operating Status</dt>
<dd>{$ ctrl.member.operating_status | decode:ctrl.operatingStatus $}</dd>
<dt translate>Provisioning Status</dt>
<dd>{$ ctrl.member.provisioning_status | decode:ctrl.provisioningStatus $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.member.admin_state_up | yesno $}</dd>
<dt translate>Member ID</dt>

View File

@ -0,0 +1,92 @@
/*
* 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.service', membersService);
membersService.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2'
];
/**
* @ngdoc service
* @name horizon.dashboard.project.lbaasv2.members.service
* @description General service for LBaaS v2 members.
* @param api The LBaaS V2 service API.
* @returns The members service.
*/
function membersService(api) {
var service = {
associateMemberStatuses: associateMemberStatuses
};
return service;
////////////
/**
* @ngdoc method
* @name horizon.dashboard.project.lbaasv2.members.service.associateMemberStatuses
* @description Associates the list of specified members with their corresponding statuses
* that are retrieved from the load balancer status tree.
* @param loadBalancerId The load balancer ID.
* @param listenerId The listener ID that the members belong to.
* @param poolId The pool ID that the members belong to.
* @param members The list of members to associate with their corresponding health statuses.
* @returns None
*/
function associateMemberStatuses(loadBalancerId, listenerId, poolId, members) {
api.getLoadBalancerStatusTree(loadBalancerId).then(function(response) {
// Collect the member status data for all specified members
var memberStatusData = [];
var listeners = response.data.statuses.loadbalancer.listeners;
for (var listenerIndex = 0; listenerIndex < listeners.length; listenerIndex++) {
var listener = listeners[listenerIndex];
if (listener.id === listenerId) {
var pools = listener.pools;
for (var poolIndex = 0; poolIndex < pools.length; poolIndex++) {
var pool = pools[poolIndex];
if (pool.id === poolId) {
memberStatusData = pool.members;
break;
}
}
break;
}
}
// Attach the status properties to each member object
members.forEach(mapStatusToMember);
function mapStatusToMember(member) {
for (var memberIndex = 0; memberIndex < memberStatusData.length; memberIndex++) {
var memberWithStatuses = memberStatusData[memberIndex];
if (memberWithStatuses.id === member.id) {
member.operating_status = memberWithStatuses.operating_status;
member.provisioning_status = memberWithStatuses.provisioning_status;
break;
}
}
}
});
}
}
}());

View File

@ -0,0 +1,107 @@
/*
* 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 Service', function() {
var service, $q, scope;
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('horizon.app.core.openstack-service-api.lbaasv2', {
getLoadBalancerStatusTree: function() {
var deferred = $q.defer();
var response = {
data: {
statuses: {
loadbalancer: {
id: 'loadbalancer1',
listeners: [
{
id: 'listener0',
pools: []
},
{
id: 'listener1',
pools: [
{
id: 'pool0',
members: []
},
{
id: 'pool1',
members: [
{
id: 'member1',
operating_status: 'ONLINE',
provisioning_status: 'ACTIVE'
},
{
id: 'member2',
operating_status: 'OFFLINE',
provisioning_status: 'INACTIVE'
}
]
}
]
}
]
}
}
}
};
deferred.resolve(response);
return deferred.promise;
}
});
}));
beforeEach(inject(function ($injector) {
service = $injector.get('horizon.dashboard.project.lbaasv2.members.service');
$q = $injector.get('$q');
scope = $injector.get('$rootScope').$new();
}));
it('should define service attributes', function() {
expect(service.associateMemberStatuses).toBeDefined();
});
it('should correctly associate member health statuses', function() {
var members = [
{ id: 'member1' },
{ id: 'member2' }
];
service.associateMemberStatuses('loadbalancer1', 'listener1', 'pool1', members);
scope.$apply();
expect(members.length).toBe(2);
expect(members[0].operating_status).toBe('ONLINE');
expect(members[0].provisioning_status).toBe('ACTIVE');
expect(members[1].operating_status).toBe('OFFLINE');
expect(members[1].provisioning_status).toBe('INACTIVE');
});
});
})();

View File

@ -24,7 +24,9 @@
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.members.actions.rowActions',
'horizon.dashboard.project.lbaasv2.members.actions.batchActions',
'$routeParams'
'$routeParams',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.service'
];
/**
@ -38,11 +40,14 @@
* @param rowActions The pool members row actions service.
* @param batchActions The members batch actions service.
* @param $routeParams The angular $routeParams service.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param membersService The LBaaS v2 members service.
* @returns undefined
*/
function MembersTableController(api, rowActions, batchActions, $routeParams) {
function MembersTableController(
api, rowActions, batchActions, $routeParams, loadBalancersService, membersService
) {
var ctrl = this;
ctrl.items = [];
ctrl.src = [];
@ -54,6 +59,8 @@
ctrl.poolId = $routeParams.poolId;
ctrl.rowActions = rowActions.init(ctrl.loadbalancerId, ctrl.poolId);
ctrl.batchActions = batchActions.init(ctrl.loadbalancerId);
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
init();
@ -69,6 +76,11 @@
function success(response) {
ctrl.src = response.data.items;
ctrl.loading = false;
membersService.associateMemberStatuses(
ctrl.loadbalancerId,
ctrl.listenerId,
ctrl.poolId,
ctrl.src);
}
function fail(/*response*/) {

View File

@ -1,5 +1,6 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
@ -17,7 +18,7 @@
'use strict';
describe('LBaaS v2 Members Table Controller', function() {
var controller, lbaasv2API, scope;
var controller, lbaasv2API, membersService, scope;
var items = [{ foo: 'bar' }];
var apiFail = false;
@ -40,15 +41,16 @@
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
membersService = $injector.get('horizon.dashboard.project.lbaasv2.members.service');
controller = $injector.get('$controller');
spyOn(lbaasv2API, 'getMembers').and.callFake(fakeAPI);
spyOn(membersService, 'associateMemberStatuses');
}));
function createController() {
@ -61,7 +63,7 @@
}});
}
it('should initialize correctly', function() {
it('should initialize the controller properties correctly', function() {
var ctrl = createController();
expect(ctrl.items).toEqual([]);
expect(ctrl.src).toEqual(items);
@ -71,7 +73,10 @@
expect(ctrl.loadbalancerId).toBeDefined();
expect(ctrl.listenerId).toBeDefined();
expect(ctrl.poolId).toBeDefined();
expect(ctrl.rowActions).toBeDefined();
expect(ctrl.batchActions).toBeDefined();
expect(ctrl.operatingStatus).toBeDefined();
expect(ctrl.provisioningStatus).toBeDefined();
});
it('should invoke lbaasv2 apis', function() {
@ -80,6 +85,12 @@
expect(ctrl.src.length).toBe(1);
});
it('should invoke the "associateMemberStatuses" method', function() {
var ctrl = createController();
expect(membersService.associateMemberStatuses).toHaveBeenCalledWith(
ctrl.loadbalancerId, ctrl.listenerId, ctrl.poolId, ctrl.src);
});
it('should show error if loading fails', function() {
apiFail = true;
var ctrl = createController();

View File

@ -38,6 +38,8 @@
<th class="rsp-p1" st-sort="id" st-sort-default="id" translate>ID</th>
<th class="rsp-p1" st-sort="address" translate>IP Address</th>
<th class="rsp-p1" st-sort="protocol_port" translate>Protocol Port</th>
<th class="rsp-p1" st-sort="protocol_port" translate>Operating Status</th>
<th class="rsp-p1" st-sort="protocol_port" translate>Provisioning Status</th>
<th class="rsp-p1" st-sort="weight" translate>Weight</th>
<th class="actions_column" translate>Actions</th>
</tr>
@ -63,6 +65,8 @@
<td class="rsp-p1"><a ng-href="project/ngloadbalancersv2/{$ ::table.loadbalancerId $}/listeners/{$ ::table.listenerId $}/pools/{$ ::table.poolId $}/members/{$ ::item.id $}">{$ ::item.id $}</a></td>
<td class="rsp-p1">{$ ::item.address $}</td>
<td class="rsp-p1">{$ ::item.protocol_port $}</td>
<td class="rsp-p1">{$ ::item.operating_status | decode:table.operatingStatus $}</td>
<td class="rsp-p1">{$ ::item.provisioning_status | decode:table.provisioningStatus $}</td>
<td class="rsp-p1">{$ item.weight $}</td>
<td class="actions_column">
<!--