Add 'provider' select box in load balancer form

Get the list of enabled providers, filter out aliases and display a
select box when creating a load balancer if there are 2 or more
available providers.

Change-Id: Id14b102b3d5ed079a3ef5d1cfba08744fd3cb5fa
This commit is contained in:
Gregory Thiemonge 2021-02-13 07:24:01 +01:00
parent 1a29983ebc
commit 1ff3f509e6
9 changed files with 223 additions and 7 deletions

View File

@ -141,6 +141,9 @@ def create_loadbalancer(request):
availability_zone = data['loadbalancer'].get('availability_zone')
if availability_zone:
build_kwargs['availability_zone'] = availability_zone
provider = data['loadbalancer'].get('provider')
if provider:
build_kwargs['provider'] = provider
loadbalancer = conn.load_balancer.create_load_balancer(**build_kwargs)
if data.get('listener'):
@ -1413,3 +1416,24 @@ class AvailabilityZones(generic.View):
)
return {'items': availability_zone_list}
@urls.register
class Providers(generic.View):
"""API for load balancer providers.
"""
url_regex = r'lbaas/providers/$'
@rest_utils.ajax()
def get(self, request):
"""List of providers.
The listing result is an object with property "items".
"""
conn = _get_sdk_connection(request)
provider_list = _sdk_object_to_list(
conn.load_balancer.providers()
)
return {'items': provider_list}

View File

@ -81,7 +81,8 @@
getFlavorProfile: getFlavorProfile,
deleteFlavorProfile: deleteFlavorProfile,
createFlavorProfile: createFlavorProfile,
editFlavorProfile: editFlavorProfile
editFlavorProfile: editFlavorProfile,
getProviders: getProviders
};
return service;
@ -915,5 +916,24 @@
});
}
// Providers
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getProviders
* @description
* Get the list of providers.
*
* The listing result is an object with property "items". Each item is
* a provider.
*/
function getProviders() {
var params = {params: {}};
return apiService.get('/api/lbaas/providers/', params)
.error(function () {
toastService.add('error', gettext('Unable to retrieve providers.'));
});
}
}
}());

View File

@ -337,6 +337,14 @@
testInput: [],
data: { params: {} }
},
{
func: 'getProviders',
method: 'get',
path: '/api/lbaas/providers/',
error: 'Unable to retrieve providers.',
testInput: [],
data: { params: {} }
},
{
func: 'createLoadBalancer',
method: 'post',

View File

@ -136,12 +136,35 @@
}
};
ctrl.providerColumns = [{
label: gettext('Provider'),
value: 'name'
}, {
label: gettext('Description'),
value: 'description'
}];
ctrl.providerOptions = [];
ctrl.providerShorthand = function(provider) {
return provider.name;
};
ctrl.setProvider = function(option) {
if (option) {
$scope.model.spec.loadbalancer.provider = option;
} else {
$scope.model.spec.loadbalancer.provider = null;
}
};
ctrl.dataLoaded = false;
ctrl._checkLoaded = function() {
if ($scope.model.initialized) {
ctrl.buildSubnetOptions();
ctrl.buildFlavorOptions();
ctrl.buildAvailabilityZoneOptions();
ctrl.buildProviderOptions();
ctrl.dataLoaded = true;
}
};
@ -169,6 +192,9 @@
$scope.$watchCollection('model.availability_zones', function() {
ctrl._checkLoaded();
});
$scope.$watchCollection('model.providers', function() {
ctrl._checkLoaded();
});
$scope.$watch('model.initialized', function() {
ctrl._checkLoaded();
});
@ -194,5 +220,14 @@
return $scope.model.availability_zones[key];
});
};
ctrl.buildProviderOptions = function() {
ctrl.providerOptions = Object.keys($scope.model.providers).filter(function(key) {
// filter out the 'octavia' provider, it's an alias for 'amphora' and
// it's deprecated
return key !== "octavia";
}).map(function(key) {
return $scope.model.providers[key];
});
};
}
})();

View File

