Merge "Implement floating ip disassociation"
This commit is contained in:
commit
94e1176221
@ -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',
|
||||
|
@ -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']}])
|
||||
|
@ -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'])
|
||||
|
Loading…
x
Reference in New Issue
Block a user