List children pools on LB details page

Change-Id: Icc3c16ba49a934c50622e745faf02701f4bde0df
Story: 1713851
Task: 5365
This commit is contained in:
Jacky Hu 2018-03-09 22:53:22 +08:00 committed by Michael Johnson
parent 424b307689
commit 9711760b33
29 changed files with 617 additions and 70 deletions

View File

@ -204,6 +204,7 @@ def create_pool(request, **kwargs):
lb_algorithm=data['pool']['method'], lb_algorithm=data['pool']['method'],
session_persistence=session_persistence, session_persistence=session_persistence,
listener_id=kwargs['listener_id'], listener_id=kwargs['listener_id'],
loadbalancer_id=kwargs['loadbalancer_id'],
name=data['pool'].get('name'), name=data['pool'].get('name'),
description=data['pool'].get('description'), description=data['pool'].get('description'),
admin_state_up=data['pool'].get('admin_state_up') admin_state_up=data['pool'].get('admin_state_up')

View File

@ -121,9 +121,15 @@
}); });
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) { if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer/' + loadbalancerId + var path;
if (listenerId) {
path = 'project/load_balancer/' + loadbalancerId +
'/listeners/' + listenerId + '/listeners/' + listenerId +
'/pools/' + poolId; '/pools/' + poolId;
} else {
path = 'project/load_balancer/' + loadbalancerId +
'/pools/' + poolId;
}
$location.path(path); $location.path(path);
} }
return actionResult.result; return actionResult.result;

View File

