Implement floating ip disassociation

According to the current design of cross pod L3 networking,
user needs to specify a pod when creating an external network
and the external network will be located in this pod. For VMs
located in other pods to access the external network, we need
a bridge network to connect these pods.

We assign the bridge network a CIDR allocated from a CIDR
pool. In the pod hosting the VM, say Pod_vm, a bridge external
network is created with the CIDR, so we can allocate a floating
ip from the CIDR and bind it to the VM port. In the pod hosting
the real external network(say "real" here to distinguish with
the bridge external network), say Pod_extnet, a bridge internal
network is created with the CIDR, so we can create a port with
the same ip as floating ip in Pod_vm, and bind it to the real
floating ip in Pod_extnet. With the bridge network, via two-step
DNAT, the VM can be accessed from the real external network.

For example, let's say we have an internal network with CIDR
10.0.1.0/24 and an external network with CIDR 162.3.124.0/24,
the CIDR of bridge network is 100.0.1.0/24, when binding a VM
ip 10.0.1.4 to a floating ip 162.3.124.5, the VM ip is first
bound to 100.0.1.4, which is allocated from 100.0.1.0/24, then
100.0.1.4 is bound to 162.3.124.5.

In the case that VM and external network are in the same pod,
bridge network is not needed.

So plugin needs to distinguish these two cases when handling
floating ip disassociation. If VM and external network are in
the same pod, plugin only disassociates the binding; if they
are in different pods, plugin also needs to release the ip
allocated from the bridge network.

Change-Id: Ibae353ec81aceda53016b6ea8aba1872d6d514be
This commit is contained in:
zhiyuan_cai 2016-05-06 17:35:05 +08:00
parent befab65951
commit 08be0eba7e
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'])