@ -22,7 +22,8 @@
beforeEach(module('horizon.dashboard.project.lbaasv2'));
describe('LoadBalancerDetailsController', function() {
var ctrl, scope, mockSubnets, mockFlavors, mockAvailabilityZones;
var ctrl, scope, mockSubnets, mockFlavors, mockAvailabilityZones,
mockProviders;
beforeEach(inject(function($controller, $rootScope) {
mockSubnets = [{
id: '7262744a-e1e4-40d7-8833-18193e8de191',
@ -79,6 +80,14 @@
is_enabled: true
}];
mockProviders = [{
name: 'amphora',
description: 'amphora description'
}, {
name: 'octavia',
description: 'octavia description'
}];
scope = $rootScope.$new();
scope.model = {
networks: {
@ -104,6 +113,11 @@
is_enabled: true
}
},
providers: {
name: 'amphora',
description: 'amphora description'
},
subnets: [{}, {}],
spec: {
loadbalancer: {
@ -118,6 +132,7 @@
spyOn(ctrl, 'buildSubnetOptions').and.callThrough();
spyOn(ctrl, 'buildFlavorOptions').and.callThrough();
spyOn(ctrl, 'buildAvailabilityZoneOptions').and.callThrough();
spyOn(ctrl, 'buildProviderOptions').and.callThrough();
spyOn(ctrl, '_checkLoaded').and.callThrough();
}));
@ -168,6 +183,15 @@
);
});
it('should create provider shorthand text', function() {
expect(ctrl.providerShorthand(mockProviders[0])).toBe(
'amphora'
);
expect(ctrl.providerShorthand(mockProviders[1])).toBe(
'octavia'
);
});
it('should set subnet', function() {
ctrl.setSubnet(mockSubnets[0]);
expect(scope.model.spec.loadbalancer.vip_subnet_id).toBe(mockSubnets[0]);
@ -189,6 +213,13 @@
expect(scope.model.spec.loadbalancer.availability_zone).toBe(null);
});
it('should set provider', function() {
ctrl.setProvider(mockProviders[0]);
expect(scope.model.spec.loadbalancer.provider).toBe(mockProviders[0]);
ctrl.setProvider(null);
expect(scope.model.spec.loadbalancer.provider).toBe(null);
});
it('should initialize watchers', function() {
ctrl.$onInit();
@ -208,6 +239,10 @@
scope.$apply();
expect(ctrl._checkLoaded).toHaveBeenCalled();
scope.model.providers = {};
scope.$apply();
expect(ctrl._checkLoaded).toHaveBeenCalled();
scope.model.initialized = true;
scope.$apply();
@ -278,6 +313,13 @@
//expect(ctrl.buildSubnetOptions).toHaveBeenCalled();
});
it('should initialize provider watcher', function() {
ctrl.$onInit();
scope.model.providers = {};
scope.$apply();
});
it('should produce flavor column data', function() {
expect(ctrl.flavorColumns).toBeDefined();
@ -298,6 +340,13 @@
expect(ctrl.availabilityZoneColumns[0].value).toBe('name');
});
it('should produce provider column data', function() {
expect(ctrl.providerColumns).toBeDefined();
expect(ctrl.providerColumns[0].label).toBe('Provider');
expect(ctrl.providerColumns[0].value).toBe('name');
});
});
});
})();

View File

@ -35,6 +35,28 @@
</div>
</div>
<div class="row" ng-if="ctrl.providerOptions.length > 1">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<label class="control-label">
<translate>Provider</translate>
</label>
<!-- value="model.spec.loadbalancer.provider " -->
<filter-select
shorthand="ctrl.providerShorthand"
on-select="ctrl.setProvider(option)"
disabled="model.context.id"
columns="ctrl.providerColumns"
options="ctrl.providerOptions"
loaded="ctrl.dataLoaded"
ng-model="model.spec.loadbalancer.provider"
></filter-select>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">

View File

