Merge "Implement floating ip disassociation"
This commit is contained in:
commit
94e1176221
@ -122,7 +122,7 @@ class NeutronResourceHandle(ResourceHandle):
|
|||||||
'router': LIST | CREATE | ACTION | UPDATE,
|
'router': LIST | CREATE | ACTION | UPDATE,
|
||||||
'security_group': LIST | CREATE | GET,
|
'security_group': LIST | CREATE | GET,
|
||||||
'security_group_rule': LIST | CREATE | DELETE,
|
'security_group_rule': LIST | CREATE | DELETE,
|
||||||
'floatingip': LIST | CREATE}
|
'floatingip': LIST | CREATE | UPDATE | DELETE}
|
||||||
|
|
||||||
def _get_client(self, cxt):
|
def _get_client(self, cxt):
|
||||||
return q_client.Client('2.0',
|
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.context as t_context
|
||||||
import tricircle.common.exceptions as t_exceptions
|
import tricircle.common.exceptions as t_exceptions
|
||||||
from tricircle.common.i18n import _
|
from tricircle.common.i18n import _
|
||||||
|
from tricircle.common.i18n import _LE
|
||||||
from tricircle.common.i18n import _LI
|
from tricircle.common.i18n import _LI
|
||||||
import tricircle.common.lock_handle as t_lock
|
import tricircle.common.lock_handle as t_lock
|
||||||
from tricircle.common import utils
|
from tricircle.common import utils
|
||||||
@ -54,6 +55,7 @@ from tricircle.common import xrpcapi
|
|||||||
import tricircle.db.api as db_api
|
import tricircle.db.api as db_api
|
||||||
from tricircle.db import core
|
from tricircle.db import core
|
||||||
from tricircle.db import models
|
from tricircle.db import models
|
||||||
|
import tricircle.network.exceptions as t_network_exc
|
||||||
from tricircle.network import security_groups
|
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,
|
def _prepare_top_element(self, t_ctx, q_ctx,
|
||||||
project_id, pod, ele, _type, body):
|
project_id, pod, ele, _type, body):
|
||||||
def list_resources(t_ctx_, q_ctx_, pod_, ele_, _type_):
|
def list_resources(t_ctx_, q_ctx_, pod_, ele_, _type_):
|
||||||
return getattr(self, 'get_%ss' % _type_)(
|
return getattr(super(TricirclePlugin, self),
|
||||||
q_ctx_, filters={'name': ele_['id']})
|
'get_%ss' % _type_)(q_ctx_,
|
||||||
|
filters={'name': [ele_['id']]})
|
||||||
|
|
||||||
def create_resources(t_ctx_, q_ctx_, pod_, body_, _type_):
|
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(
|
return t_lock.get_or_create_element(
|
||||||
t_ctx, q_ctx,
|
t_ctx, q_ctx,
|
||||||
@ -817,7 +821,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
}
|
}
|
||||||
_, port_id = self._prepare_top_element(
|
_, port_id = self._prepare_top_element(
|
||||||
t_ctx, q_ctx, project_id, pod, port_ele, 'port', port_body)
|
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,
|
def _get_bottom_bridge_elements(self, q_ctx, project_id,
|
||||||
pod, t_net, is_external, t_subnet, t_port):
|
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
|
# az hint parameter, so tricircle plugin knows where to create the
|
||||||
# corresponding bottom external network. here we get bottom external
|
# corresponding bottom external network. here we get bottom external
|
||||||
# network ID from resource routing table.
|
# 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_name = network[az_ext.AZ_HINTS][0]
|
||||||
pod = db_api.get_pod_by_name(t_ctx, pod_name)
|
pod = db_api.get_pod_by_name(t_ctx, pod_name)
|
||||||
b_net_id = db_api.get_bottom_id_by_top_id_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
|
# create corresponding bottom router in the pod where external network
|
||||||
# is located.
|
# is located.
|
||||||
t_router = self._get_router(context, router_id)
|
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,
|
body = {'router': {'name': router_id,
|
||||||
'distributed': False}}
|
'distributed': False}}
|
||||||
_, b_router_id = self._prepare_bottom_element(
|
_, b_router_id = self._prepare_bottom_element(
|
||||||
@ -1224,7 +1234,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
return return_info
|
return return_info
|
||||||
|
|
||||||
@staticmethod
|
@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):
|
fip_address, port_id):
|
||||||
try:
|
try:
|
||||||
client.create_floatingips(
|
client.create_floatingips(
|
||||||
@ -1237,127 +1247,270 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
'comparator': 'eq',
|
'comparator': 'eq',
|
||||||
'value': fip_address}])
|
'value': fip_address}])
|
||||||
# NOTE(zhiyuan) if the internal port associated with the existing
|
# NOTE(zhiyuan) if the internal port associated with the existing
|
||||||
# fip is what we expect, just ignore this exception
|
# fip is what we expect, just ignore this exception; or if the
|
||||||
if fips[0].get('port_id') == port_id:
|
# 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
|
pass
|
||||||
|
elif not associated_port_id:
|
||||||
|
client.update_floatingips(t_ctx, fips[0]['id'],
|
||||||
|
{'floatingip': {'port_id': port_id}})
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _disassociate_floatingip(context, _id):
|
def _rollback_floatingip_data(context, _id, org_data):
|
||||||
with context.session.begin():
|
"""Rollback the data of floating ip object to the original one
|
||||||
fip_qry = context.session.query(l3_db.FloatingIP)
|
|
||||||
floating_ips = fip_qry.filter_by(id=_id)
|
:param context: request context
|
||||||
for floating_ip in floating_ips:
|
:param _id: ID of the floating ip
|
||||||
floating_ip.update({'fixed_port_id': None,
|
:param org_data: data of floating ip we rollback to
|
||||||
'fixed_ip_address': None,
|
:return: None
|
||||||
'router_id': 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):
|
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(
|
res = super(TricirclePlugin, self).update_floatingip(
|
||||||
context, _id, floatingip)
|
context, _id, floatingip)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
t_ctx = t_context.get_context_from_neutron_context(context)
|
if floatingip['floatingip']['port_id']:
|
||||||
|
self._associate_floatingip(context, _id, floatingip)
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
int_net_pod, b_int_port_id = mappings[0]
|
self._disassociate_floatingip(context, org_floatingip_dict)
|
||||||
ext_net_id = floatingip_db['floating_network_id']
|
return res
|
||||||
ext_net = self.get_network(context, ext_net_id)
|
except Exception as e:
|
||||||
ext_net_pod = db_api.get_pod_by_name(t_ctx,
|
# NOTE(zhiyuan) when exception occurs, we update floating ip object
|
||||||
ext_net[az_ext.AZ_HINTS][0])
|
# 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
|
def _associate_floatingip(self, context, _id, floatingip):
|
||||||
# need to use bridge network.
|
t_ctx = t_context.get_context_from_neutron_context(context)
|
||||||
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)
|
|
||||||
|
|
||||||
return res
|
fip = floatingip['floatingip']
|
||||||
|
floatingip_db = self._get_floatingip(context, _id)
|
||||||
# below handle the case that external network and internal network
|
int_port_id = fip['port_id']
|
||||||
# are in different pods
|
project_id = floatingip_db['tenant_id']
|
||||||
int_client = self._get_client(int_net_pod['pod_name'])
|
fip_address = floatingip_db['floating_ip_address']
|
||||||
ext_client = self._get_client(ext_net_pod['pod_name'])
|
mappings = db_api.get_bottom_mappings_by_top_id(
|
||||||
ns_bridge_net_name = t_constants.ns_bridge_net_name % project_id
|
t_ctx, int_port_id, t_constants.RT_PORT)
|
||||||
ns_bridge_net = self.get_networks(
|
if not mappings:
|
||||||
context, {'name': [ns_bridge_net_name]})[0]
|
int_port = self.get_port(context, int_port_id)
|
||||||
int_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
|
int_network = self.get_network(context, int_port['network_id'])
|
||||||
t_ctx, ns_bridge_net['id'], int_net_pod['pod_name'],
|
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)
|
t_constants.RT_NETWORK)
|
||||||
ext_bridge_net_id = db_api.get_bottom_id_by_top_id_pod_name(
|
b_int_port_body = {
|
||||||
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': {
|
'port': {
|
||||||
'tenant_id': project_id,
|
'tenant_id': project_id,
|
||||||
'admin_state_up': True,
|
'admin_state_up': True,
|
||||||
'name': 'ns_bridge_port',
|
'name': int_port['id'],
|
||||||
'network_id': ext_bridge_net_id,
|
'network_id': b_int_net_id,
|
||||||
'fixed_ips': [{'ip_address': t_ns_bridge_port[
|
'mac_address': int_port['mac_address'],
|
||||||
'fixed_ips'][0]['ip_address']}]
|
'fixed_ips': [{'ip_address': int_port['fixed_ips'][0][
|
||||||
|
'ip_address']}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, b_ns_bridge_port_id = self._prepare_bottom_element(
|
# TODO(zhiyuan) handle DHCP port ip address conflict problem
|
||||||
t_ctx, project_id, ext_net_pod, t_ns_bridge_port,
|
_, b_int_port_id = self._prepare_bottom_element(
|
||||||
t_constants.RT_PORT, port_body)
|
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(
|
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_ctx, ext_net_id, ext_net_pod['pod_name'],
|
||||||
t_constants.RT_NETWORK)
|
t_constants.RT_NETWORK)
|
||||||
self._safe_create_bottom_floatingip(
|
self._safe_create_bottom_floatingip(
|
||||||
t_ctx, ext_client, b_ext_net_id, fip_address,
|
t_ctx, int_net_pod, client, b_ext_net_id, fip_address,
|
||||||
b_ns_bridge_port_id)
|
b_int_port_id)
|
||||||
self._safe_create_bottom_floatingip(
|
return
|
||||||
t_ctx, int_client, int_bridge_net_id,
|
|
||||||
t_ns_bridge_port['fixed_ips'][0]['ip_address'], b_int_port_id)
|
|
||||||
|
|
||||||
return res
|
# below handle the case that external network and internal network
|
||||||
except Exception:
|
# are in different pods
|
||||||
# NOTE(zhiyuan) currently we just handle floating ip association
|
int_client = self._get_client(int_net_pod['pod_name'])
|
||||||
# in this function, so when exception occurs, we update floating
|
ext_client = self._get_client(ext_net_pod['pod_name'])
|
||||||
# ip object to unset fixed_port_id, fixed_ip_address, router_id
|
ns_bridge_net_name = t_constants.ns_bridge_net_name % project_id
|
||||||
self._disassociate_floatingip(context, _id)
|
ns_bridge_net = self.get_networks(
|
||||||
raise
|
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 copy
|
||||||
import mock
|
import mock
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
import netaddr
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from sqlalchemy.orm import attributes
|
from sqlalchemy.orm import attributes
|
||||||
@ -67,19 +68,21 @@ BOTTOM1_SUBNETS = []
|
|||||||
BOTTOM1_PORTS = []
|
BOTTOM1_PORTS = []
|
||||||
BOTTOM1_ROUTERS = []
|
BOTTOM1_ROUTERS = []
|
||||||
BOTTOM1_SGS = []
|
BOTTOM1_SGS = []
|
||||||
|
BOTTOM1_FIPS = []
|
||||||
BOTTOM2_NETS = []
|
BOTTOM2_NETS = []
|
||||||
BOTTOM2_SUBNETS = []
|
BOTTOM2_SUBNETS = []
|
||||||
BOTTOM2_PORTS = []
|
BOTTOM2_PORTS = []
|
||||||
BOTTOM2_ROUTERS = []
|
BOTTOM2_ROUTERS = []
|
||||||
BOTTOM2_SGS = []
|
BOTTOM2_SGS = []
|
||||||
|
BOTTOM2_FIPS = []
|
||||||
RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_ROUTERS, TOP_ROUTERPORT,
|
RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_ROUTERS, TOP_ROUTERPORT,
|
||||||
TOP_SUBNETPOOLS, TOP_SUBNETPOOLPREFIXES, TOP_IPALLOCATIONS,
|
TOP_SUBNETPOOLS, TOP_SUBNETPOOLPREFIXES, TOP_IPALLOCATIONS,
|
||||||
TOP_VLANALLOCATIONS, TOP_SEGMENTS, TOP_EXTNETS, TOP_FLOATINGIPS,
|
TOP_VLANALLOCATIONS, TOP_SEGMENTS, TOP_EXTNETS, TOP_FLOATINGIPS,
|
||||||
TOP_SGS, TOP_SG_RULES,
|
TOP_SGS, TOP_SG_RULES,
|
||||||
BOTTOM1_NETS, BOTTOM1_SUBNETS, BOTTOM1_PORTS, BOTTOM1_ROUTERS,
|
BOTTOM1_NETS, BOTTOM1_SUBNETS, BOTTOM1_PORTS, BOTTOM1_ROUTERS,
|
||||||
BOTTOM1_SGS,
|
BOTTOM1_SGS, BOTTOM1_FIPS,
|
||||||
BOTTOM2_NETS, BOTTOM2_SUBNETS, BOTTOM2_PORTS, BOTTOM2_ROUTERS,
|
BOTTOM2_NETS, BOTTOM2_SUBNETS, BOTTOM2_PORTS, BOTTOM2_ROUTERS,
|
||||||
BOTTOM2_SGS]
|
BOTTOM2_SGS, BOTTOM2_FIPS]
|
||||||
RES_MAP = {'networks': TOP_NETS,
|
RES_MAP = {'networks': TOP_NETS,
|
||||||
'subnets': TOP_SUBNETS,
|
'subnets': TOP_SUBNETS,
|
||||||
'ports': TOP_PORTS,
|
'ports': TOP_PORTS,
|
||||||
@ -155,12 +158,14 @@ class FakeClient(object):
|
|||||||
'subnet': BOTTOM1_SUBNETS,
|
'subnet': BOTTOM1_SUBNETS,
|
||||||
'port': BOTTOM1_PORTS,
|
'port': BOTTOM1_PORTS,
|
||||||
'router': BOTTOM1_ROUTERS,
|
'router': BOTTOM1_ROUTERS,
|
||||||
'security_group': BOTTOM1_SGS},
|
'security_group': BOTTOM1_SGS,
|
||||||
|
'floatingip': BOTTOM1_FIPS},
|
||||||
'pod_2': {'network': BOTTOM2_NETS,
|
'pod_2': {'network': BOTTOM2_NETS,
|
||||||
'subnet': BOTTOM2_SUBNETS,
|
'subnet': BOTTOM2_SUBNETS,
|
||||||
'port': BOTTOM2_PORTS,
|
'port': BOTTOM2_PORTS,
|
||||||
'router': BOTTOM2_ROUTERS,
|
'router': BOTTOM2_ROUTERS,
|
||||||
'security_group': BOTTOM2_SGS}}
|
'security_group': BOTTOM2_SGS,
|
||||||
|
'floatingip': BOTTOM2_FIPS}}
|
||||||
|
|
||||||
def __init__(self, pod_name):
|
def __init__(self, pod_name):
|
||||||
self.pod_name = pod_name
|
self.pod_name = pod_name
|
||||||
@ -191,10 +196,12 @@ class FakeClient(object):
|
|||||||
fixed_ip['ip_address'])
|
fixed_ip['ip_address'])
|
||||||
fixed_ips = body[_type].get('fixed_ips', [])
|
fixed_ips = body[_type].get('fixed_ips', [])
|
||||||
for fixed_ip in fixed_ips:
|
for fixed_ip in fixed_ips:
|
||||||
# just skip ip address check when subnet_id not given
|
for subnet in self._res_map[self.pod_name]['subnet']:
|
||||||
# currently test case doesn't need to cover such situation
|
ip_range = netaddr.IPNetwork(subnet['cidr'])
|
||||||
if 'subnet_id' not in fixed_ip:
|
ip = netaddr.IPAddress(fixed_ip['ip_address'])
|
||||||
continue
|
if ip in ip_range:
|
||||||
|
fixed_ip['subnet_id'] = subnet['id']
|
||||||
|
break
|
||||||
if fixed_ip['ip_address'] in subnet_ips_map.get(
|
if fixed_ip['ip_address'] in subnet_ips_map.get(
|
||||||
fixed_ip['subnet_id'], set()):
|
fixed_ip['subnet_id'], set()):
|
||||||
raise q_exceptions.IpAddressInUseClient()
|
raise q_exceptions.IpAddressInUseClient()
|
||||||
@ -249,7 +256,33 @@ class FakeClient(object):
|
|||||||
return self.add_gateway_routers(ctx, args, kwargs)
|
return self.add_gateway_routers(ctx, args, kwargs)
|
||||||
|
|
||||||
def create_floatingips(self, ctx, body):
|
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
|
pass
|
||||||
|
|
||||||
def create_security_group_rules(self, ctx, body):
|
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
|
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):
|
class FakeQuery(object):
|
||||||
def __init__(self, records, table):
|
def __init__(self, records, table):
|
||||||
self.records = records
|
self.records = records
|
||||||
@ -1842,11 +1894,11 @@ class PluginTest(unittest.TestCase,
|
|||||||
new=mock.Mock)
|
new=mock.Mock)
|
||||||
@patch.object(l3_db.L3_NAT_dbonly_mixin, 'update_floatingip',
|
@patch.object(l3_db.L3_NAT_dbonly_mixin, 'update_floatingip',
|
||||||
new=mock.Mock)
|
new=mock.Mock)
|
||||||
@patch.object(FakePlugin, '_disassociate_floatingip')
|
@patch.object(FakePlugin, '_rollback_floatingip_data')
|
||||||
@patch.object(FakeClient, 'create_floatingips')
|
@patch.object(FakeClient, 'create_floatingips')
|
||||||
@patch.object(context, 'get_context_from_neutron_context')
|
@patch.object(context, 'get_context_from_neutron_context')
|
||||||
def test_associate_floatingip_port_exception(
|
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'
|
plugin_path = 'tricircle.tests.unit.network.test_plugin.FakePlugin'
|
||||||
cfg.CONF.set_override('core_plugin', plugin_path)
|
cfg.CONF.set_override('core_plugin', plugin_path)
|
||||||
|
|
||||||
@ -1865,7 +1917,72 @@ class PluginTest(unittest.TestCase,
|
|||||||
self.assertRaises(q_exceptions.ConnectionFailed,
|
self.assertRaises(q_exceptions.ConnectionFailed,
|
||||||
fake_plugin.update_floatingip, q_ctx, fip['id'],
|
fake_plugin.update_floatingip, q_ctx, fip['id'],
|
||||||
{'floatingip': fip_body})
|
{'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
|
# check the association information is cleared
|
||||||
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_port_id'])
|
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_port_id'])
|
||||||
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_ip_address'])
|
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_ip_address'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user