Merge "Implement floating ip disassociation"

This commit is contained in:
Jenkins 2016-06-08 11:44:18 +00:00 committed by Gerrit Code Review
commit 94e1176221
3 changed files with 388 additions and 118 deletions

View File

@ -122,7 +122,7 @@ class NeutronResourceHandle(ResourceHandle):
'router': LIST | CREATE | ACTION | UPDATE,
'security_group': LIST | CREATE | GET,
'security_group_rule': LIST | CREATE | DELETE,
'floatingip': LIST | CREATE}
'floatingip': LIST | CREATE | UPDATE | DELETE}
def _get_client(self, cxt):
return q_client.Client('2.0',

View File

@ -47,6 +47,7 @@ import tricircle.common.constants as t_constants
import tricircle.common.context as t_context
import tricircle.common.exceptions as t_exceptions
from tricircle.common.i18n import _
from tricircle.common.i18n import _LE
from tricircle.common.i18n import _LI
import tricircle.common.lock_handle as t_lock
from tricircle.common import utils
@ -54,6 +55,7 @@ from tricircle.common import xrpcapi
import tricircle.db.api as db_api
from tricircle.db import core
from tricircle.db import models
import tricircle.network.exceptions as t_network_exc
from tricircle.network import security_groups
@ -631,11 +633,13 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
def _prepare_top_element(self, t_ctx, q_ctx,
project_id, pod, ele, _type, body):
def list_resources(t_ctx_, q_ctx_, pod_, ele_, _type_):
return getattr(self, 'get_%ss' % _type_)(
q_ctx_, filters={'name': ele_['id']})
return getattr(super(TricirclePlugin, self),
'get_%ss' % _type_)(q_ctx_,
filters={'name': [ele_['id']]})
def create_resources(t_ctx_, q_ctx_, pod_, body_, _type_):
return getattr(self, 'create_%s' % _type_)(q_ctx_, body_)
return getattr(super(TricirclePlugin, self),
'create_%s' % _type_)(q_ctx_, body_)
return t_lock.get_or_create_element(
t_ctx, q_ctx,
@ -817,7 +821,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
}
_, port_id = self._prepare_top_element(
t_ctx, q_ctx, project_id, pod, port_ele, 'port', port_body)
return self.get_port(q_ctx, port_id)
return super(TricirclePlugin, self).get_port(q_ctx, port_id)
def _get_bottom_bridge_elements(self, q_ctx, project_id,
pod, t_net, is_external, t_subnet, t_port):
@ -908,6 +912,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# az hint parameter, so tricircle plugin knows where to create the
# corresponding bottom external network. here we get bottom external
# network ID from resource routing table.
if not network.get(az_ext.AZ_HINTS):
raise t_exceptions.ExternalNetPodNotSpecify()
pod_name = network[az_ext.AZ_HINTS][0]
pod = db_api.get_pod_by_name(t_ctx, pod_name)
b_net_id = db_api.get_bottom_id_by_top_id_pod_name(
@ -916,6 +922,10 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# create corresponding bottom router in the pod where external network
# is located.
t_router = self._get_router(context, router_id)
# TODO(zhiyuan) decide router is distributed or not from pod table
# currently "distributed" is set to False, should add a metadata field
# to pod table, and decide distributed or not from the metadata later
body = {'router': {'name': router_id,
'distributed': False}}
_, b_router_id = self._prepare_bottom_element(
@ -1224,7 +1234,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return return_info
@staticmethod
def _safe_create_bottom_floatingip(t_ctx, client, fip_net_id,
def _safe_create_bottom_floatingip(t_ctx, pod, client, fip_net_id,
fip_address, port_id):
try:
client.create_floatingips(
@ -1237,127 +1247,270 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
'comparator': 'eq',
'value': fip_address}])
# NOTE(zhiyuan) if the internal port associated with the existing
# fip is what we expect, just ignore this exception
if fips[0].get('port_id') == port_id:
# fip is what we expect, just ignore this exception; or if the
# existing fip is not associated with any internal port, update the
# fip to add association
if not fips:
# this is rare case that we got IpAddressInUseClient exception
# a second ago but now the floating ip is missing
raise t_network_exc.BottomPodOperationFailure(
resource='floating ip', pod_name=pod['pod_name'])
associated_port_id = fips[0].get('port_id')
if associated_port_id == port_id:
pass
elif not associated_port_id:
client.update_floatingips(t_ctx, fips[0]['id'],
{'floatingip': {'port_id': port_id}})
else:
raise
@staticmethod
def _disassociate_floatingip(context, _id):
with context.session.begin():
fip_qry = context.session.query(l3_db.FloatingIP)
floating_ips = fip_qry.filter_by(id=_id)
for floating_ip in floating_ips:
floating_ip.update({'fixed_port_id': None,
'fixed_ip_address': None,
'router_id': None})
def _rollback_floatingip_data(context, _id, org_data):
"""Rollback the data of floating ip object to the original one
:param context: request context
:param _id: ID of the floating ip
:param org_data: data of floating ip we rollback to
:return: None
"""
try:
with context.session.begin():
fip_qry = context.session.query(l3_db.FloatingIP)
floating_ips = fip_qry.filter_by(id=_id)
for floating_ip in floating_ips:
floating_ip.update(org_data)
except Exception as e:
# log the exception and re-raise it
LOG.exception(_LE('Fail to rollback floating ip data, reason: '
'%(reason)s') % {'reason': e.message})
raise
def update_floatingip(self, context, _id, floatingip):
"""Update floating ip object in top and bottom pods
:param context: request context
:param _id: ID of the floating ip
:param floatingip: data of floating ip we update to
:return: updated floating ip ojbect
"""
org_floatingip_dict = self._make_floatingip_dict(
self._get_floatingip(context, _id))
res = super(TricirclePlugin, self).update_floatingip(
context, _id, floatingip)
try:
t_ctx = t_context.get_context_from_neutron_context(context)
fip = floatingip['floatingip']
floatingip_db = self._get_floatingip(context, _id)
int_port_id = fip['port_id']
project_id = floatingip_db['tenant_id']
fip_address = floatingip_db['floating_ip_address']
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, int_port_id, t_constants.RT_PORT)
if not mappings:
int_port = self.get_port(context, int_port_id)
int_network = self.get_network(context, int_port['network_id'])
if az_ext.AZ_HINTS not in int_network:
raise Exception('Cross pods L3 networking not support')
self._validate_availability_zones(
context, int_network[az_ext.AZ_HINTS], False)
int_net_pod, _ = az_ag.get_pod_by_az_tenant(
t_ctx, int_network[az_ext.AZ_HINTS][0], project_id)
b_int_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, int_network['id'], int_net_pod['pod_name'],
t_constants.RT_NETWORK)
b_int_port_body = {
'port': {
'tenant_id': project_id,
'admin_state_up': True,
'name': int_port['id'],
'network_id': b_int_net_id,
'mac_address': int_port['mac_address'],
'fixed_ips': [{'ip_address': int_port['fixed_ips'][0][
'ip_address']}]
}
}
# TODO(zhiyuan) handle DHCP port ip address conflict problem
_, b_int_port_id = self._prepare_bottom_element(
t_ctx, project_id, int_net_pod, int_port,
t_constants.RT_PORT, b_int_port_body)
if floatingip['floatingip']['port_id']:
self._associate_floatingip(context, _id, floatingip)
else:
int_net_pod, b_int_port_id = mappings[0]
ext_net_id = floatingip_db['floating_network_id']
ext_net = self.get_network(context, ext_net_id)
ext_net_pod = db_api.get_pod_by_name(t_ctx,
ext_net[az_ext.AZ_HINTS][0])
self._disassociate_floatingip(context, org_floatingip_dict)
return res
except Exception as e:
# NOTE(zhiyuan) when exception occurs, we update floating ip object
# to rollback fixed_port_id, fixed_ip_address, router_id
LOG.exception(
_LE('Fail to update floating ip, reason: '
'%(reason)s, rollback floating ip data') % {
'reason': e.message})
org_data = {
'fixed_port_id': org_floatingip_dict['port_id'],
'fixed_ip_address': org_floatingip_dict['fixed_ip_address'],
'router_id': org_floatingip_dict['router_id']}
self._rollback_floatingip_data(context, _id, org_data)
raise
# external network and internal network are in the same pod, no
# need to use bridge network.
if int_net_pod['pod_name'] == ext_net_pod['pod_name']:
client = self._get_client(int_net_pod['pod_name'])
b_ext_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, ext_net_id, ext_net_pod['pod_name'],
t_constants.RT_NETWORK)
self._safe_create_bottom_floatingip(
t_ctx, client, b_ext_net_id, fip_address, b_int_port_id)
def _associate_floatingip(self, context, _id, floatingip):
t_ctx = t_context.get_context_from_neutron_context(context)
return res
# below handle the case that external network and internal network
# are in different pods
int_client = self._get_client(int_net_pod['pod_name'])
ext_client = self._get_client(ext_net_pod['pod_name'])
ns_bridge_net_name = t_constants.ns_bridge_net_name % project_id
ns_bridge_net = self.get_networks(
context, {'name': [ns_bridge_net_name]})[0]
int_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, ns_bridge_net['id'], int_net_pod['pod_name'],
fip = floatingip['floatingip']
floatingip_db = self._get_floatingip(context, _id)
int_port_id = fip['port_id']
project_id = floatingip_db['tenant_id']
fip_address = floatingip_db['floating_ip_address']
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, int_port_id, t_constants.RT_PORT)
if not mappings:
int_port = self.get_port(context, int_port_id)
int_network = self.get_network(context, int_port['network_id'])
if az_ext.AZ_HINTS not in int_network:
raise Exception('Cross pods L3 networking not support')
self._validate_availability_zones(
context, int_network[az_ext.AZ_HINTS], False)
int_net_pod, _ = az_ag.get_pod_by_az_tenant(
t_ctx, int_network[az_ext.AZ_HINTS][0], project_id)
b_int_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, int_network['id'], int_net_pod['pod_name'],
t_constants.RT_NETWORK)
ext_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, ns_bridge_net['id'], ext_net_pod['pod_name'],
t_constants.RT_NETWORK)
t_pod = db_api.get_top_pod(t_ctx)
t_ns_bridge_port = self._get_bridge_interface(
t_ctx, context, project_id, t_pod, ns_bridge_net['id'],
None, b_int_port_id, False)
port_body = {
b_int_port_body = {
'port': {
'tenant_id': project_id,
'admin_state_up': True,
'name': 'ns_bridge_port',
'network_id': ext_bridge_net_id,
'fixed_ips': [{'ip_address': t_ns_bridge_port[
'fixed_ips'][0]['ip_address']}]
'name': int_port['id'],
'network_id': b_int_net_id,
'mac_address': int_port['mac_address'],
'fixed_ips': [{'ip_address': int_port['fixed_ips'][0][
'ip_address']}]
}
}
_, b_ns_bridge_port_id = self._prepare_bottom_element(
t_ctx, project_id, ext_net_pod, t_ns_bridge_port,
t_constants.RT_PORT, port_body)
# TODO(zhiyuan) handle DHCP port ip address conflict problem
_, b_int_port_id = self._prepare_bottom_element(
t_ctx, project_id, int_net_pod, int_port,
t_constants.RT_PORT, b_int_port_body)
else:
int_net_pod, b_int_port_id = mappings[0]
ext_net_id = floatingip_db['floating_network_id']
ext_net = self.get_network(context, ext_net_id)
ext_net_pod = db_api.get_pod_by_name(t_ctx,
ext_net[az_ext.AZ_HINTS][0])
# external network and internal network are in the same pod, no
# need to use bridge network.
if int_net_pod['pod_name'] == ext_net_pod['pod_name']:
client = self._get_client(int_net_pod['pod_name'])
b_ext_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, ext_net_id, ext_net_pod['pod_name'],
t_constants.RT_NETWORK)
self._safe_create_bottom_floatingip(
t_ctx, ext_client, b_ext_net_id, fip_address,
b_ns_bridge_port_id)
self._safe_create_bottom_floatingip(
t_ctx, int_client, int_bridge_net_id,
t_ns_bridge_port['fixed_ips'][0]['ip_address'], b_int_port_id)
t_ctx, int_net_pod, client, b_ext_net_id, fip_address,
b_int_port_id)
return
return res
except Exception:
# NOTE(zhiyuan) currently we just handle floating ip association
# in this function, so when exception occurs, we update floating
# ip object to unset fixed_port_id, fixed_ip_address, router_id
self._disassociate_floatingip(context, _id)
raise
# below handle the case that external network and internal network
# are in different pods
int_client = self._get_client(int_net_pod['pod_name'])
ext_client = self._get_client(ext_net_pod['pod_name'])
ns_bridge_net_name = t_constants.ns_bridge_net_name % project_id
ns_bridge_net = self.get_networks(
context, {'name': [ns_bridge_net_name]})[0]
int_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, ns_bridge_net['id'], int_net_pod['pod_name'],
t_constants.RT_NETWORK)
ext_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, ns_bridge_net['id'], ext_net_pod['pod_name'],
t_constants.RT_NETWORK)
t_pod = db_api.get_top_pod(t_ctx)
t_ns_bridge_port = self._get_bridge_interface(
t_ctx, context, project_id, t_pod, ns_bridge_net['id'],
None, b_int_port_id, False)
port_body = {
'port': {
'tenant_id': project_id,
'admin_state_up': True,
'name': 'ns_bridge_port',
'network_id': ext_bridge_net_id,
'fixed_ips': [{'ip_address': t_ns_bridge_port[
'fixed_ips'][0]['ip_address']}]
}
}
_, b_ns_bridge_port_id = self._prepare_bottom_element(
t_ctx, project_id, ext_net_pod, t_ns_bridge_port,
t_constants.RT_PORT, port_body)
b_ext_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, ext_net_id, ext_net_pod['pod_name'],
t_constants.RT_NETWORK)
self._safe_create_bottom_floatingip(
t_ctx, ext_net_pod, ext_client, b_ext_net_id, fip_address,
b_ns_bridge_port_id)
self._safe_create_bottom_floatingip(
t_ctx, int_net_pod, int_client, int_bridge_net_id,
t_ns_bridge_port['fixed_ips'][0]['ip_address'], b_int_port_id)
def _disassociate_floatingip(self, context, ori_floatingip_db):
if not ori_floatingip_db['port_id']:
# floating ip has not been associated with fixed ip, no
# operation in bottom pod needed
return
t_ctx = t_context.get_context_from_neutron_context(context)
project_id = ori_floatingip_db['tenant_id']
t_int_port_id = ori_floatingip_db['port_id']
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_int_port_id, t_constants.RT_PORT)
if not mappings:
# floating ip in top pod is associated but no mapping between
# top and bottom internal port, this is an inconsistent state,
# but since bottom internal port does not exist, no operation
# in bottom pod is required
LOG.warning(_LI('Internal port associated with floating ip '
'does not exist in bottom pod.'))
return
b_int_net_pod, b_int_port_id = mappings[0]
t_ext_net_id = ori_floatingip_db['floating_network_id']
t_ext_net = self.get_network(context, t_ext_net_id)
b_ext_net_pod = db_api.get_pod_by_name(t_ctx,
t_ext_net[az_ext.AZ_HINTS][0])
b_ext_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, t_ext_net_id, b_ext_net_pod['pod_name'],
t_constants.RT_NETWORK)
# external network and internal network are in the same pod, so
# bridge network is not created in this pod
if b_int_net_pod['pod_name'] == b_ext_net_pod['pod_name']:
b_client = self._get_client(b_int_net_pod['pod_name'])
b_fips = b_client.list_floatingips(
t_ctx,
[{'key': 'floating_ip_address',
'comparator': 'eq',
'value': ori_floatingip_db['floating_ip_address']},
{'key': 'floating_network_id',
'comparator': 'eq',
'value': b_ext_net_id}])
if not b_fips:
return
b_client.update_floatingips(t_ctx, b_fips[0]['id'],
{'floatingip': {'port_id': None}})
return
# below handle the case that external network and internal network
# are in different pods
b_int_client = self._get_client(b_int_net_pod['pod_name'])
b_ext_client = self._get_client(b_ext_net_pod['pod_name'])
ns_bridge_net_name = t_constants.ns_bridge_net_name % project_id
t_ns_bridge_net = self.get_networks(
context, {'name': [ns_bridge_net_name]})[0]
b_int_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, t_ns_bridge_net['id'], b_int_net_pod['pod_name'],
t_constants.RT_NETWORK)
t_pod = db_api.get_top_pod(t_ctx)
t_ns_bridge_port = self._get_bridge_interface(
t_ctx, context, project_id, t_pod, t_ns_bridge_net['id'],
None, b_int_port_id, False)
b_int_fips = b_int_client.list_floatingips(
t_ctx,
[{'key': 'floating_ip_address',
'comparator': 'eq',
'value': t_ns_bridge_port['fixed_ips'][0]['ip_address']},
{'key': 'floating_network_id',
'comparator': 'eq',
'value': b_int_bridge_net_id}])
b_ext_fips = b_ext_client.list_floatingips(
t_ctx,
[{'key': 'floating_ip_address',
'comparator': 'eq',
'value': ori_floatingip_db['floating_ip_address']},
{'key': 'floating_network_id',
'comparator': 'eq',
'value': b_ext_net_id}])
if b_int_fips:
b_int_client.delete_floatingips(
t_ctx, b_int_fips[0]['id'])
if b_ext_fips:
b_ext_client.update_floatingips(
t_ctx, b_ext_fips[0]['id'],
{'floatingip': {'port_id': None}})
# delete bridge port
self.delete_port(context, t_ns_bridge_port['id'], l3_port_check=False)
# for bridge port, we have two resource routing entries, one for bridge
# port in top pod, another for bridge port in bottom pod. calling
# delete_port above will delete bridge port in bottom pod as well as
# routing entry for it, but we also need to remove routing entry for
# bridge port in top pod
# bridge network will be deleted when deleting router
with t_ctx.session.begin():
core.delete_resources(t_ctx, models.ResourceRouting,
[{'key': 'top_id', 'comparator': 'eq',
'value': t_ns_bridge_port['name']}])

