Merge "Add floatingip plugin to support floating IP resource"
This commit is contained in:
commit
751983800a
|
@ -416,3 +416,27 @@ def host_extra_capability_get_all_per_name(host_id,
|
|||
def host_get_all_by_queries_including_extracapabilities(queries):
|
||||
"""Returns hosts filtered by an array of queries."""
|
||||
return IMPL.host_get_all_by_queries_including_extracapabilities(queries)
|
||||
|
||||
|
||||
# Floating ip
|
||||
|
||||
def floatingip_create(values):
|
||||
"""Create a floating ip from the values."""
|
||||
return IMPL.floatingip_create(values)
|
||||
|
||||
|
||||
@to_dict
|
||||
def floatingip_get(floatingip_id):
|
||||
"""Return a specific floating ip."""
|
||||
return IMPL.floatingip_get(floatingip_id)
|
||||
|
||||
|
||||
@to_dict
|
||||
def floatingip_list():
|
||||
"""Return a list of floating ip."""
|
||||
return IMPL.floatingip_list()
|
||||
|
||||
|
||||
def floatingip_destroy(floatingip_id):
|
||||
"""Delete specific floating ip."""
|
||||
IMPL.floatingip_destroy(floatingip_id)
|
||||
|
|
|
@ -828,3 +828,51 @@ def host_extra_capability_get_all_per_name(host_id, capability_name):
|
|||
with session.begin():
|
||||
query = _host_extra_capability_get_all_per_host(session, host_id)
|
||||
return query.filter_by(capability_name=capability_name).all()
|
||||
|
||||
|
||||
# Floating IP
|
||||
def _floatingip_get(session, floatingip_id):
|
||||
query = model_query(models.FloatingIP, session)
|
||||
return query.filter_by(id=floatingip_id).first()
|
||||
|
||||
|
||||
def _floatingip_get_all(session):
|
||||
query = model_query(models.FloatingIP, session)
|
||||
return query
|
||||
|
||||
|
||||
def floatingip_get(floatingip_id):
|
||||
return _floatingip_get(get_session(), floatingip_id)
|
||||
|
||||
|
||||
def floatingip_list():
|
||||
return model_query(models.FloatingIP, get_session()).all()
|
||||
|
||||
|
||||
def floatingip_create(values):
|
||||
values = values.copy()
|
||||
floatingip = models.FloatingIP()
|
||||
floatingip.update(values)
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
try:
|
||||
floatingip.save(session=session)
|
||||
except common_db_exc.DBDuplicateEntry as e:
|
||||
# raise exception about duplicated columns (e.columns)
|
||||
raise db_exc.BlazarDBDuplicateEntry(
|
||||
model=floatingip.__class__.__name__, columns=e.columns)
|
||||
|
||||
return floatingip_get(floatingip.id)
|
||||
|
||||
|
||||
def floatingip_destroy(floatingip_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
floatingip = _floatingip_get(session, floatingip_id)
|
||||
|
||||
if not floatingip:
|
||||
# raise not found error
|
||||
raise db_exc.BlazarDBNotFound(id=floatingip_id, model='FloatingIP')
|
||||
|
||||
session.delete(floatingip)
|
||||
|
|
|
@ -191,3 +191,14 @@ class CantUpdateParameter(exceptions.BlazarException):
|
|||
class InvalidPeriod(exceptions.BlazarException):
|
||||
code = 400
|
||||
msg_fmt = _('The end_date must be later than the start_date.')
|
||||
|
||||
|
||||
# floating ip plugin related exceptions
|
||||
|
||||
class FloatingIPNotFound(exceptions.NotFound):
|
||||
msg_fmt = _("Floating IP %(floatingip)s not found.")
|
||||
|
||||
|
||||
class CantDeleteFloatingIP(exceptions.BlazarException):
|
||||
code = 409
|
||||
msg_fmt = _("Can't delete floating IP %(floatingip)s. %(msg)s")
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# Copyright (c) 2019 NTT.
|
||||
#
|
||||
# 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.
|
||||
|
||||
RESOURCE_TYPE = u'virtual:floatingip'
|
|
@ -0,0 +1,101 @@
|
|||
# Copyright (c) 2019 NTT.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from blazar.db import api as db_api
|
||||
from blazar.db import exceptions as db_ex
|
||||
from blazar import exceptions
|
||||
from blazar.manager import exceptions as manager_ex
|
||||
from blazar.plugins import base
|
||||
from blazar.plugins import floatingips as plugin
|
||||
from blazar.utils.openstack import neutron
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FloatingIpPlugin(base.BasePlugin):
|
||||
"""Plugin for floating IP resource."""
|
||||
|
||||
resource_type = plugin.RESOURCE_TYPE
|
||||
title = 'Floating IP Plugin'
|
||||
description = 'This plugin creates and assigns floating IPs.'
|
||||
|
||||
def reserve_resource(self, reservation_id, values):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_start(self, resource_id):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_end(self, resource_id):
|
||||
raise NotImplementedError
|
||||
|
||||
def validate_floatingip_params(self, values):
|
||||
marshall_attributes = set(['floating_network_id',
|
||||
'floating_ip_address'])
|
||||
missing_attr = marshall_attributes - set(values.keys())
|
||||
if missing_attr:
|
||||
raise manager_ex.MissingParameter(param=','.join(missing_attr))
|
||||
|
||||
def create_floatingip(self, values):
|
||||
|
||||
self.validate_floatingip_params(values)
|
||||
|
||||
network_id = values.pop('floating_network_id')
|
||||
floatingip_address = values.pop('floating_ip_address')
|
||||
|
||||
pool = neutron.FloatingIPPool(network_id)
|
||||
# validate the floating ip address is out of allocation_pools and
|
||||
# within its subnet cidr.
|
||||
try:
|
||||
subnet = pool.fetch_subnet(floatingip_address)
|
||||
except exceptions.BlazarException:
|
||||
LOG.info("Floating IP %s in network %s can't be used "
|
||||
"for Blazar's resource.", floatingip_address, network_id)
|
||||
raise
|
||||
|
||||
floatingip_values = {
|
||||
'floating_network_id': network_id,
|
||||
'subnet_id': subnet['id'],
|
||||
'floating_ip_address': floatingip_address
|
||||
}
|
||||
|
||||
floatingip = db_api.floatingip_create(floatingip_values)
|
||||
|
||||
return floatingip
|
||||
|
||||
def get_floatingip(self, fip_id):
|
||||
fip = db_api.floatingip_get(fip_id)
|
||||
if fip is None:
|
||||
raise manager_ex.FloatingIPNotFound(floatingip=fip_id)
|
||||
return fip
|
||||
|
||||
def list_floatingip(self):
|
||||
fips = db_api.floatingip_list()
|
||||
return fips
|
||||
|
||||
def delete_floatingip(self, fip_id):
|
||||
fip = db_api.floatingip_get(fip_id)
|
||||
if fip is None:
|
||||
raise manager_ex.FloatingIPNotFound(floatingip=fip_id)
|
||||
|
||||
# TODO(masahito): Check no allocation exists for the floating ip here
|
||||
# once this plugin supports reserve_resource method.
|
||||
|
||||
try:
|
||||
db_api.floatingip_destroy(fip_id)
|
||||
except db_ex.BlazarDBException as e:
|
||||
raise manager_ex.CantDeleteFloatingIP(floatingip=fip_id,
|
||||
msg=str(e))
|
|
@ -0,0 +1,145 @@
|
|||
# Copyright (c) 2019 NTT.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from blazar.db import api as db_api
|
||||
from blazar.manager import exceptions as mgr_exceptions
|
||||
from blazar.plugins.floatingips import floatingip_plugin
|
||||
from blazar import tests
|
||||
from blazar.utils.openstack import exceptions as opst_exceptions
|
||||
from blazar.utils.openstack import neutron
|
||||
|
||||
|
||||
class FloatingIpPluginTest(tests.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FloatingIpPluginTest, self).setUp()
|
||||
self.fip_pool = self.patch(neutron, 'FloatingIPPool')
|
||||
|
||||
def test_create_floatingip(self):
|
||||
m = mock.MagicMock()
|
||||
m.fetch_subnet.return_value = {'id': 'subnet-id'}
|
||||
self.fip_pool.return_value = m
|
||||
fip_row = {
|
||||
'id': 'fip-id',
|
||||
'network_id': 'net-id',
|
||||
'subnet_id': 'subnet-id',
|
||||
'floating_ip_address': '172.24.4.100',
|
||||
'reservable': True
|
||||
}
|
||||
patch_fip_create = self.patch(db_api, 'floatingip_create')
|
||||
patch_fip_create.return_value = fip_row
|
||||
|
||||
data = {
|
||||
'floating_ip_address': '172.24.4.100',
|
||||
'floating_network_id': 'net-id'
|
||||
}
|
||||
expected = fip_row
|
||||
|
||||
fip_plugin = floatingip_plugin.FloatingIpPlugin()
|
||||
ret = fip_plugin.create_floatingip(data)
|
||||
|
||||
self.assertDictEqual(expected, ret)
|
||||
m.fetch_subnet.assert_called_once_with('172.24.4.100')
|
||||
patch_fip_create.assert_called_once_with({
|
||||
'floating_network_id': 'net-id',
|
||||
'subnet_id': 'subnet-id',
|
||||
'floating_ip_address': '172.24.4.100'})
|
||||
|
||||
def test_create_floatingip_with_invalid_ip(self):
|
||||
m = mock.MagicMock()
|
||||
m.fetch_subnet.side_effect = opst_exceptions.NeutronUsesFloatingIP()
|
||||
self.fip_pool.return_value = m
|
||||
|
||||
fip_plugin = floatingip_plugin.FloatingIpPlugin()
|
||||
self.assertRaises(opst_exceptions.NeutronUsesFloatingIP,
|
||||
fip_plugin.create_floatingip,
|
||||
{'floating_ip_address': 'invalid-ip',
|
||||
'floating_network_id': 'id'})
|
||||
|
||||
def test_get_floatingip(self):
|
||||
fip_row = {
|
||||
'id': 'fip-id',
|
||||
'network_id': 'net-id',
|
||||
'subnet_id': 'subnet-id',
|
||||
'floating_ip_address': '172.24.4.100',
|
||||
'reservable': True
|
||||
}
|
||||
patch_fip_get = self.patch(db_api, 'floatingip_get')
|
||||
patch_fip_get.return_value = fip_row
|
||||
|
||||
expected = fip_row
|
||||
|
||||
fip_plugin = floatingip_plugin.FloatingIpPlugin()
|
||||
ret = fip_plugin.get_floatingip('fip-id')
|
||||
|
||||
self.assertDictEqual(expected, ret)
|
||||
patch_fip_get.assert_called_once_with('fip-id')
|
||||
|
||||
def test_get_floatingip_with_no_exist(self):
|
||||
patch_fip_get = self.patch(db_api, 'floatingip_get')
|
||||
patch_fip_get.return_value = None
|
||||
|
||||
fip_plugin = floatingip_plugin.FloatingIpPlugin()
|
||||
self.assertRaises(mgr_exceptions.FloatingIPNotFound,
|
||||
fip_plugin.get_floatingip, 'fip-id')
|
||||
|
||||
patch_fip_get.assert_called_once_with('fip-id')
|
||||
|
||||
def test_get_list_floatingips(self):
|
||||
fip_rows = [{
|
||||
'id': 'fip-id',
|
||||
'network_id': 'net-id',
|
||||
'subnet_id': 'subnet-id',
|
||||
'floating_ip_address': '172.24.4.100',
|
||||
'reservable': True
|
||||
}]
|
||||
patch_fip_list = self.patch(db_api, 'floatingip_list')
|
||||
patch_fip_list.return_value = fip_rows
|
||||
|
||||
expected = fip_rows
|
||||
|
||||
fip_plugin = floatingip_plugin.FloatingIpPlugin()
|
||||
ret = fip_plugin.list_floatingip()
|
||||
|
||||
self.assertListEqual(expected, ret)
|
||||
patch_fip_list.assert_called_once_with()
|
||||
|
||||
def test_delete_floatingip(self):
|
||||
fip_row = {
|
||||
'id': 'fip-id',
|
||||
'network_id': 'net-id',
|
||||
'subnet_id': 'subnet-id',
|
||||
'floating_ip_address': '172.24.4.100',
|
||||
'reservable': True
|
||||
}
|
||||
patch_fip_get = self.patch(db_api, 'floatingip_get')
|
||||
patch_fip_get.return_value = fip_row
|
||||
patch_fip_destroy = self.patch(db_api, 'floatingip_destroy')
|
||||
|
||||
fip_plugin = floatingip_plugin.FloatingIpPlugin()
|
||||
fip_plugin.delete_floatingip('fip-id')
|
||||
|
||||
patch_fip_get.assert_called_once_with('fip-id')
|
||||
patch_fip_destroy.assert_called_once_with('fip-id')
|
||||
|
||||
def test_delete_floatingip_with_no_exist(self):
|
||||
patch_fip_get = self.patch(db_api, 'floatingip_get')
|
||||
patch_fip_get.return_value = None
|
||||
|
||||
fip_plugin = floatingip_plugin.FloatingIpPlugin()
|
||||
self.assertRaises(mgr_exceptions.FloatingIPNotFound,
|
||||
fip_plugin.delete_floatingip,
|
||||
'non-exists-id')
|
|
@ -51,3 +51,12 @@ class InventoryUpdateFailed(exceptions.BlazarException):
|
|||
|
||||
class FloatingIPNetworkNotFound(exceptions.InvalidInput):
|
||||
msg_fmt = _("Failed to find network %(network)s")
|
||||
|
||||
|
||||
class FloatingIPSubnetNotFound(exceptions.NotFound):
|
||||
msg_fmt = _("Valid subnet for the floating IP %(fip)s is not found.")
|
||||
|
||||
|
||||
class NeutronUsesFloatingIP(exceptions.InvalidInput):
|
||||
msg_fmt = _("The floating IP %(floatingip)s is used in allocation_pools "
|
||||
"or gateway_ip in subnet %(subnet)s .")
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
from keystoneauth1.identity import v3
|
||||
from keystoneauth1 import session
|
||||
import netaddr
|
||||
from neutronclient.common import exceptions as neutron_exceptions
|
||||
from neutronclient.v2_0 import client as neutron_client
|
||||
|
||||
|
@ -72,3 +73,31 @@ class FloatingIPPool(BlazarNeutronClient):
|
|||
raise exceptions.FloatingIPNetworkNotFound(network=network_id)
|
||||
|
||||
self.network_id = network_id
|
||||
|
||||
def fetch_subnet(self, floatingip):
|
||||
fip = netaddr.IPAddress(floatingip)
|
||||
network = self.neutron.show_network(self.network_id)['network']
|
||||
subnet_ids = network['subnets']
|
||||
|
||||
for sub_id in subnet_ids:
|
||||
subnet = self.neutron.show_subnet(sub_id)['subnet']
|
||||
cidr = netaddr.IPNetwork(subnet['cidr'])
|
||||
|
||||
# skip the subnet because it has not valid cidr for the floating ip
|
||||
if fip not in cidr:
|
||||
continue
|
||||
|
||||
allocated_ip = netaddr.IPSet()
|
||||
|
||||
allocated_ip.add(netaddr.IPAddress(subnet['gateway_ip']))
|
||||
for alloc in subnet['allocation_pools']:
|
||||
allocated_ip.add(netaddr.IPRange(alloc['start'], alloc['end']))
|
||||
|
||||
if fip in allocated_ip:
|
||||
raise exceptions.NeutronUsesFloatingIP(floatingip=fip,
|
||||
subnet=subnet['id'])
|
||||
else:
|
||||
self.subnet_id = subnet['id']
|
||||
return subnet
|
||||
|
||||
raise exceptions.FloatingIPSubnetNotFound(fip=floatingip)
|
||||
|
|
Loading…
Reference in New Issue