Merge "Add load balancer flavor support"
This commit is contained in:
commit
9422dec8f0
@ -46,7 +46,7 @@ msgpack-python==0.4.0
|
||||
munch==2.1.0
|
||||
netaddr==0.7.18
|
||||
netifaces==0.10.4
|
||||
openstacksdk==0.11.2
|
||||
openstacksdk==0.24.0
|
||||
os-client-config==1.28.0
|
||||
os-service-types==1.2.0
|
||||
osc-lib==1.8.0
|
||||
|
@ -126,15 +126,28 @@ def poll_loadbalancer_status(request, loadbalancer_id, callback,
|
||||
def create_loadbalancer(request):
|
||||
data = request.DATA
|
||||
|
||||
flavor_id = data['loadbalancer'].get('flavor_id')
|
||||
|
||||
conn = _get_sdk_connection(request)
|
||||
loadbalancer = conn.load_balancer.create_load_balancer(
|
||||
project_id=request.user.project_id,
|
||||
vip_subnet_id=data['loadbalancer']['vip_subnet_id'],
|
||||
name=data['loadbalancer'].get('name'),
|
||||
description=data['loadbalancer'].get('description'),
|
||||
vip_address=data['loadbalancer'].get('vip_address'),
|
||||
admin_state_up=data['loadbalancer'].get('admin_state_up')
|
||||
)
|
||||
if flavor_id:
|
||||
loadbalancer = conn.load_balancer.create_load_balancer(
|
||||
project_id=request.user.project_id,
|
||||
vip_subnet_id=data['loadbalancer']['vip_subnet_id'],
|
||||
name=data['loadbalancer'].get('name'),
|
||||
description=data['loadbalancer'].get('description'),
|
||||
vip_address=data['loadbalancer'].get('vip_address'),
|
||||
admin_state_up=data['loadbalancer'].get('admin_state_up'),
|
||||
flavor_id=flavor_id
|
||||
)
|
||||
else:
|
||||
loadbalancer = conn.load_balancer.create_load_balancer(
|
||||
project_id=request.user.project_id,
|
||||
vip_subnet_id=data['loadbalancer']['vip_subnet_id'],
|
||||
name=data['loadbalancer'].get('name'),
|
||||
description=data['loadbalancer'].get('description'),
|
||||
vip_address=data['loadbalancer'].get('vip_address'),
|
||||
admin_state_up=data['loadbalancer'].get('admin_state_up'),
|
||||
)
|
||||
|
||||
if data.get('listener'):
|
||||
# There is work underway to add a new API to LBaaS v2 that will
|
||||
@ -284,6 +297,39 @@ def create_health_monitor(request, **kwargs):
|
||||
return _get_sdk_object_dict(health_mon)
|
||||
|
||||
|
||||
def create_flavor(request, **kwargs):
|
||||
"""Create a new flavor.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor = conn.load_balancer.create_flavor(
|
||||
name=data['flavor']['name'],
|
||||
flavor_profile_id=data['flavor']['flavor_profile_id'],
|
||||
description=data['flavor'].get('description'),
|
||||
enabled=data['flavor'].get('enabled'),
|
||||
)
|
||||
|
||||
return _get_sdk_object_dict(flavor)
|
||||
|
||||
|
||||
def create_flavor_profile(request, **kwargs):
|
||||
"""Create a new flavor profile.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor_profile = conn.load_balancer.create_flavor(
|
||||
name=data['flavor_profile']['name'],
|
||||
provider_name=data['flavor_profile']['provider_name'],
|
||||
flavor_data=data['flavor_profile']['flavor_data'],
|
||||
)
|
||||
|
||||
return _get_sdk_object_dict(flavor_profile)
|
||||
|
||||
|
||||
def add_member(request, **kwargs):
|
||||
"""Add a member to a pool.
|
||||
|
||||
@ -534,6 +580,42 @@ def update_monitor(request, **kwargs):
|
||||
return _get_sdk_object_dict(healthmonitor)
|
||||
|
||||
|
||||
def update_flavor(request, **kwargs):
|
||||
"""Update a flavor.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
flavor_id = data['flavor']['id']
|
||||
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor = conn.load_balancer.update_flavor(
|
||||
flavor_id,
|
||||
name=data['flavor'].get('name'),
|
||||
description=data['flavor'].get('description'),
|
||||
enabled=data['flavor'].get('enabled'),
|
||||
)
|
||||
|
||||
return _get_sdk_object_dict(flavor)
|
||||
|
||||
|
||||
def update_flavor_profile(request, **kwargs):
|
||||
"""Update a flavor profile.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
flavor_profile_id = data['flavor_profile']['id']
|
||||
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor_profile = conn.load_balancer.update_flavor_profile(
|
||||
flavor_profile_id,
|
||||
name=data['flavor_profile'].get('name'),
|
||||
provider_name=data['flavor_profile'].get('provider_name'),
|
||||
flavor_data=data['flavor_profile'].get('flavor_data'),
|
||||
)
|
||||
|
||||
return _get_sdk_object_dict(flavor_profile)
|
||||
|
||||
|
||||
def update_member_list(request, **kwargs):
|
||||
"""Update the list of members by adding or removing the necessary members.
|
||||
|
||||
@ -1183,3 +1265,134 @@ class HealthMonitor(generic.View):
|
||||
|
||||
"""
|
||||
update_monitor(request)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Flavors(generic.View):
|
||||
"""API for load balancer flavors.
|
||||
|
||||
"""
|
||||
url_regex = r'lbaas/flavors/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""List of flavors for the current project.
|
||||
|
||||
The listing result is an object with property "items".
|
||||
"""
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor_list = _sdk_object_to_list(
|
||||
conn.load_balancer.flavors()
|
||||
)
|
||||
|
||||
return {'items': flavor_list}
|
||||
|
||||
@rest_utils.ajax()
|
||||
def post(self, request):
|
||||
"""Create a new flavor.
|
||||
|
||||
"""
|
||||
kwargs = {
|
||||
'flavor': request.DATA.get('flavor')
|
||||
}
|
||||
return create_flavor(request, **kwargs)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Flavor(generic.View):
|
||||
"""API for retrieving a single flavor.
|
||||
|
||||
"""
|
||||
url_regex = r'lbaas/flavors/(?P<flavor_id>[^/]+)/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, flavor_id):
|
||||
"""Get a specific flavor.
|
||||
|
||||
"""
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor = conn.load_balancer.find_flavor(flavor_id)
|
||||
return _get_sdk_object_dict(flavor)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def delete(self, request, flavor_id):
|
||||
"""Delete a specific flavor.
|
||||
|
||||
http://localhost/api/lbaas/flavors/3971d368-ca9b-4770-929a-3adca5bf89eb
|
||||
"""
|
||||
conn = _get_sdk_connection(request)
|
||||
conn.load_balancer.delete_flavor(flavor_id,
|
||||
ignore_missing=True)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def put(self, request, flavor_id):
|
||||
"""Edit a flavor.
|
||||
|
||||
"""
|
||||
update_flavor(request)
|
||||
|
||||
|
||||
@urls.register
|
||||
class FlavorProfiles(generic.View):
|
||||
"""API for load balancer flavor profiles.
|
||||
|
||||
"""
|
||||
url_regex = r'lbaas/flavorprofiles/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""List of flavor profiles for the current project.
|
||||
|
||||
The listing result is an object with property "items".
|
||||
"""
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor_profile_list = _sdk_object_to_list(
|
||||
conn.load_balancer.flavor_profiles()
|
||||
)
|
||||
|
||||
return {'items': flavor_profile_list}
|
||||
|
||||
@rest_utils.ajax()
|
||||
def post(self, request):
|
||||
"""Create a new flavor_profile.
|
||||
|
||||
"""
|
||||
kwargs = {
|
||||
'flavor_profile': request.DATA.get('flavor_profile')
|
||||
}
|
||||
return create_flavor_profile(request, **kwargs)
|
||||
|
||||
|
||||
@urls.register
|
||||
class FlavorProfile(generic.View):
|
||||
"""API for retrieving a single flavor profile.
|
||||
|
||||
"""
|
||||
url_regex = r'lbaas/flavorprofiles/(?P<flavor_profile_id>[^/]+)/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, flavor_profile_id):
|
||||
"""Get a specific flavor profile.
|
||||
|
||||
"""
|
||||
conn = _get_sdk_connection(request)
|
||||
flavor_profile = conn.load_balancer.find_flavor_profile(
|
||||
flavor_profile_id)
|
||||
return _get_sdk_object_dict(flavor_profile)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def delete(self, request, flavor_profile_id):
|
||||
"""Delete a specific flavor profile.
|
||||
|
||||
http://localhost/api/lbaas/flavorprofiles/e8150eab-aefa-42cc-867e-3fb336da52bd
|
||||
"""
|
||||
conn = _get_sdk_connection(request)
|
||||
conn.load_balancer.delete_flavor_profile(flavor_profile_id,
|
||||
ignore_missing=True)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def put(self, request, flavor_profile_id):
|
||||
"""Edit a flavor profile.
|
||||
|
||||
"""
|
||||
update_flavor_profile(request)
|
||||
|
@ -70,7 +70,17 @@
|
||||
deleteHealthMonitor: deleteHealthMonitor,
|
||||
createHealthMonitor: createHealthMonitor,
|
||||
editHealthMonitor: editHealthMonitor,
|
||||
updateMemberList: updateMemberList
|
||||
updateMemberList: updateMemberList,
|
||||
getFlavors: getFlavors,
|
||||
getFlavor: getFlavor,
|
||||
deleteFlavor: deleteFlavor,
|
||||
createFlavor: createFlavor,
|
||||
editFlavor: editFlavor,
|
||||
getFlavorProfiles: getFlavorProfiles,
|
||||
getFlavorProfile: getFlavorProfile,
|
||||
deleteFlavorProfile: deleteFlavorProfile,
|
||||
createFlavorProfile: createFlavorProfile,
|
||||
editFlavorProfile: editFlavorProfile
|
||||
};
|
||||
|
||||
return service;
|
||||
@ -721,5 +731,169 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Flavors
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.getFlavor
|
||||
* @description
|
||||
* Get a single load balancer flavor by ID.
|
||||
* @param {string} flavorId
|
||||
* Specifies the id of the flavor.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.getFlavors
|
||||
* @description
|
||||
* Get the list of flavors.
|
||||
*
|
||||
* The listing result is an object with property "items". Each item is
|
||||
* a flavor.
|
||||
*/
|
||||
|
||||
function getFlavors() {
|
||||
var params = {params: {}};
|
||||
return apiService.get('/api/lbaas/flavors/', params)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve flavors.'));
|
||||
});
|
||||
}
|
||||
|
||||
function getFlavor(flavorId) {
|
||||
return apiService.get('/api/lbaas/flavors/' + flavorId + '/')
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve flavor.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.editFlavor
|
||||
* @description
|
||||
* Edit a flavor
|
||||
* @param {string} id
|
||||
* Specifies the id of the flavor to update.
|
||||
* @param {object} spec
|
||||
* Specifies the data used to update the flavor.
|
||||
*/
|
||||
|
||||
function editFlavor(id, spec) {
|
||||
return apiService.put('/api/lbaas/flavors/' + id + '/', spec)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to update flavor.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.deleteFlavor
|
||||
* @description
|
||||
* Delete a single flavor by ID
|
||||
* @param {string} id
|
||||
* Specifies the id of the flavor to delete.
|
||||
* @param {boolean} quiet
|
||||
*/
|
||||
|
||||
function deleteFlavor(id, quiet) {
|
||||
var promise = apiService.delete('/api/lbaas/flavors/' + id + '/');
|
||||
return quiet ? promise : promise.error(function () {
|
||||
toastService.add('error', gettext('Unable to delete flavor.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.createFlavor
|
||||
* @description
|
||||
* Create a new flavor
|
||||
* @param {object} spec
|
||||
* Specifies the data used to create the new flavor.
|
||||
*/
|
||||
|
||||
function createFlavor(spec) {
|
||||
return apiService.post('/api/lbaas/flavors/', spec)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to create flavor.'));
|
||||
});
|
||||
}
|
||||
|
||||
// Flavor Profiles
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.getFlavorProfile
|
||||
* @description
|
||||
* Get a single load balancer flavor profile by ID.
|
||||
* @param {string} flavorProfileId
|
||||
* Specifies the id of the flavor profile.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.getFlavorProfiles
|
||||
* @description
|
||||
* Get the list of flavor profiles.
|
||||
*
|
||||
* The listing result is an object with property "items". Each item is
|
||||
* a flavor profile.
|
||||
*/
|
||||
|
||||
function getFlavorProfiles() {
|
||||
var params = {params: {}};
|
||||
return apiService.get('/api/lbaas/flavorprofiles/', params)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve flavor profiles.'));
|
||||
});
|
||||
}
|
||||
|
||||
function getFlavorProfile(flavorProfileId) {
|
||||
return apiService.get('/api/lbaas/flavorprofiles/' + flavorProfileId + '/')
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve flavor profile.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.editFlavorProfile
|
||||
* @description
|
||||
* Edit a flavor Profile
|
||||
* @param {string} id
|
||||
* Specifies the id of the flavor profile to update.
|
||||
* @param {object} spec
|
||||
* Specifies the data used to update the flavor profile.
|
||||
*/
|
||||
|
||||
function editFlavorProfile(id, spec) {
|
||||
return apiService.put('/api/lbaas/flavorprofiles/' + id + '/', spec)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to update flavor profile.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.deleteFlavorProfile
|
||||
* @description
|
||||
* Delete a single flavor profile by ID
|
||||
* @param {string} id
|
||||
* Specifies the id of the flavor profile to delete.
|
||||
* @param {boolean} quiet
|
||||
*/
|
||||
|
||||
function deleteFlavorProfile(id, quiet) {
|
||||
var promise = apiService.delete('/api/lbaas/flavorprofiles/' + id + '/');
|
||||
return quiet ? promise : promise.error(function () {
|
||||
toastService.add('error', gettext('Unable to delete flavor profile.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.createFlavorProfile
|
||||
* @description
|
||||
* Create a new flavor profile
|
||||
* @param {object} spec
|
||||
* Specifies the data used to create the new flavor profile.
|
||||
*/
|
||||
|
||||
function createFlavorProfile(spec) {
|
||||
return apiService.post('/api/lbaas/flavorprofiles/', spec)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to create flavor profile.'));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}());
|
||||
|
@ -269,6 +269,66 @@
|
||||
error: 'Unable to delete health monitor.',
|
||||
testInput: [ '1234' ]
|
||||
},
|
||||
{
|
||||
func: 'getFlavors',
|
||||
method: 'get',
|
||||
path: '/api/lbaas/flavors/',
|
||||
error: 'Unable to retrieve flavors.',
|
||||
testInput: [],
|
||||
data: { params: {} }
|
||||
},
|
||||
{
|
||||
func: 'getFlavor',
|
||||
method: 'get',
|
||||
path: '/api/lbaas/flavors/1234/',
|
||||
error: 'Unable to retrieve flavor.',
|
||||
testInput: [ '1234' ]
|
||||
},
|
||||
{
|
||||
func: 'editFlavor',
|
||||
method: 'put',
|
||||
path: '/api/lbaas/flavors/1234/',
|
||||
error: 'Unable to update flavor.',
|
||||
data: { name: 'flavor-1' },
|
||||
testInput: [ '1234', { name: 'flavor-1' } ]
|
||||
},
|
||||
{
|
||||
func: 'deleteFlavor',
|
||||
method: 'delete',
|
||||
path: '/api/lbaas/flavors/1234/',
|
||||
error: 'Unable to delete flavor.',
|
||||
testInput: [ '1234' ]
|
||||
},
|
||||
{
|
||||
func: 'getFlavorProfiles',
|
||||
method: 'get',
|
||||
path: '/api/lbaas/flavorprofiles/',
|
||||
error: 'Unable to retrieve flavor profiles.',
|
||||
testInput: [],
|
||||
data: { params: {} }
|
||||
},
|
||||
{
|
||||
func: 'getFlavorProfile',
|
||||
method: 'get',
|
||||
path: '/api/lbaas/flavorprofiles/1234/',
|
||||
error: 'Unable to retrieve flavor profile.',
|
||||
testInput: [ '1234' ]
|
||||
},
|
||||
{
|
||||
func: 'editFlavorProfile',
|
||||
method: 'put',
|
||||
path: '/api/lbaas/flavorprofiles/1234/',
|
||||
error: 'Unable to update flavor profile.',
|
||||
data: { name: 'flavorprofile-1' },
|
||||
testInput: [ '1234', { name: 'flavorprofile-1' } ]
|
||||
},
|
||||
{
|
||||
func: 'deleteFlavorProfile',
|
||||
method: 'delete',
|
||||
path: '/api/lbaas/flavorprofiles/1234/',
|
||||
error: 'Unable to delete flavor profile.',
|
||||
testInput: [ '1234' ]
|
||||
},
|
||||
{
|
||||
func: 'createLoadBalancer',
|
||||
method: 'post',
|
||||
@ -364,6 +424,22 @@
|
||||
data: { name: 'healthmonitor-1' },
|
||||
testInput: [ { name: 'healthmonitor-1' } ]
|
||||
},
|
||||
{
|
||||
func: 'createFlavor',
|
||||
method: 'post',
|
||||
path: '/api/lbaas/flavors/',
|
||||
error: 'Unable to create flavor.',
|
||||
data: { name: 'flavor-1' },
|
||||
testInput: [ { name: 'flavor-1' } ]
|
||||
},
|
||||
{
|
||||
func: 'createFlavorProfile',
|
||||
method: 'post',
|
||||
path: '/api/lbaas/flavorprofiles/',
|
||||
error: 'Unable to create flavor profile.',
|
||||
data: { name: 'flavorprofile-1' },
|
||||
testInput: [ { name: 'flavorprofile-1' } ]
|
||||
},
|
||||
{
|
||||
func: 'updateMemberList',
|
||||
method: 'put',
|
||||
@ -412,6 +488,16 @@
|
||||
expect(service.deleteHealthMonitor("whatever", true)).toBe("promise");
|
||||
});
|
||||
|
||||
it('supresses the error if instructed for deleteFlavor', function() {
|
||||
spyOn(apiService, 'delete').and.returnValue("promise");
|
||||
expect(service.deleteFlavor("whatever", true)).toBe("promise");
|
||||
});
|
||||
|
||||
it('supresses the error if instructed for deleteFlavorProfile', function() {
|
||||
spyOn(apiService, 'delete').and.returnValue("promise");
|
||||
expect(service.deleteFlavorProfile("whatever", true)).toBe("promise");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
|
@ -46,7 +46,7 @@
|
||||
item="ctrl.loadbalancer"
|
||||
property-groups="[[
|
||||
'id', 'name', 'description', 'project_id', 'created_at', 'updated_at',
|
||||
'vip_port_id', 'vip_subnet_id', 'vip_network_id', 'provider', 'flavor',
|
||||
'vip_port_id', 'vip_subnet_id', 'vip_network_id', 'provider', 'flavor_id',
|
||||
'floating_ip_address']]">
|
||||
</hz-resource-property-list>
|
||||
</div>
|
||||
|
@ -5,5 +5,5 @@
|
||||
['name', 'id', 'project_id'],
|
||||
['created_at', 'updated_at', 'description'],
|
||||
['vip_network_id', 'vip_subnet_id', 'vip_port_id'],
|
||||
['provider', 'floating_ip_address']]">
|
||||
['flavor_id', 'provider', 'floating_ip_address']]">
|
||||
</hz-resource-property-list>
|
||||
|
@ -178,8 +178,8 @@
|
||||
listeners: gettext('Listeners'),
|
||||
pools: gettext('Pools'),
|
||||
provider: gettext('Provider'),
|
||||
flavor: {
|
||||
label: gettext('Flavor'),
|
||||
flavor_id: {
|
||||
label: gettext('Flavor ID'),
|
||||
filters: ['noValue']
|
||||
},
|
||||
floating_ip_address: {
|
||||
|
@ -87,10 +87,52 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Defines columns for the flavor selection filtered pop-up
|
||||
ctrl.flavorColumns = [{
|
||||
label: gettext('Flavor'),
|
||||
value: 'name'
|
||||
}, {
|
||||
label: gettext('Flavor ID'),
|
||||
value: 'id'
|
||||
}, {
|
||||
label: gettext('Flavor Description'),
|
||||
value: 'description'
|
||||
}, {
|
||||
label: gettext('Provider'),
|
||||
value: function(flavor) {
|
||||
var flavorProfile = $scope.model.flavorProfiles[flavor.flavor_profile_id];
|
||||
return flavorProfile ? flavorProfile.provider_name : '';
|
||||
}
|
||||
}];
|
||||
|
||||
ctrl.flavorOptions = [];
|
||||
|
||||
ctrl.flavorShorthand = function(flavor) {
|
||||
var flavorProfile = $scope.model.flavorProfiles[flavor.flavor_profile_id];
|
||||
|
||||
var providerText =
|
||||
flavorProfile
|
||||
? flavorProfile.provider_name
|
||||
: '';
|
||||
var flavorText = flavor.name || flavor.id.substring(0, 10) + '...';
|
||||
var flavorDescription = flavor.description || '';
|
||||
|
||||
return flavorText + ' (' + providerText + '): ' + flavorDescription;
|
||||
};
|
||||
|
||||
ctrl.setFlavor = function(option) {
|
||||
if (option) {
|
||||
$scope.model.spec.loadbalancer.flavor_id = option;
|
||||
} else {
|
||||
$scope.model.spec.loadbalancer.flavor_id = null;
|
||||
}
|
||||
};
|
||||
|
||||
ctrl.dataLoaded = false;
|
||||
ctrl._checkLoaded = function() {
|
||||
if ($scope.model.initialized) {
|
||||
ctrl.buildSubnetOptions();
|
||||
ctrl.buildFlavorOptions();
|
||||
ctrl.dataLoaded = true;
|
||||
}
|
||||
};
|
||||
@ -112,6 +154,12 @@
|
||||
$scope.$watchCollection('model.networks', function() {
|
||||
ctrl._checkLoaded();
|
||||
});
|
||||
$scope.$watchCollection('model.flavors', function() {
|
||||
ctrl._checkLoaded();
|
||||
});
|
||||
$scope.$watchCollection('model.flavorProfiles', function() {
|
||||
ctrl._checkLoaded();
|
||||
});
|
||||
$scope.$watch('model.initialized', function() {
|
||||
ctrl._checkLoaded();
|
||||
});
|
||||
@ -121,5 +169,13 @@
|
||||
// Subnets are sliced to maintain data immutability
|
||||
ctrl.subnetOptions = $scope.model.subnets.slice(0);
|
||||
};
|
||||
|
||||
ctrl.buildFlavorOptions = function() {
|
||||
ctrl.flavorOptions = Object.keys($scope.model.flavors).filter(function(key) {
|
||||
return $scope.model.flavors[key].is_enabled;
|
||||
}).map(function(key) {
|
||||
return $scope.model.flavors[key];
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
@ -22,7 +22,7 @@
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
describe('LoadBalancerDetailsController', function() {
|
||||
var ctrl, scope, mockSubnets;
|
||||
var ctrl, scope, mockSubnets, mockFlavors;
|
||||
beforeEach(inject(function($controller, $rootScope) {
|
||||
mockSubnets = [{
|
||||
id: '7262744a-e1e4-40d7-8833-18193e8de191',
|
||||
@ -41,6 +41,24 @@
|
||||
cidr: '2.2.2.2/16'
|
||||
}];
|
||||
|
||||
mockFlavors = [{
|
||||
id: '15d990e1-3438-4073-89b8-6e4706f0b176',
|
||||
flavor_profile_id: '79e16118-daba-4255-9d1d-9cc7812e18a1',
|
||||
name: 'flavor_1',
|
||||
description: 'Flavor 1 description',
|
||||
is_enabled: true
|
||||
}, {
|
||||
id: 'b0703ed4-dd30-4dbe-92bb-dccc945365e9',
|
||||
flavor_profile_id: 'ace487e6-9946-4bdc-882e-c889af43fc3b',
|
||||
name: 'flavor_2',
|
||||
is_enabled: true
|
||||
}, {
|
||||
id: '94306089-567a-44ed-ab16-87653adbece3',
|
||||
flavor_profile_id: 'b272d5fb-0021-4002-beb5-db758e59a763',
|
||||
name: '',
|
||||
is_enabled: true
|
||||
}];
|
||||
|
||||
scope = $rootScope.$new();
|
||||
scope.model = {
|
||||
networks: {
|
||||
@ -49,6 +67,21 @@
|
||||
name: 'network_1'
|
||||
}
|
||||
},
|
||||
flavors: {
|
||||
'15d990e1-3438-4073-89b8-6e4706f0b176': {
|
||||
id: '15d990e1-3438-4073-89b8-6e4706f0b176',
|
||||
name: 'flavor_1',
|
||||
flavor_profile_id: '79e16118-daba-4255-9d1d-9cc7812e18a1',
|
||||
is_enabled: true
|
||||
}
|
||||
},
|
||||
flavorProfiles: {
|
||||
'79e16118-daba-4255-9d1d-9cc7812e18a1': {
|
||||
id: '79e16118-daba-4255-9d1d-9cc7812e18a1',
|
||||
name: 'flavor_profile_1',
|
||||
provider_name: 'amphora'
|
||||
}
|
||||
},
|
||||
subnets: [{}, {}],
|
||||
spec: {
|
||||
loadbalancer: {
|
||||
@ -61,6 +94,7 @@
|
||||
ctrl = $controller('LoadBalancerDetailsController', {$scope: scope});
|
||||
|
||||
spyOn(ctrl, 'buildSubnetOptions').and.callThrough();
|
||||
spyOn(ctrl, 'buildFlavorOptions').and.callThrough();
|
||||
spyOn(ctrl, '_checkLoaded').and.callThrough();
|
||||
}));
|
||||
|
||||
@ -87,6 +121,18 @@
|
||||
);
|
||||
});
|
||||
|
||||
it('should create flavor shorthand text', function() {
|
||||
expect(ctrl.flavorShorthand(mockFlavors[0])).toBe(
|
||||
'flavor_1 (amphora): Flavor 1 description'
|
||||
);
|
||||
expect(ctrl.flavorShorthand(mockFlavors[1])).toBe(
|
||||
'flavor_2 (): '
|
||||
);
|
||||
expect(ctrl.flavorShorthand(mockFlavors[2])).toBe(
|
||||
'94306089-5... (): '
|
||||
);
|
||||
});
|
||||
|
||||
it('should set subnet', function() {
|
||||
ctrl.setSubnet(mockSubnets[0]);
|
||||
expect(scope.model.spec.loadbalancer.vip_subnet_id).toBe(mockSubnets[0]);
|
||||
@ -94,6 +140,13 @@
|
||||
expect(scope.model.spec.loadbalancer.vip_subnet_id).toBe(null);
|
||||
});
|
||||
|
||||
it('should set flavor', function() {
|
||||
ctrl.setFlavor(mockFlavors[0]);
|
||||
expect(scope.model.spec.loadbalancer.flavor_id).toBe(mockFlavors[0]);
|
||||
ctrl.setFlavor(null);
|
||||
expect(scope.model.spec.loadbalancer.flavor_id).toBe(null);
|
||||
});
|
||||
|
||||
it('should initialize watchers', function() {
|
||||
ctrl.$onInit();
|
||||
|
||||
@ -105,6 +158,14 @@
|
||||
scope.$apply();
|
||||
expect(ctrl._checkLoaded).toHaveBeenCalled();
|
||||
|
||||
scope.model.flavors = {};
|
||||
scope.$apply();
|
||||
expect(ctrl._checkLoaded).toHaveBeenCalled();
|
||||
|
||||
scope.model.flavorProfiles = {};
|
||||
scope.$apply();
|
||||
expect(ctrl._checkLoaded).toHaveBeenCalled();
|
||||
|
||||
scope.model.initialized = true;
|
||||
|
||||
scope.$apply();
|
||||
@ -158,6 +219,34 @@
|
||||
expect(ctrl.buildSubnetOptions).toHaveBeenCalled();
|
||||
expect(ctrl.dataLoaded).toBe(true);
|
||||
});
|
||||
|
||||
it('should initialize flavors watcher', function() {
|
||||
ctrl.$onInit();
|
||||
|
||||
scope.model.flavors = {};
|
||||
scope.$apply();
|
||||
//expect(ctrl.buildSubnetOptions).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should produce flavor column data', function() {
|
||||
expect(ctrl.flavorColumns).toBeDefined();
|
||||
|
||||
expect(ctrl.flavorColumns[0].label).toBe('Flavor');
|
||||
expect(ctrl.flavorColumns[0].value).toBe('name');
|
||||
|
||||
expect(ctrl.flavorColumns[1].label).toBe('Flavor ID');
|
||||
expect(ctrl.flavorColumns[1].value).toBe('id');
|
||||
|
||||
expect(ctrl.flavorColumns[2].label).toBe('Flavor Description');
|
||||
expect(ctrl.flavorColumns[2].value).toBe('description');
|
||||
|
||||
expect(ctrl.flavorColumns[3].label).toBe('Provider');
|
||||
var flavorLabel3 = ctrl.flavorColumns[3].value(mockFlavors[0]);
|
||||
expect(flavorLabel3).toBe('amphora');
|
||||
flavorLabel3 = ctrl.flavorColumns[3].value(mockFlavors[1]);
|
||||
expect(flavorLabel3).toBe('');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -35,6 +35,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="control-label">
|
||||
<translate>Flavor</translate>
|
||||
</label>
|
||||
<!-- value="model.spec.loadbalancer.flavor_id" -->
|
||||
<filter-select
|
||||
shorthand="ctrl.flavorShorthand"
|
||||
|
||||
on-select="ctrl.setFlavor(option)"
|
||||
disabled="model.context.id"
|
||||
columns="ctrl.flavorColumns"
|
||||
options="ctrl.flavorOptions"
|
||||
loaded="ctrl.dataLoaded"
|
||||
|
||||
ng-model="model.spec.loadbalancer.flavor_id"
|
||||
></filter-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
|
@ -85,6 +85,8 @@
|
||||
subnets: [],
|
||||
members: [],
|
||||
networks: {},
|
||||
flavors: {},
|
||||
flavorProfiles: {},
|
||||
listenerProtocols: ['HTTP', 'TCP', 'TERMINATED_HTTPS', 'HTTPS'],
|
||||
l7policyActions: ['REJECT', 'REDIRECT_TO_URL', 'REDIRECT_TO_POOL'],
|
||||
l7ruleTypes: ['HOST_NAME', 'PATH', 'FILE_TYPE', 'HEADER', 'COOKIE'],
|
||||
@ -149,6 +151,7 @@
|
||||
description: null,
|
||||
vip_address: null,
|
||||
vip_subnet_id: null,
|
||||
flavor_id: null,
|
||||
admin_state_up: true
|
||||
},
|
||||
listener: {
|
||||
@ -263,6 +266,8 @@
|
||||
function initCreateLoadBalancer(keymanagerPromise) {
|
||||
model.context.submit = createLoadBalancer;
|
||||
return $q.all([
|
||||
lbaasv2API.getFlavors().then(onGetFlavors),
|
||||
lbaasv2API.getFlavorProfiles().then(onGetFlavorProfiles),
|
||||
neutronAPI.getSubnets().then(onGetSubnets),
|
||||
neutronAPI.getPorts().then(onGetPorts),
|
||||
neutronAPI.getNetworks().then(onGetNetworks),
|
||||
@ -277,6 +282,18 @@
|
||||
});
|
||||
}
|
||||
|
||||
function onGetFlavors(response) {
|
||||
angular.forEach(response.data.items, function(value) {
|
||||
model.flavors[value.id] = value;
|
||||
});
|
||||
}
|
||||
|
||||
function onGetFlavorProfiles(response) {
|
||||
angular.forEach(response.data.items, function(value) {
|
||||
model.flavorProfiles[value.id] = value;
|
||||
});
|
||||
}
|
||||
|
||||
function initCreateListener(keymanagerPromise) {
|
||||
model.context.submit = createListener;
|
||||
return $q.all([
|
||||
@ -337,10 +354,12 @@
|
||||
function initEditLoadBalancer() {
|
||||
model.context.submit = editLoadBalancer;
|
||||
return $q.all([
|
||||
lbaasv2API.getFlavors().then(onGetFlavors),
|
||||
lbaasv2API.getFlavorProfiles().then(onGetFlavorProfiles),
|
||||
lbaasv2API.getLoadBalancer(model.context.id).then(onGetLoadBalancer),
|
||||
neutronAPI.getSubnets().then(onGetSubnets),
|
||||
neutronAPI.getNetworks().then(onGetNetworks)
|
||||
]).then(initSubnet);
|
||||
]).then(initSubnet).then(initFlavor);
|
||||
}
|
||||
|
||||
function initEditListener() {
|
||||
@ -457,6 +476,10 @@
|
||||
function cleanFinalSpecLoadBalancer(finalSpec) {
|
||||
var context = model.context;
|
||||
|
||||
if (angular.isObject(finalSpec.loadbalancer.flavor_id)) {
|
||||
finalSpec.loadbalancer.flavor_id = finalSpec.loadbalancer.flavor_id.id;
|
||||
}
|
||||
|
||||
// Load balancer requires vip_subnet_id
|
||||
if (!finalSpec.loadbalancer.vip_subnet_id) {
|
||||
delete finalSpec.loadbalancer;
|
||||
@ -466,6 +489,7 @@
|
||||
|
||||
// Cannot edit the IP or subnet
|
||||
if (context.resource === 'loadbalancer' && context.id) {
|
||||
delete finalSpec.flavor_id;
|
||||
delete finalSpec.vip_subnet_id;
|
||||
delete finalSpec.vip_address;
|
||||
}
|
||||
@ -751,6 +775,7 @@
|
||||
spec.description = loadbalancer.description;
|
||||
spec.vip_address = loadbalancer.vip_address;
|
||||
spec.vip_subnet_id = loadbalancer.vip_subnet_id;
|
||||
spec.flavor_id = loadbalancer.flavor_id;
|
||||
spec.admin_state_up = loadbalancer.admin_state_up;
|
||||
}
|
||||
|
||||
@ -892,6 +917,10 @@
|
||||
model.spec.loadbalancer.vip_subnet_id = subnet;
|
||||
}
|
||||
|
||||
function initFlavor() {
|
||||
model.spec.loadbalancer.flavor_id = model.flavors[model.spec.loadbalancer.flavor_id];
|
||||
}
|
||||
|
||||
function mapSubnetObj(subnetId) {
|
||||
var subnet = model.subnets.filter(function mapSubnet(subnet) {
|
||||
return subnet.id === subnetId;
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
describe('LBaaS v2 Workflow Model Service', function() {
|
||||
var model, $q, scope, listenerResources, barbicanEnabled,
|
||||
certificatesError, mockNetworks;
|
||||
certificatesError, mockNetworks, mockFlavors, mockFlavorProfiles;
|
||||
var includeChildResources = true;
|
||||
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
@ -95,6 +95,26 @@
|
||||
id: 'b2'
|
||||
}
|
||||
};
|
||||
mockFlavors = {
|
||||
f1: {
|
||||
name: 'flavor_1',
|
||||
id: 'f1'
|
||||
},
|
||||
f2: {
|
||||
name: 'flavor_2',
|
||||
id: 'f2'
|
||||
}
|
||||
};
|
||||
mockFlavorProfiles = {
|
||||
p1: {
|
||||
name: 'flavor_profile_1',
|
||||
id: 'p1'
|
||||
},
|
||||
p2: {
|
||||
name: 'flavor_profile_2',
|
||||
id: 'p2'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
@ -118,6 +138,7 @@
|
||||
name: 'Load Balancer 1',
|
||||
vip_address: '1.2.3.4',
|
||||
vip_subnet_id: 'subnet-1',
|
||||
flavor_id: 'flavor-1',
|
||||
description: ''
|
||||
};
|
||||
|
||||
@ -242,6 +263,32 @@
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
getFlavors: function() {
|
||||
var flavors = [{
|
||||
name: 'flavor_1',
|
||||
id: 'f1'
|
||||
}, {
|
||||
name: 'flavor_2',
|
||||
id: 'f2'
|
||||
}];
|
||||
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({data: {items: flavors}});
|
||||
return deferred.promise;
|
||||
},
|
||||
getFlavorProfiles: function() {
|
||||
var flavorProfiles = [{
|
||||
name: 'flavor_profile_1',
|
||||
id: 'p1'
|
||||
}, {
|
||||
name: 'flavor_profile_2',
|
||||
id: 'p2'
|
||||
}];
|
||||
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({data: {items: flavorProfiles}});
|
||||
return deferred.promise;
|
||||
},
|
||||
createLoadBalancer: function(spec) {
|
||||
return spec;
|
||||
},
|
||||
@ -486,6 +533,8 @@
|
||||
expect(model.initialized).toBe(true);
|
||||
expect(model.subnets.length).toBe(2);
|
||||
expect(model.networks).toEqual(mockNetworks);
|
||||
expect(model.flavors).toEqual(mockFlavors);
|
||||
expect(model.flavorProfiles).toEqual(mockFlavorProfiles);
|
||||
expect(model.members.length).toBe(2);
|
||||
expect(model.certificates.length).toBe(2);
|
||||
expect(model.listenerPorts.length).toBe(0);
|
||||
@ -742,6 +791,8 @@
|
||||
expect(model.initialized).toBe(true);
|
||||
expect(model.subnets.length).toBe(2);
|
||||
expect(model.networks).toEqual(mockNetworks);
|
||||
expect(model.flavors).toEqual(mockFlavors);
|
||||
expect(model.flavorProfiles).toEqual(mockFlavorProfiles);
|
||||
expect(model.members.length).toBe(0);
|
||||
expect(model.certificates.length).toBe(0);
|
||||
expect(model.listenerPorts.length).toBe(0);
|
||||
@ -1238,7 +1289,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(5);
|
||||
expect(Object.keys(model.spec.loadbalancer).length).toBe(6);
|
||||
expect(Object.keys(model.spec.listener).length).toBe(14);
|
||||
expect(Object.keys(model.spec.l7policy).length).toBe(8);
|
||||
expect(Object.keys(model.spec.l7rule).length).toBe(7);
|
||||
@ -1486,6 +1537,7 @@
|
||||
it('should set final spec properties', function() {
|
||||
model.spec.loadbalancer.vip_address = '1.2.3.4';
|
||||
model.spec.loadbalancer.vip_subnet_id = model.subnets[0];
|
||||
model.spec.loadbalancer.flavor_id = model.flavors[Object.keys(model.flavors)[0]];
|
||||
model.spec.listener.protocol = 'TCP';
|
||||
model.spec.listener.protocol_port = 80;
|
||||
model.spec.listener.connection_limit = 999;
|
||||
@ -1587,6 +1639,7 @@
|
||||
it('should set final spec certificates', function() {
|
||||
model.spec.loadbalancer.vip_address = '1.2.3.4';
|
||||
model.spec.loadbalancer.vip_subnet_id = model.subnets[0];
|
||||
model.spec.loadbalancer.flavor_id = model.flavors[Object.keys(model.flavors)[0]];
|
||||
model.spec.listener.protocol = 'TERMINATED_HTTPS';
|
||||
model.spec.listener.protocol_port = 443;
|
||||
model.spec.listener.connection_limit = 9999;
|
||||
|
7
releasenotes/notes/flavor-support-0195a486faa16b7f.yaml
Normal file
7
releasenotes/notes/flavor-support-0195a486faa16b7f.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add load balancer flavor support.
|
||||
issues:
|
||||
- |
|
||||
The octavia-dashboard requires openstacksdk > 0.24.0 for flavor support.
|
@ -4,7 +4,7 @@
|
||||
|
||||
horizon>=14.0.0.0b3 # Apache-2.0
|
||||
Babel!=2.4.0,>=2.3.4 # BSD
|
||||
openstacksdk>=0.11.2 # Apache-2.0
|
||||
openstacksdk>=0.24.0 # Apache-2.0
|
||||
oslo.log>=3.36.0 # Apache-2.0
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
python-barbicanclient>=4.5.2 # Apache-2.0
|
||||
|
Loading…
Reference in New Issue
Block a user