Add REST API for working with floating IPs

This adds the REST APIs for certain floating IP actions such as
listing floating IP addresses and pools, allocating new floating IPs
and associating and disassociating floating IPs.

Related to blueprint horizon-lbaas-v2-ui
Change-Id: I80e550a04b0a0f2ad05203180781013ba5501472
This commit is contained in:
Justin Pomeroy 2016-02-16 16:08:51 -06:00
parent 8157b38f97
commit 062bc25b68
4 changed files with 368 additions and 0 deletions

View File

@ -1,5 +1,6 @@
# Copyright 2015, Hewlett-Packard Development Company, L.P.
# 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.
@ -44,3 +45,78 @@ class SecurityGroups(generic.View):
security_groups = api.network.security_group_list(request)
return {'items': [sg.to_dict() for sg in security_groups]}
@urls.register
class FloatingIP(generic.View):
"""API for a single floating IP address.
"""
url_regex = r'network/floatingip/$'
@rest_utils.ajax(data_required=True)
def post(self, request):
"""Allocate a new floating IP address.
:param pool_id: The ID of the floating IP address pool in which to
allocate the new address.
:return: JSON representation of the new floating IP address
"""
pool = request.DATA['pool_id']
result = api.network.tenant_floating_ip_allocate(request, pool)
return result.to_dict()
@rest_utils.ajax(data_required=True)
def patch(self, request):
"""Associate or disassociate a floating IP address.
:param address_id: The ID of the floating IP address to associate
or disassociate.
:param port_id: The ID of the port to associate.
"""
address = request.DATA['address_id']
port = request.DATA.get('port_id')
if port is None:
api.network.floating_ip_disassociate(request, address)
else:
api.network.floating_ip_associate(request, address, port)
@urls.register
class FloatingIPs(generic.View):
"""API for floating IP addresses.
"""
url_regex = r'network/floatingips/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of floating IP addresses.
The listing result is an object with property "items". Each item is
an extension.
Example:
http://localhost/api/network/floatingips
"""
result = api.network.tenant_floating_ip_list(request)
return {'items': [ip.to_dict() for ip in result]}
@urls.register
class FloatingIPPools(generic.View):
"""API for floating IP pools.
"""
url_regex = r'network/floatingippools/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of floating IP pools.
The listing result is an object with property "items". Each item is
an extension.
Example:
http://localhost/api/network/floatingippools
"""
result = api.network.floating_ip_pools_list(request)
return {'items': [p.to_dict() for p in result]}

View File

