Merge "Add floatingip plugin to support floating IP resource"

This commit is contained in:
Zuul 2019-03-20 22:30:29 +00:00 committed by Gerrit Code Review
commit 751983800a
9 changed files with 382 additions and 0 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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")

View File

@ -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'

View File

@ -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))

View File

@ -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')

View File

@ -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 .")

View File

@ -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)