@ -64,7 +64,7 @@
}); });
}); });
it('should handle the action result properly', function() { it('should handle the action result properly with listener', function() {
spyOn($location, 'path'); spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop}); spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deleteHealthMonitor').and.callFake(angular.noop); spyOn(lbaasv2API, 'deleteHealthMonitor').and.callFake(angular.noop);
@ -91,6 +91,33 @@
expect(result.failed[0].id).toBe(1); expect(result.failed[0].id).toBe(1);
}); });
it('should handle the action result properly without listener', function() {
spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deleteHealthMonitor').and.callFake(angular.noop);
service.perform({loadbalancerId: 1, poolId: 3, id: 1, name: 'one'});
var result = service.deleteResult({
fail: [],
pass: [{
context: {
id: 1
}
}]
});
var path = 'project/load_balancer/1/pools/3';
expect($location.path).toHaveBeenCalledWith(path);
expect(result.deleted[0].id).toBe(1);
result = service.deleteResult({
pass: [],
fail: [{
context: {
id: 1
}
}]
});
expect(result.failed[0].id).toBe(1);
});
describe('allow method', function() { describe('allow method', function() {
it('should use default policy if batch action', function () { it('should use default policy if batch action', function () {
spyOn(policyAPI, 'ifAllowed'); spyOn(policyAPI, 'ifAllowed');

View File

@ -63,6 +63,13 @@
ctrl.provisioningStatus = loadBalancersService.provisioningStatus; ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.loadbalancer = loadbalancer; ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener; ctrl.listener = listener;
if (!angular.equals({}, ctrl.listener)) {
ctrl.withListenerStyle = {};
ctrl.withoutListenerStyle = {display: 'none'};
} else {
ctrl.withListenerStyle = {display: 'none'};
ctrl.withoutListenerStyle = {};
}
ctrl.pool = pool; ctrl.pool = pool;
ctrl.healthmonitor = healthmonitor; ctrl.healthmonitor = healthmonitor;
ctrl.resourceType = typeRegistry.getResourceType(resourceType); ctrl.resourceType = typeRegistry.getResourceType(resourceType);

View File

@ -47,6 +47,19 @@
}; };
$timeout = _$timeout_; $timeout = _$timeout_;
scope = $rootScope.$new(); scope = $rootScope.$new();
ctrl = $controller('HealthMonitorDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: {},
pool: { id: '123' },
healthmonitor: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
ctrl = $controller('HealthMonitorDetailController', { ctrl = $controller('HealthMonitorDetailController', {
$scope: scope, $scope: scope,
loadbalancer: { id: '123' }, loadbalancer: { id: '123' },

View File

@ -4,8 +4,9 @@
<li class="breadcrumb-item-truncate"><translate>Network</translate></li> <li class="breadcrumb-item-truncate"><translate>Network</translate></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li> <li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li> <li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li> <li class="breadcrumb-item-truncate" ng-style="ctrl.withListenerStyle"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ :: ctrl.listener.id $}">{$ :: (ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li> <li class="breadcrumb-item-truncate" ng-style="ctrl.withListenerStyle"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ :: ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="breadcrumb-item-truncate" ng-style="ctrl.withoutListenerStyle"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="breadcrumb-item-truncate active">{$ ::(ctrl.healthmonitor.name || ctrl.healthmonitor.id) $}</li> <li class="breadcrumb-item-truncate active">{$ ::(ctrl.healthmonitor.name || ctrl.healthmonitor.id) $}</li>
</ol> </ol>
<div class="row"> <div class="row">

View File

@ -68,9 +68,12 @@
var loadbalancers = '/project/load_balancer'; var loadbalancers = '/project/load_balancer';
var listener = loadbalancers + '/:loadbalancerId/listeners/:listenerId'; var listener = loadbalancers + '/:loadbalancerId/listeners/:listenerId';
var pool = listener + '/pools/:poolId'; var listenerPool = listener + '/pools/:poolId';
var member = pool + '/members/:memberId'; var listenerPoolMember = listenerPool + '/members/:memberId';
var healthmonitor = pool + '/healthmonitors/:healthmonitorId'; var listenerPoolHealthmonitor = listenerPool + '/healthmonitors/:healthmonitorId';
var loadbalancerPool = loadbalancers + '/:loadbalancerId/pools/:poolId';
var loadbalancerPoolMember = loadbalancerPool + '/members/:memberId';
var loadbalancerPoolHealthmonitor = loadbalancerPool + '/healthmonitors/:healthmonitorId';
$routeProvider $routeProvider
.when(loadbalancers, { .when(loadbalancers, {
@ -126,7 +129,7 @@
controller: 'ListenerDetailController', controller: 'ListenerDetailController',
controllerAs: 'ctrl' controllerAs: 'ctrl'
}) })
.when(pool, { .when(listenerPool, {
templateUrl: basePath + 'pools/details/detail.html', templateUrl: basePath + 'pools/details/detail.html',
resolve: { resolve: {
loadbalancer: [ loadbalancer: [
@ -169,7 +172,7 @@
controller: 'PoolDetailController', controller: 'PoolDetailController',
controllerAs: 'ctrl' controllerAs: 'ctrl'
}) })
.when(member, { .when(listenerPoolMember, {
templateUrl: basePath + 'members/details/detail.html', templateUrl: basePath + 'members/details/detail.html',
resolve: { resolve: {
loadbalancer: [ loadbalancer: [
@ -225,7 +228,7 @@
controller: 'MemberDetailController', controller: 'MemberDetailController',
controllerAs: 'ctrl' controllerAs: 'ctrl'
}) })
.when(healthmonitor, { .when(listenerPoolHealthmonitor, {
templateUrl: basePath + 'healthmonitors/details/detail.html', templateUrl: basePath + 'healthmonitors/details/detail.html',
resolve: { resolve: {
loadbalancer: [ loadbalancer: [
@ -280,6 +283,137 @@
}, },
controller: 'HealthMonitorDetailController', controller: 'HealthMonitorDetailController',
controllerAs: 'ctrl' controllerAs: 'ctrl'
})
.when(loadbalancerPool, {
templateUrl: basePath + 'pools/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: function() {
return {};
},
pool: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getPool($route.current.params.poolId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
return response.data;
}
);
}
]
},
controller: 'PoolDetailController',
controllerAs: 'ctrl'
})
.when(loadbalancerPoolMember, {
templateUrl: basePath + 'members/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: function() {
return {};
},
pool: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getPool($route.current.params.poolId).then(
function success(response) {
return response.data;
}
);
}
],
member: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getMember($route.current.params.poolId,
$route.current.params.memberId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
response.data.poolId = $route.current.params.poolId;
return response.data;
}
);
}
]
},
controller: 'MemberDetailController',
controllerAs: 'ctrl'
})
.when(loadbalancerPoolHealthmonitor, {
templateUrl: basePath + 'healthmonitors/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: function() {
return {};
},
pool: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getPool($route.current.params.poolId).then(
function success(response) {
return response.data;
}
);
}
],
healthmonitor: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getHealthMonitor(
$route.current.params.healthmonitorId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
response.data.poolId = $route.current.params.poolId;
return response.data;
}
);
}
]
},
controller: 'HealthMonitorDetailController',
controllerAs: 'ctrl'
}); });
} }