@ -0,0 +1,132 @@
/**
* 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.app.core.openstack-service-api')
.factory('horizon.app.core.openstack-service-api.network', networkAPI);
networkAPI.$inject = [
'horizon.framework.util.http.service',
'horizon.framework.widgets.toast.service'
];
/**
* @ngdoc service
* @name horizon.app.core.openstack-service-api.network
* @description Provides access to APIs that are common to nova network
* and neutron.
*/
function networkAPI(apiService, toastService) {
var service = {
getFloatingIps: getFloatingIps,
getFloatingIpPools: getFloatingIpPools,
allocateFloatingIp: allocateFloatingIp,
associateFloatingIp: associateFloatingIp,
disassociateFloatingIp: disassociateFloatingIp
};
return service;
/////////////
// Floating IPs
/**
* @name horizon.app.core.openstack-service-api.networkAPI.getFloatingIps
* @description
* Get a list of floating IP addresses.
*
* The listing result is an object with property "items". Each item is
* a floating IP address.
*/
function getFloatingIps() {
return apiService.get('/api/network/floatingips/')
.error(function () {
toastService.add('error', gettext('Unable to retrieve floating IPs.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.networkAPI.getFloatingIpPools
* @description
* Get a list of floating IP pools.
*
* The listing result is an object with property "items". Each item is
* a floating IP address.
*/
function getFloatingIpPools() {
return apiService.get('/api/network/floatingippools/')
.error(function () {
toastService.add('error', gettext('Unable to retrieve floating IP pools.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.networkAPI.allocateFloatingIp
* @description
* Allocate a floating IP address within a pool.
*
* @param {string} poolId
* The Id of the pool in which to allocate the new floating IP address.
*
* Returns the new floating IP address on success.
*/
function allocateFloatingIp(poolId) {
return apiService.post('/api/network/floatingip/', { pool_id: poolId })
.error(function () {
toastService.add('error', gettext('Unable to allocate new floating IP address.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.networkAPI.associateFloatingIp
* @description
* Associate a floating IP address with a port.
*
* @param {string} addressId
* The Id of the floating IP address to associate.
*
* @param {string} portId
* The Id of the port to associate.
*/
function associateFloatingIp(addressId, portId) {
var params = { address_id: addressId, port_id: portId };
return apiService.patch('/api/network/floatingip/', params)
.error(function () {
toastService.add('error', gettext('Unable to associate floating IP address.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.networkAPI.disassociateFloatingIp
* @description
* Disassociate a floating IP address.
*
* @param {string} addressId
* The Id of the floating IP address to disassociate.
*/
function disassociateFloatingIp(addressId) {
return apiService.patch('/api/network/floatingip/', { address_id: addressId })
.error(function () {
toastService.add('error', gettext('Unable to disassociate floating IP address.'));
});
}
}
}());

View File

@ -0,0 +1,89 @@
/**
* 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('Network API', function() {
var testCall, service;
var apiService = {};
var toastService = {};
beforeEach(function() {
module('horizon.mock.openstack-service-api', function($provide, initServices) {
testCall = initServices($provide, apiService, toastService);
});
module('horizon.app.core.openstack-service-api');
inject(['horizon.app.core.openstack-service-api.network', function(networkAPI) {
service = networkAPI;
}]);
});
it('defines the service', function() {
expect(service).toBeDefined();
});
var tests = [
{
func: 'getFloatingIps',
method: 'get',
path: '/api/network/floatingips/',
error: 'Unable to retrieve floating IPs.'
},
{
func: 'getFloatingIpPools',
method: 'get',
path: '/api/network/floatingippools/',
error: 'Unable to retrieve floating IP pools.'
},
{
func: 'allocateFloatingIp',
method: 'post',
path: '/api/network/floatingip/',
data: { pool_id: 'pool' },
error: 'Unable to allocate new floating IP address.',
testInput: [ 'pool' ]
},
{
func: 'associateFloatingIp',
method: 'patch',
path: '/api/network/floatingip/',
data: { address_id: 'address', port_id: 'port' },
error: 'Unable to associate floating IP address.',
testInput: [ 'address', 'port' ]
},
{
func: 'disassociateFloatingIp',
method: 'patch',
path: '/api/network/floatingip/',
data: { address_id: 'address' },
error: 'Unable to disassociate floating IP address.',
testInput: [ 'address' ]
}
];
// Iterate through the defined tests and apply as Jasmine specs.
angular.forEach(tests, function(params) {
it('defines the ' + params.func + ' call properly', function() {
var callParams = [apiService, service, toastService, params];
testCall.apply(this, callParams);
});
});
});
})();

View File

@ -1,4 +1,5 @@
# Copyright 2015, Hewlett-Packard Development Company, L.P.
# 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.
@ -31,3 +32,73 @@ class RestNetworkApiSecurityGroupTests(test.TestCase):
self.assertEqual(response.json,
{"items": [{"name": "default"}]})
client.security_group_list.assert_called_once_with(request)
class RestNetworkApiFloatingIpTests(test.TestCase):
@mock.patch.object(network.api, 'network')
def test_floating_ip_list(self, client):
request = self.mock_rest_request()
client.tenant_floating_ip_list.return_value = ([
mock.Mock(**{'to_dict.return_value': {'ip': '1.2.3.4'}}),
mock.Mock(**{'to_dict.return_value': {'ip': '2.3.4.5'}})
])
response = network.FloatingIPs().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.json,
{'items': [{'ip': '1.2.3.4'}, {'ip': '2.3.4.5'}]})
client.tenant_floating_ip_list.assert_called_once_with(request)
@mock.patch.object(network.api, 'network')
def test_floating_ip_pool_list(self, client):
request = self.mock_rest_request()
client.floating_ip_pools_list.return_value = ([
mock.Mock(**{'to_dict.return_value': {'name': '1'}}),
mock.Mock(**{'to_dict.return_value': {'name': '2'}})
])
response = network.FloatingIPPools().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.json,
{'items': [{'name': '1'}, {'name': '2'}]})
client.floating_ip_pools_list.assert_called_once_with(request)
@mock.patch.object(network.api, 'network')
def test_allocate_floating_ip(self, client):
request = self.mock_rest_request(
body='{"pool_id": "pool"}'
)
client.tenant_floating_ip_allocate.return_value = (
mock.Mock(**{'to_dict.return_value': {'ip': '1.2.3.4'}})
)
response = network.FloatingIP().post(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.json,
{'ip': '1.2.3.4'})
client.tenant_floating_ip_allocate.assert_called_once_with(request,
'pool')
@mock.patch.object(network.api, 'network')
def test_associate_floating_ip(self, client):
request = self.mock_rest_request(
body='{"address_id": "address", "port_id": "port"}'
)
response = network.FloatingIP().patch(request)
self.assertStatusCode(response, 204)
client.floating_ip_associate.assert_called_once_with(request,
'address',
'port')
@mock.patch.object(network.api, 'network')
def test_disassociate_floating_ip(self, client):
request = self.mock_rest_request(
body='{"address_id": "address"}'
)
response = network.FloatingIP().patch(request)
self.assertStatusCode(response, 204)
client.floating_ip_disassociate.assert_called_once_with(request,
'address')