View File

@ -17,6 +17,7 @@
import copy
import mock
from mock import patch
import netaddr
import unittest
from sqlalchemy.orm import attributes
@ -67,19 +68,21 @@ BOTTOM1_SUBNETS = []
BOTTOM1_PORTS = []
BOTTOM1_ROUTERS = []
BOTTOM1_SGS = []
BOTTOM1_FIPS = []
BOTTOM2_NETS = []
BOTTOM2_SUBNETS = []
BOTTOM2_PORTS = []
BOTTOM2_ROUTERS = []
BOTTOM2_SGS = []
BOTTOM2_FIPS = []
RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_ROUTERS, TOP_ROUTERPORT,
TOP_SUBNETPOOLS, TOP_SUBNETPOOLPREFIXES, TOP_IPALLOCATIONS,
TOP_VLANALLOCATIONS, TOP_SEGMENTS, TOP_EXTNETS, TOP_FLOATINGIPS,
TOP_SGS, TOP_SG_RULES,
BOTTOM1_NETS, BOTTOM1_SUBNETS, BOTTOM1_PORTS, BOTTOM1_ROUTERS,
BOTTOM1_SGS,
BOTTOM1_SGS, BOTTOM1_FIPS,
BOTTOM2_NETS, BOTTOM2_SUBNETS, BOTTOM2_PORTS, BOTTOM2_ROUTERS,
BOTTOM2_SGS]
BOTTOM2_SGS, BOTTOM2_FIPS]
RES_MAP = {'networks': TOP_NETS,
'subnets': TOP_SUBNETS,
'ports': TOP_PORTS,
@ -155,12 +158,14 @@ class FakeClient(object):
'subnet': BOTTOM1_SUBNETS,
'port': BOTTOM1_PORTS,
'router': BOTTOM1_ROUTERS,
'security_group': BOTTOM1_SGS},
'security_group': BOTTOM1_SGS,
'floatingip': BOTTOM1_FIPS},
'pod_2': {'network': BOTTOM2_NETS,
'subnet': BOTTOM2_SUBNETS,
'port': BOTTOM2_PORTS,
'router': BOTTOM2_ROUTERS,
'security_group': BOTTOM2_SGS}}
'security_group': BOTTOM2_SGS,
'floatingip': BOTTOM2_FIPS}}
def __init__(self, pod_name):
self.pod_name = pod_name
@ -191,10 +196,12 @@ class FakeClient(object):
fixed_ip['ip_address'])
fixed_ips = body[_type].get('fixed_ips', [])
for fixed_ip in fixed_ips:
# just skip ip address check when subnet_id not given
# currently test case doesn't need to cover such situation
if 'subnet_id' not in fixed_ip:
continue
for subnet in self._res_map[self.pod_name]['subnet']:
ip_range = netaddr.IPNetwork(subnet['cidr'])
ip = netaddr.IPAddress(fixed_ip['ip_address'])
if ip in ip_range:
fixed_ip['subnet_id'] = subnet['id']
break
if fixed_ip['ip_address'] in subnet_ips_map.get(
fixed_ip['subnet_id'], set()):
raise q_exceptions.IpAddressInUseClient()
@ -249,7 +256,33 @@ class FakeClient(object):
return self.add_gateway_routers(ctx, args, kwargs)
def create_floatingips(self, ctx, body):
# only for mock purpose
fip = self.create_resources('floatingip', ctx, body)
for key in ['fixed_port_id']:
if key not in fip:
fip[key] = None
return fip
def list_floatingips(self, ctx, filters=None):
filters = filters or []
return_list = []
for fip in self._res_map[self.pod_name]['floatingip']:
is_skip = False
for filter in filters:
if filter['key'] not in fip:
is_skip = True
break
if fip[filter['key']] != filter['value']:
is_skip = True
break
if is_skip:
continue
return_list.append(copy.copy(fip))
return return_list
def update_floatingips(self, ctx, _id, body):
pass
def delete_floatingips(self, ctx, _id):
pass
def create_security_group_rules(self, ctx, body):
@ -350,6 +383,25 @@ def unlink_models(res_list, model_dict, foreign_key, key, link_prop,
return
def update_floatingip(self, context, _id, floatingip):
for fip in TOP_FLOATINGIPS:
if fip['id'] != _id:
continue
update_dict = floatingip['floatingip']
if not floatingip['floatingip']['port_id']:
update_dict['fixed_port_id'] = None
update_dict['fixed_ip_address'] = None
fip.update(update_dict)
return
for port in TOP_PORTS:
if port['id'] != floatingip['floatingip']['port_id']:
continue
update_dict['fixed_port_id'] = port['id']
update_dict[
'fixed_ip_address'] = port['fixed_ips'][0]['ip_address']
fip.update(update_dict)
class FakeQuery(object):
def __init__(self, records, table):
self.records = records
@ -1842,11 +1894,11 @@ class PluginTest(unittest.TestCase,
new=mock.Mock)
@patch.object(l3_db.L3_NAT_dbonly_mixin, 'update_floatingip',
new=mock.Mock)
@patch.object(FakePlugin, '_disassociate_floatingip')
@patch.object(FakePlugin, '_rollback_floatingip_data')
@patch.object(FakeClient, 'create_floatingips')
@patch.object(context, 'get_context_from_neutron_context')
def test_associate_floatingip_port_exception(
self, mock_context, mock_create, mock_disassociate):
self, mock_context, mock_create, mock_rollback):
plugin_path = 'tricircle.tests.unit.network.test_plugin.FakePlugin'
cfg.CONF.set_override('core_plugin', plugin_path)
@ -1865,7 +1917,72 @@ class PluginTest(unittest.TestCase,
self.assertRaises(q_exceptions.ConnectionFailed,
fake_plugin.update_floatingip, q_ctx, fip['id'],
{'floatingip': fip_body})
mock_disassociate.assert_called_once_with(q_ctx, fip['id'])
data = {'fixed_port_id': None,
'fixed_ip_address': None,
'router_id': None}
mock_rollback.assert_called_once_with(q_ctx, fip['id'], data)
# check the association information is cleared
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_port_id'])
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_ip_address'])
self.assertIsNone(TOP_FLOATINGIPS[0]['router_id'])
@patch.object(ipam_non_pluggable_backend.IpamNonPluggableBackend,
'_allocate_specific_ip', new=_allocate_specific_ip)
@patch.object(ipam_non_pluggable_backend.IpamNonPluggableBackend,
'_generate_ip', new=fake_generate_ip)
@patch.object(l3_db.L3_NAT_dbonly_mixin, '_make_router_dict',
new=fake_make_router_dict)
@patch.object(db_base_plugin_common.DbBasePluginCommon,
'_make_subnet_dict', new=fake_make_subnet_dict)
@patch.object(subnet_alloc.SubnetAllocator, '_lock_subnetpool',
new=mock.Mock)
@patch.object(l3_db.L3_NAT_dbonly_mixin, 'update_floatingip',
new=update_floatingip)
@patch.object(FakeClient, 'delete_floatingips')
@patch.object(FakeClient, 'update_floatingips')
@patch.object(context, 'get_context_from_neutron_context')
def test_disassociate_floatingip(self, mock_context, mock_update,
mock_delete):
plugin_path = 'tricircle.tests.unit.network.test_plugin.FakePlugin'
cfg.CONF.set_override('core_plugin', plugin_path)
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
(t_port_id, b_port_id,
fip, e_net) = self._prepare_associate_floatingip_test(t_ctx, q_ctx,
fake_plugin)
# associate floating ip
fip_body = {'port_id': t_port_id}
fake_plugin.update_floatingip(q_ctx, fip['id'],
{'floatingip': fip_body})
bridge_port_name = constants.ns_bridge_port_name % (
e_net['tenant_id'], None, b_port_id)
t_pod = db_api.get_top_pod(t_ctx)
mapping = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, bridge_port_name, t_pod['pod_name'], constants.RT_PORT)
# check routing for bridge port in top pod exists
self.assertIsNotNone(mapping)
# disassociate floating ip
fip_body = {'port_id': None}
fake_plugin.update_floatingip(q_ctx, fip['id'],
{'floatingip': fip_body})
fip_id1 = BOTTOM1_FIPS[0]['id']
fip_id2 = BOTTOM2_FIPS[0]['id']
mock_update.assert_called_once_with(
t_ctx, fip_id2, {'floatingip': {'port_id': None}})
mock_delete.assert_called_once_with(t_ctx, fip_id1)
mapping = db_api.get_bottom_id_by_top_id_pod_name(
t_ctx, bridge_port_name, t_pod['pod_name'], constants.RT_PORT)
# check routing for bridge port in top pod is deleted
self.assertIsNone(mapping)
# check the association information is cleared
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_port_id'])
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_ip_address'])