View File

@ -182,7 +182,7 @@
}); });
})); }));
it('should route resolved pool detail', inject(function($injector) { it('should route resolved listener pool detail', inject(function($injector) {
function loadbalancerAPI() { function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' }; var loadbalancer = { provisioning_status: 'ACTIVE' };
return { return {
@ -233,7 +233,7 @@
}); });
})); }));
it('should route resolved member detail', inject(function($injector) { it('should route resolved listener pool member detail', inject(function($injector) {
function loadbalancerAPI() { function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' }; var loadbalancer = { provisioning_status: 'ACTIVE' };
return { return {
@ -297,7 +297,7 @@
}); });
})); }));
it('should route resolved health monitor detail', inject(function($injector) { it('should route resolved listener pool health monitor detail', inject(function($injector) {
function loadbalancerAPI() { function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' }; var loadbalancer = { provisioning_status: 'ACTIVE' };
return { return {
@ -361,6 +361,146 @@
}); });
})); }));
it('should route resolved loadbalancer pool detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function poolAPI() {
var pool = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(pool);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(poolAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/pools/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/pools/3');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved loadbalancer pool member detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function poolAPI() {
var pool = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(pool);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function memberAPI() {
var member = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(member);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(poolAPI);
spyOn(lbaasv2API, 'getMember').and.callFake(memberAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/members/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/pools/3/members/4');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved loadbalancer pool health monitor detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function poolAPI() {
var pool = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(pool);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function healthmonitorAPI() {
var healthmonitor = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(healthmonitor);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(poolAPI);
spyOn(lbaasv2API, 'getHealthMonitor').and.callFake(healthmonitorAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/healthmonitors/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/pools/3/healthmonitors/4');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should redirect to project home on route change error', it('should redirect to project home on route change error',
inject(function($location, $rootScope) { inject(function($location, $rootScope) {
spyOn($location, 'path').and.callThrough(); spyOn($location, 'path').and.callThrough();

View File

@ -57,12 +57,10 @@
list-function-extra-params="ctrl.listFunctionExtraParams"> list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table> </hz-resource-table>
</uib-tab> </uib-tab>
<!--
<uib-tab heading="{$ 'Pools' | translate $}"> <uib-tab heading="{$ 'Pools' | translate $}">
<hz-resource-table resource-type-name="OS::Octavia::Pool" <hz-resource-table resource-type-name="OS::Octavia::Pool"
track-by="trackBy" track-by="trackBy"
list-function-extra-params="ctrl.listFunctionExtraParams"> list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table> </hz-resource-table>
</uib-tab> </uib-tab>
-->
</uib-tabset> </uib-tabset>

View File

@ -104,10 +104,16 @@
} }
function getMemberDetailsPath(item) { function getMemberDetailsPath(item) {
if (item.listenerId) {
return 'project/load_balancer/' + item.loadbalancerId + return 'project/load_balancer/' + item.loadbalancerId +
'/listeners/' + item.listenerId + '/listeners/' + item.listenerId +
'/pools/' + item.poolId + '/pools/' + item.poolId +
'/members/' + item.id; '/members/' + item.id;
} else {
return 'project/load_balancer/' + item.loadbalancerId +
'/pools/' + item.poolId +
'/members/' + item.id;
}
} }
function getMembersPromise(params) { function getMembersPromise(params) {
@ -131,10 +137,16 @@
} }
function getHealthMonitorDetailsPath(item) { function getHealthMonitorDetailsPath(item) {
if (item.listenerId) {
return 'project/load_balancer/' + item.loadbalancerId + return 'project/load_balancer/' + item.loadbalancerId +
'/listeners/' + item.listenerId + '/listeners/' + item.listenerId +
'/pools/' + item.poolId + '/pools/' + item.poolId +
'/healthmonitors/' + item.id; '/healthmonitors/' + item.id;
} else {
return 'project/load_balancer/' + item.loadbalancerId +
'/pools/' + item.poolId +
'/healthmonitors/' + item.id;
}
} }
function getHealthMonitorsPromise(params) { function getHealthMonitorsPromise(params) {
@ -177,9 +189,15 @@
} }
function getPoolDetailsPath(item) { function getPoolDetailsPath(item) {
if (item.listeners.length > 0) {
return 'project/load_balancer/' + return 'project/load_balancer/' +
item.loadbalancerId + '/listeners/' + item.loadbalancerId + '/listeners/' +
item.listeners[0].id + '/pools/' + item.id; item.listeners[0].id + '/pools/' + item.id;
} else {
return 'project/load_balancer/' +
item.loadbalancerId +
'/pools/' + item.id;
}
} }
function getListenersPromise(params) { function getListenersPromise(params) {

View File

@ -95,6 +95,9 @@
var myItem = {loadbalancerId: '123', id: '789', listeners: [{id: '456'}]}; var myItem = {loadbalancerId: '123', id: '789', listeners: [{id: '456'}]};
expect(service.getPoolDetailsPath(myItem)) expect(service.getPoolDetailsPath(myItem))
.toBe('project/load_balancer/123/listeners/456/pools/789'); .toBe('project/load_balancer/123/listeners/456/pools/789');
myItem = {loadbalancerId: '123', id: '789', listeners: []};
expect(service.getPoolDetailsPath(myItem))
.toBe('project/load_balancer/123/pools/789');
}); });
it("getPoolsPromise provides a promise", inject(function($timeout) { it("getPoolsPromise provides a promise", inject(function($timeout) {
@ -127,6 +130,13 @@
}; };
expect(service.getMemberDetailsPath(myItem)) expect(service.getMemberDetailsPath(myItem))
.toBe('project/load_balancer/1/listeners/2/pools/3/members/4'); .toBe('project/load_balancer/1/listeners/2/pools/3/members/4');
myItem = {
loadbalancerId: '1',
poolId: '3',
id: '4'
};
expect(service.getMemberDetailsPath(myItem))
.toBe('project/load_balancer/1/pools/3/members/4');
}); });
it("getMembersPromise provides a promise", inject(function($timeout) { it("getMembersPromise provides a promise", inject(function($timeout) {
@ -165,6 +175,13 @@
}; };
expect(service.getHealthMonitorDetailsPath(myItem)) expect(service.getHealthMonitorDetailsPath(myItem))
.toBe('project/load_balancer/1/listeners/2/pools/3/healthmonitors/4'); .toBe('project/load_balancer/1/listeners/2/pools/3/healthmonitors/4');
myItem = {
loadbalancerId: '1',
poolId: '3',
id: '4'
};
expect(service.getHealthMonitorDetailsPath(myItem))
.toBe('project/load_balancer/1/pools/3/healthmonitors/4');
}); });
it("getHealthMonitorsPromise provides a promise", inject(function($timeout) { it("getHealthMonitorsPromise provides a promise", inject(function($timeout) {

View File

@ -119,9 +119,15 @@
}); });
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) { if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer/' + loadbalancerId + var path;
if (listenerId) {
path = 'project/load_balancer/' + loadbalancerId +
'/listeners/' + listenerId + '/listeners/' + listenerId +
'/pools/' + poolId; '/pools/' + poolId;
} else {
path = 'project/load_balancer/' + loadbalancerId +
'/pools/' + poolId;
}
$location.path(path); $location.path(path);
} }
return actionResult.result; return actionResult.result;

View File

@ -63,7 +63,7 @@
}); });
}); });
it('should handle the action result properly', function() { it('should handle the action result properly with listener', function() {
spyOn($location, 'path'); spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop}); spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deleteMember').and.callFake(angular.noop); spyOn(lbaasv2API, 'deleteMember').and.callFake(angular.noop);
@ -90,6 +90,33 @@
expect(result.failed[0].id).toBe(1); expect(result.failed[0].id).toBe(1);
}); });
it('should handle the action result properly without listener', function() {
spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deleteMember').and.callFake(angular.noop);
service.perform({loadbalancerId: 1, poolId: 3, id: 1, name: 'one'});
var result = service.deleteResult({
fail: [],
pass: [{
context: {
id: 1
}
}]
});
var path = 'project/load_balancer/1/pools/3';
expect($location.path).toHaveBeenCalledWith(path);
expect(result.deleted[0].id).toBe(1);
result = service.deleteResult({
pass: [],
fail: [{
context: {
id: 1
}
}]
});
expect(result.failed[0].id).toBe(1);
});
describe('allow method', function() { describe('allow method', function() {
it('should use default policy if batch action', function () { it('should use default policy if batch action', function () {
spyOn(policyAPI, 'ifAllowed'); spyOn(policyAPI, 'ifAllowed');

View File

@ -63,6 +63,13 @@
ctrl.provisioningStatus = loadBalancersService.provisioningStatus; ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.loadbalancer = loadbalancer; ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener; ctrl.listener = listener;
if (!angular.equals({}, ctrl.listener)) {
ctrl.withListenerStyle = {};
ctrl.withoutListenerStyle = {display: 'none'};
} else {
ctrl.withListenerStyle = {display: 'none'};
ctrl.withoutListenerStyle = {};
}
ctrl.pool = pool; ctrl.pool = pool;
ctrl.member = member; ctrl.member = member;
ctrl.resourceType = typeRegistry.getResourceType(resourceType); ctrl.resourceType = typeRegistry.getResourceType(resourceType);

View File

@ -47,6 +47,19 @@
}; };
$timeout = _$timeout_; $timeout = _$timeout_;
scope = $rootScope.$new(); scope = $rootScope.$new();
ctrl = $controller('MemberDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: {},
pool: { id: '123' },
member: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
ctrl = $controller('MemberDetailController', { ctrl = $controller('MemberDetailController', {
$scope: scope, $scope: scope,
loadbalancer: { id: '123' }, loadbalancer: { id: '123' },

View File

@ -4,8 +4,9 @@
<li class="breadcrumb-item-truncate"><translate>Network</translate></li> <li class="breadcrumb-item-truncate"><translate>Network</translate></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li> <li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li> <li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li> <li class="breadcrumb-item-truncate" ng-style="ctrl.withListenerStyle"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ :: ctrl.listener.id $}">{$ :: (ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li> <li class="breadcrumb-item-truncate" ng-style="ctrl.withListenerStyle"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ :: ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="breadcrumb-item-truncate" ng-style="ctrl.withoutListenerStyle"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="breadcrumb-item-truncate active">{$ ::(ctrl.member.name || ctrl.member.id) $}</li> <li class="breadcrumb-item-truncate active">{$ ::(ctrl.member.name || ctrl.member.id) $}</li>
</ol> </ol>
<div class="row"> <div class="row">

View File

@ -27,9 +27,7 @@
'$q', '$q',
'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext', 'horizon.framework.util.i18n.gettext'
'horizon.framework.util.q.extensions',
'$routeParams'
]; ];
/** /**
@ -45,15 +43,13 @@
* @param workflowModal The LBaaS workflow modal service. * @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
* @param qExtensions horizon extensions to the $q service.
* @param $routeParams The angular $routeParams service.
* *
* @returns The pool create service. * @returns The pool create service.
*/ */
function createService( function createService(
resourceType, actionResultService, resourceType, actionResultService,
$q, workflowModal, policy, gettext, qExtensions, $routeParams $q, workflowModal, policy, gettext
) { ) {
return workflowModal.init({ return workflowModal.init({
controller: 'CreatePoolWizardController', controller: 'CreatePoolWizardController',
@ -66,7 +62,6 @@
function allowed() { function allowed() {
return $q.all([ return $q.all([
qExtensions.booleanAsPromise(!!$routeParams.listenerId),
policy.ifAllowed({ rules: [['neutron', 'create_pool']] }) policy.ifAllowed({ rules: [['neutron', 'create_pool']] })
]); ]);
} }

View File

@ -40,6 +40,9 @@
'fa fa-cloud-download', 'fa fa-cloud-download',
['pool', 'members', 'monitor'] ['pool', 'members', 'monitor']
); );
if (!listenerId) {
listenerId = null;
}
scope.model.initialize('pool', false, loadbalancerId, listenerId); scope.model.initialize('pool', false, loadbalancerId, listenerId);
} }

View File

@ -40,6 +40,16 @@
beforeEach(inject(function ($controller) { beforeEach(inject(function ($controller) {
spyOn(model, 'initialize'); spyOn(model, 'initialize');
ctrl = $controller('CreatePoolWizardController', { $scope: scope }); ctrl = $controller('CreatePoolWizardController', { $scope: scope });
ctrl = $controller(
'CreatePoolWizardController',
{
$scope: scope,
$routeParams: {
loadBalancerId: '1',
listenerId: '2'
}
}
);
})); }));
it('defines the controller', function() { it('defines the controller', function() {

View File

@ -121,8 +121,13 @@
}); });
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) { if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer/' + loadbalancerId + var path;
if (listenerId) {
path = 'project/load_balancer/' + loadbalancerId +
'/listeners/' + listenerId; '/listeners/' + listenerId;
} else {
path = 'project/load_balancer/' + loadbalancerId;
}
$location.path(path); $location.path(path);
} }
return actionResult.result; return actionResult.result;

View File

@ -64,7 +64,7 @@
}); });
}); });
it('should handle the action result properly', function() { it('should handle the action result properly with listener', function() {
spyOn($location, 'path'); spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop}); spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deletePool').and.callFake(angular.noop); spyOn(lbaasv2API, 'deletePool').and.callFake(angular.noop);
@ -91,6 +91,33 @@
expect(result.failed[0].id).toBe(1); expect(result.failed[0].id).toBe(1);
}); });
it('should handle the action result properly without listener', function() {
spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deletePool').and.callFake(angular.noop);
service.perform({loadbalancerId: 1, id: 1, name: 'one'});
var result = service.deleteResult({
fail: [],
pass: [{
context: {
id: 1
}
}]
});
var path = 'project/load_balancer/1';
expect($location.path).toHaveBeenCalledWith(path);
expect(result.deleted[0].id).toBe(1);
result = service.deleteResult({
pass: [],
fail: [{
context: {
id: 1
}
}]
});
expect(result.failed[0].id).toBe(1);
});
describe('allow method', function() { describe('allow method', function() {
it('should use default policy if batch action', function () { it('should use default policy if batch action', function () {
spyOn(policyAPI, 'ifAllowed'); spyOn(policyAPI, 'ifAllowed');

View File

@ -62,6 +62,11 @@
ctrl.loadBalancerAlgorithm = loadBalancersService.loadBalancerAlgorithm; ctrl.loadBalancerAlgorithm = loadBalancersService.loadBalancerAlgorithm;
ctrl.loadbalancer = loadbalancer; ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener; ctrl.listener = listener;
if (!angular.equals({}, ctrl.listener)) {
ctrl.withListenerStyle = {};
} else {
ctrl.withListenerStyle = {display: 'none'};
}
ctrl.pool = pool; ctrl.pool = pool;
ctrl.listFunctionExtraParams = { ctrl.listFunctionExtraParams = {
loadbalancerId: ctrl.loadbalancer.id, loadbalancerId: ctrl.loadbalancer.id,

View File

@ -47,6 +47,18 @@
}; };
$timeout = _$timeout_; $timeout = _$timeout_;
scope = $rootScope.$new(); scope = $rootScope.$new();
ctrl = $controller('PoolDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: {},
pool: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
ctrl = $controller('PoolDetailController', { ctrl = $controller('PoolDetailController', {
$scope: scope, $scope: scope,
loadbalancer: { id: '123' }, loadbalancer: { id: '123' },

View File

@ -4,7 +4,7 @@
<li class="breadcrumb-item-truncate"><translate>Network</translate></li> <li class="breadcrumb-item-truncate"><translate>Network</translate></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li> <li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li> <li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li> <li class="breadcrumb-item-truncate" ng-style="ctrl.withListenerStyle"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ :: ctrl.listener.id $}">{$ :: (ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="breadcrumb-item-truncate active">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</li> <li class="breadcrumb-item-truncate active">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</li>
</ol> </ol>
<div class="row"> <div class="row">

View File

@ -86,6 +86,7 @@
subnets: [], subnets: [],
members: [], members: [],
listenerProtocols: ['HTTP', 'TCP', 'TERMINATED_HTTPS', 'HTTPS'], listenerProtocols: ['HTTP', 'TCP', 'TERMINATED_HTTPS', 'HTTPS'],
poolProtocols: ['HTTP', 'HTTPS', 'PROXY', 'TCP'],
methods: ['LEAST_CONNECTIONS', 'ROUND_ROBIN', 'SOURCE_IP'], methods: ['LEAST_CONNECTIONS', 'ROUND_ROBIN', 'SOURCE_IP'],
types: ['SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'], types: ['SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'],
monitorTypes: ['HTTP', 'PING', 'TCP'], monitorTypes: ['HTTP', 'PING', 'TCP'],
@ -243,12 +244,20 @@
model.context.submit = createPool; model.context.submit = createPool;
// We get the listener details here because we need to know the listener protocol // We get the listener details here because we need to know the listener protocol
// in order to default the new pool's protocol to match. // in order to default the new pool's protocol to match.
if (model.spec.parentResourceId) {
return $q.all([ return $q.all([
lbaasv2API.getListener(model.spec.parentResourceId).then(onGetListener), lbaasv2API.getListener(model.spec.parentResourceId).then(onGetListener),
neutronAPI.getSubnets().then(onGetSubnets), neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getPorts().then(onGetPorts), neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers) novaAPI.getServers().then(onGetServers)
]).then(initMemberAddresses); ]).then(initMemberAddresses);
} else {
return $q.all([
neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers)
]).then(initMemberAddresses);
}
} }
function initCreateMonitor() { function initCreateMonitor() {

View File

@ -451,7 +451,7 @@
}); });
}); });
describe('Post initialize model (create pool)', function() { describe('Post initialize model (create pool with listener)', function() {
beforeEach(function() { beforeEach(function() {
includeChildResources = false; includeChildResources = false;
@ -489,6 +489,44 @@
}); });
}); });
describe('Post initialize model (create pool without listener)', function() {
beforeEach(function() {
includeChildResources = false;
model.initialize('pool', false, '1234');
scope.$apply();
});
it('should initialize model properties', function() {
expect(model.initializing).toBe(false);
expect(model.initialized).toBe(true);
expect(model.subnets.length).toBe(2);
expect(model.members.length).toBe(2);
expect(model.certificates.length).toBe(0);
expect(model.listenerPorts.length).toBe(0);
expect(model.spec).toBeDefined();
expect(model.spec.loadbalancer_id).toBe('1234');
expect(model.spec.parentResourceId).toBeUndefined();
expect(model.spec.loadbalancer).toBeDefined();
expect(model.spec.listener).toBeDefined();
expect(model.spec.pool).toBeDefined();
expect(model.spec.members.length).toBe(0);
expect(model.spec.certificates).toEqual([]);
expect(model.spec.monitor).toBeDefined();
expect(model.certificatesError).toBe(false);
});
it('should initialize names', function() {
expect(model.spec.pool.name).toBe('Pool 1');
});
it('should initialize context properties', function() {
expect(model.context.resource).toBe('pool');
expect(model.context.id).toBeFalsy();
expect(model.context.submit).toBeDefined();
});
});
describe('Post initialize model (create health monitor)', function() { describe('Post initialize model (create health monitor)', function() {
beforeEach(function() { beforeEach(function() {

View File

@ -19,6 +19,12 @@
</li> </li>
</ul> </ul>
</p> </p>
<p>
<strong translate>Protocol:</strong>
<translate>
The protocol for which this pool and its members listen. A valid value is HTTP, HTTPS, PROXY, or TCP.
</translate>
</p>
<p> <p>
<strong translate>Session Persistence:</strong> <strong translate>Session Persistence:</strong>
<translate> <translate>

View File

@ -21,6 +21,40 @@
</div> </div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group required">
<label class="control-label" for="method">
<translate>Algorithm</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<select class="form-control" name="method" id="method"
ng-options="method for method in model.methods"
ng-model="model.spec.pool.method"
ng-required="true">
</select>
</div>
</div>
<div class="col-xs-12 col-sm-8 col-md-6" ng-if="model.context.id || (model.spec.parentResourceId === null)">
<div class="form-group required">
<label class="control-label" for="protocol">
<translate>Protocol</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<select class="form-control" name="protocol" id="protocol"
ng-model="model.spec.pool.protocol" ng-required="true"
ng-disabled="model.context.id">
<option ng-repeat="protocol in model.poolProtocols" value="{$ protocol $}">
{$ protocol $}
</option>
</select>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-8 col-md-6"> <div class="col-xs-12 col-sm-8 col-md-6">
@ -46,24 +80,6 @@
</div> </div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group required">
<label class="control-label" for="method">
<translate>Algorithm</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<select class="form-control" name="method" id="method"
ng-options="method for method in model.methods"
ng-model="model.spec.pool.method"
ng-required="true">
</select>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-8 col-md-6"> <div class="col-xs-12 col-sm-8 col-md-6">

View File

@ -0,0 +1,5 @@
---
features:
- |
Pools attached to a load balancer are now listed on the load balancer
details page.