@ -104,6 +104,7 @@
{ label: gettext('Yes'), value: true },
{ label: gettext('No'), value: false }
],
providers: {},
/**
* api methods for UI controllers
@ -153,7 +154,8 @@
vip_subnet_id: null,
flavor_id: null,
availability_zone: null,
admin_state_up: true
admin_state_up: true,
provider: null
},
listener: {
id: null,
@ -270,6 +272,7 @@
return $q.all([
lbaasv2API.getFlavors().then(onGetFlavors),
lbaasv2API.getAvailabilityZones().then(onGetAvailabilityZones),
lbaasv2API.getProviders().then(onGetProviders),
neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getPorts().then(onGetPorts),
neutronAPI.getNetworks().then(onGetNetworks),
@ -296,6 +299,12 @@
});
}
function onGetProviders(response) {
angular.forEach(response.data.items, function(value) {
model.providers[value.name] = value;
});
}
function initCreateListener(keymanagerPromise) {
model.context.submit = createListener;
return $q.all([
@ -359,9 +368,10 @@
lbaasv2API.getFlavors().then(onGetFlavors),
lbaasv2API.getAvailabilityZones().then(onGetAvailabilityZones),
lbaasv2API.getLoadBalancer(model.context.id).then(onGetLoadBalancer),
lbaasv2API.getProviders().then(onGetProviders),
neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getNetworks().then(onGetNetworks)
]).then(initSubnet).then(initFlavor).then(initAvailabilityZone);
]).then(initSubnet).then(initFlavor).then(initAvailabilityZone).then(initProvider);
}
function initEditListener() {
@ -486,6 +496,10 @@
finalSpec.loadbalancer.availability_zone = finalSpec.loadbalancer.availability_zone.name;
}
if (angular.isObject(finalSpec.loadbalancer.provider)) {
finalSpec.loadbalancer.provider = finalSpec.loadbalancer.provider.name;
}
// Load balancer requires vip_subnet_id
if (!finalSpec.loadbalancer.vip_subnet_id) {
delete finalSpec.loadbalancer;
@ -497,6 +511,7 @@
if (context.resource === 'loadbalancer' && context.id) {
delete finalSpec.flavor_id;
delete finalSpec.availability_zone;
delete finalSpec.provider;
delete finalSpec.vip_subnet_id;
delete finalSpec.vip_address;
}
@ -785,6 +800,7 @@
spec.flavor_id = loadbalancer.flavor_id;
spec.availability_zone = loadbalancer.availability_zone;
spec.admin_state_up = loadbalancer.admin_state_up;
spec.provider = loadbalancer.provider;
}
function setListenerSpec(listener) {
@ -952,6 +968,11 @@
model.spec.loadbalancer.availability_zone];
}
function initProvider() {
model.spec.loadbalancer.provider = model.providers[
model.spec.loadbalancer.provider];
}
function mapSubnetObj(subnetId) {
var subnet = model.subnets.filter(function mapSubnet(subnet) {
return subnet.id === subnetId;

View File

@ -19,7 +19,7 @@
describe('LBaaS v2 Workflow Model Service', function() {
var model, $q, scope, listenerResources, barbicanEnabled,
certificatesError, mockNetworks, mockFlavors,
mockAvailabilityZones;
mockAvailabilityZones, mockProviders;
var includeChildResources = true;
beforeEach(module('horizon.framework.util.i18n'));
@ -116,6 +116,16 @@
id: 'az2'
}
};
mockProviders = {
amphora: {
name: 'amphora',
description: 'amphora description'
},
octavia: {
name: 'octavia',
description: 'octavia description'
}
};
});
beforeEach(module(function($provide) {
@ -141,7 +151,8 @@
vip_subnet_id: 'subnet-1',
flavor_id: 'flavor-1',
availability_zone: 'az-1',
description: ''
description: '',
provider: 'amphora'
};
var deferred = $q.defer();
@ -291,6 +302,19 @@
deferred.resolve({data: {items: availabilityZones}});
return deferred.promise;
},
getProviders: function() {
var providers = [{
name: 'amphora',
description: 'amphora description'
}, {
name: 'octavia',
description: 'octavia description'
}];
var deferred = $q.defer();
deferred.resolve({data: {items: providers}});
return deferred.promise;
},
createLoadBalancer: function(spec) {
return spec;
},
@ -543,6 +567,7 @@
expect(model.networks).toEqual(mockNetworks);
expect(model.flavors).toEqual(mockFlavors);
expect(model.availability_zones).toEqual(mockAvailabilityZones);
expect(model.providers).toEqual(mockProviders);
expect(model.members.length).toBe(2);
expect(model.certificates.length).toBe(3);
expect(model.listenerPorts.length).toBe(0);
@ -801,6 +826,7 @@
expect(model.networks).toEqual(mockNetworks);
expect(model.flavors).toEqual(mockFlavors);
expect(model.availability_zones).toEqual(mockAvailabilityZones);
expect(model.providers).toEqual(mockProviders);
expect(model.members.length).toBe(0);
expect(model.certificates.length).toBe(0);
expect(model.listenerPorts.length).toBe(0);
@ -1297,7 +1323,7 @@
// to implement tests for them.
it('has the right number of properties', function() {
expect(Object.keys(model.spec).length).toBe(11);
expect(Object.keys(model.spec.loadbalancer).length).toBe(7);
expect(Object.keys(model.spec.loadbalancer).length).toBe(8);
expect(Object.keys(model.spec.listener).length).toBe(15);
expect(Object.keys(model.spec.l7policy).length).toBe(8);
expect(Object.keys(model.spec.l7rule).length).toBe(7);
@ -1548,6 +1574,8 @@
model.spec.loadbalancer.flavor_id = model.flavors[Object.keys(model.flavors)[0]];
model.spec.loadbalancer.availability_zone = model.availability_zones[
Object.keys(model.availability_zones)[0]];
model.spec.loadbalancer.provider = model.providers[
Object.keys(model.providers)[0]];
model.spec.listener.protocol = 'TCP';
model.spec.listener.protocol_port = 80;
model.spec.listener.connection_limit = 999;
@ -1601,6 +1629,7 @@
expect(finalSpec.loadbalancer.vip_subnet_id).toBe(model.subnets[0].id);
expect(finalSpec.loadbalancer.admin_state_up).toBe(true);
expect(finalSpec.loadbalancer.availability_zone).toBe('az_1');
expect(finalSpec.loadbalancer.provider).toBe('amphora');
expect(finalSpec.listener.name).toBeUndefined();
expect(finalSpec.listener.description).toBeUndefined();
@ -1653,6 +1682,8 @@
model.spec.loadbalancer.flavor_id = model.flavors[Object.keys(model.flavors)[0]];
model.spec.loadbalancer.availability_zone = model.availability_zones[
Object.keys(model.availability_zones)[0]];
model.spec.loadbalancer.provider = model.providers[
Object.keys(model.providers)[0]];
model.spec.listener.protocol = 'TERMINATED_HTTPS';
model.spec.listener.protocol_port = 443;
model.spec.listener.connection_limit = 9999;

View File

@ -0,0 +1,6 @@
---
features:
- |
Add the possibility to select the provider for a new load balancer. A
select box displays the list of available providers, if only one provider is
enabled, the box is hidden.