Move dhcp port handle to the helper module

1. What is the problem
Currently dhcp port handle is only done in the Nova_apigw. Please
refer to our design documnet[1] to see why dhcp port needs special
process, which is discussed in section 7.4.10. Since we are going
to support one top network to spread into different AZs(availability
zones), routers will be created in different AZs. For shared VLAN
type network, bottom networks in different AZs are actually in the
same broadcast domain, so router gateway IPs should be different
in different AZs, otherwise we have two interfaces with the same
IP in the same broadcase domain. Thus, we need to allocate one IP
for each bottom router and the Tricircle plugin needs to handle
dhcp port creation.

2. What is the solution to the problem
Reconstrut the code to move dhcp port handle from the Nova_apigw
to the helper module, so both the Nova_apigw and the Tricircle
plugin can use.

3. What the features need to be implemented to the Tricircle
   to realize the solution
No new feature introuduced.

[1] https://docs.google.com/document/d/18kZZ1snMOCD9IQvUKI5NVDzSASpw-QKj7l2zNqMEd3g

Change-Id: I2525a7a18761ef4aa8c6e743cb46ed238a313731
This commit is contained in:
zhiyuan_cai 2016-07-22 13:56:22 +08:00
parent 1c78317e02
commit de082eff4f
8 changed files with 338 additions and 303 deletions

View File

@ -18,6 +18,7 @@ Jinja2>=2.8 # BSD License (3 clause)
keystonemiddleware!=4.1.0,!=4.5.0,>=4.0.0 # Apache-2.0
netaddr!=0.7.16,>=0.7.12 # BSD
netifaces>=0.10.4 # MIT
neutron-lib>=0.2.0 # Apache-2.0
retrying!=1.3.0,>=1.2.3 # Apache-2.0
SQLAlchemy<1.1.0,>=1.0.10 # MIT
WebOb>=1.2.3 # MIT

View File

@ -57,6 +57,8 @@ ns_bridge_subnet_name = 'ns_bridge_subnet_%s' # project_id
# for floating ip port: project_id None b_internal_port_id
ns_bridge_port_name = 'ns_bridge_port_%s_%s_%s'
dhcp_port_name = 'dhcp_port_%s' # subnet_id
MAX_INT = 0x7FFFFFFF
expire_time = datetime.datetime(2000, 1, 1)

View File

@ -15,13 +15,19 @@
from neutron_lib import constants
from neutron.extensions import external_net
import neutron.plugins.common.constants as p_constants
import tricircle.common.client as t_client
from tricircle.common import client
import tricircle.common.constants as t_constants
import tricircle.common.context as t_context
import tricircle.common.lock_handle as t_lock
from tricircle.common import utils
import tricircle.db.api as db_api
from tricircle.db import core
from tricircle.db import models
# manually define these constants to avoid depending on neutron repos
EXTERNAL = 'router:external' # neutron.extensions.external_net.EXTERNAL
TYPE_VLAN = 'vlan' # neutron.plugins.common.constants.TYPE_VLAN
class NetworkHelper(object):
@ -31,16 +37,16 @@ class NetworkHelper(object):
@staticmethod
def _transfer_network_type(network_type):
network_type_map = {t_constants.NT_SHARED_VLAN: p_constants.TYPE_VLAN}
network_type_map = {t_constants.NT_SHARED_VLAN: TYPE_VLAN}
return network_type_map.get(network_type, network_type)
def _get_client(self, pod_name=None):
if not pod_name:
if t_constants.TOP not in self.clients:
self.clients[t_constants.TOP] = t_client.Client()
self.clients[t_constants.TOP] = client.Client()
return self.clients[t_constants.TOP]
if pod_name not in self.clients:
self.clients[pod_name] = t_client.Client(pod_name)
self.clients[pod_name] = client.Client(pod_name)
return self.clients[pod_name]
# operate top resource
@ -97,7 +103,7 @@ class NetworkHelper(object):
:param _type: type of the resource
:param body: request body to create resource
:return: boolean value indicating whether the resource is newly
created or already exists and id of the resource
created or already exists and id of the resource
"""
if self.call_obj:
return self._prepare_top_element_by_call(
@ -117,9 +123,9 @@ class NetworkHelper(object):
:param t_net_id: top bridge network id
:param b_router_id: bottom router id
:param b_port_id: needed when creating bridge interface for south-
north network, id of the internal port bound to floating ip
north network, id of the internal port bound to floating ip
:param is_ew: create the bridge interface for east-west network or
south-north network
south-north network
:return: bridge interface id
"""
if is_ew:
@ -160,7 +166,7 @@ class NetworkHelper(object):
:param _type: type of the resource
:param body: request body to create resource
:return: boolean value indicating whether the resource is newly
created or already exists and id of the resource
created or already exists and id of the resource
"""
def list_resources(t_ctx_, q_ctx, pod_, ele_, _type_):
client = self._get_client(pod_['pod_name'])
@ -181,30 +187,42 @@ class NetworkHelper(object):
project_id, pod, ele, _type, body,
list_resources, create_resources)
def get_bottom_elements(self, t_ctx, project_id, pod,
t_net, t_subnet, t_port):
"""Get or create bottom network, subnet and port
@staticmethod
def get_create_network_body(project_id, network):
"""Get request body to create bottom network
:param t_ctx: tricircle context
:param project_id: project id
:param pod: dict of bottom pod
:param t_net: dict of top network
:param t_subnet: dict of top subnet
:param t_port: dict of top port
:return: bottom port id
:param network: top network dict
:return: request body to create bottom network
"""
net_body = {
body = {
'network': {
'tenant_id': project_id,
'name': utils.get_bottom_network_name(t_net),
'name': utils.get_bottom_network_name(network),
'admin_state_up': True
}
}
_, net_id = self.prepare_bottom_element(
t_ctx, project_id, pod, t_net, 'network', net_body)
subnet_body = {
network_type = network.get('provider:network_type')
if network_type == t_constants.NT_SHARED_VLAN:
body['network']['provider:network_type'] = 'vlan'
body['network']['provider:physical_network'] = network[
'provider:physical_network']
body['network']['provider:segmentation_id'] = network[
'provider:segmentation_id']
return body
@staticmethod
def get_create_subnet_body(project_id, t_subnet, b_net_id):
"""Get request body to create bottom subnet
:param project_id: project id
:param t_subnet: top subnet dict
:param b_net_id: bottom network id
:return: request body to create bottom subnet
"""
body = {
'subnet': {
'network_id': net_id,
'network_id': b_net_id,
'name': t_subnet['id'],
'ip_version': t_subnet['ip_version'],
'cidr': t_subnet['cidr'],
@ -214,22 +232,82 @@ class NetworkHelper(object):
'tenant_id': project_id
}
}
_, subnet_id = self.prepare_bottom_element(
t_ctx, project_id, pod, t_subnet, 'subnet', subnet_body)
port_body = {
return body
@staticmethod
def get_create_port_body(project_id, t_port, subnet_map, b_net_id,
b_security_group_ids=None):
"""Get request body to create bottom port
:param project_id: project id
:param t_port: top port dict
:param subnet_map: dict with top subnet id as key and bottom subnet
id as value
:param b_net_id: bottom network id
:param security_group_ids: list of bottom security group id
:return: request body to create bottom port
"""
b_fixed_ips = []
for ip in t_port['fixed_ips']:
b_ip = {'subnet_id': subnet_map[ip['subnet_id']],
'ip_address': ip['ip_address']}
b_fixed_ips.append(b_ip)
body = {
'port': {
'network_id': net_id,
'name': t_port['id'],
'tenant_id': project_id,
'admin_state_up': True,
'fixed_ips': [
{'subnet_id': subnet_id,
'ip_address': t_port['fixed_ips'][0]['ip_address']}],
'mac_address': t_port['mac_address']
'name': t_port['id'],
'network_id': b_net_id,
'mac_address': t_port['mac_address'],
'fixed_ips': b_fixed_ips
}
}
_, port_id = self.prepare_bottom_element(
t_ctx, project_id, pod, t_port, 'port', port_body)
return port_id
if b_security_group_ids:
body['port']['security_groups'] = b_security_group_ids
return body
def prepare_bottom_network_subnets(self, t_ctx, project_id, pod,
t_net, t_subnets):
"""Get or create bottom network, subnet and dhcp port
:param t_ctx: tricircle context
:param project_id: project id
:param pod: dict of bottom pod
:param t_net: dict of top network
:param t_subnets: list of top subnet dict
:return: bottom network id and a dict with top subnet id as key,
bottom subnet id as value
"""
# network
net_body = self.get_create_network_body(project_id, t_net)
if net_body['network'].get('provider:network_type'):
# if network type specified, we need to switch to admin account
admin_context = t_context.get_admin_context()
_, b_net_id = self.prepare_bottom_element(
admin_context, project_id, pod, t_net, t_constants.RT_NETWORK,
net_body)
else:
_, b_net_id = self.prepare_bottom_element(
t_ctx, project_id, pod, t_net, t_constants.RT_NETWORK,
net_body)
# subnet
subnet_map = {}
for subnet in t_subnets:
subnet_body = self.get_create_subnet_body(
project_id, subnet, b_net_id)
_, b_subnet_id = self.prepare_bottom_element(
t_ctx, project_id, pod, subnet, t_constants.RT_SUBNET,
subnet_body)
subnet_map[subnet['id']] = b_subnet_id
# dhcp port
for t_subnet_id, b_subnet_id in subnet_map.iteritems():
self.prepare_dhcp_port(t_ctx, project_id, pod, t_net['id'],
t_subnet_id, b_net_id, b_subnet_id)
return b_net_id, subnet_map
def get_bottom_bridge_elements(self, t_ctx, project_id,
pod, t_net, is_external, t_subnet, t_port):
@ -240,10 +318,12 @@ class NetworkHelper(object):
:param pod: dict of bottom pod
:param t_net: dict of top bridge network
:param is_external: whether the bottom network should be created as
an external network, this is True for south-north case
an external network, this is True for south-north case
:param t_subnet: dict of top bridge subnet
:param t_port: dict of top bridge port
:return:
:return: tuple (boolean value indicating whether the resource is newly
created or already exists, bottom port id, bottom subnet id,
bottom network id)
"""
net_body = {'network': {
'tenant_id': project_id,
@ -254,7 +334,7 @@ class NetworkHelper(object):
'provider:segmentation_id': t_net['provider:segmentation_id'],
'admin_state_up': True}}
if is_external:
net_body['network'][external_net.EXTERNAL] = True
net_body['network'][EXTERNAL] = True
_, b_net_id = self.prepare_bottom_element(
t_ctx, project_id, pod, t_net, 'network', net_body)
@ -293,3 +373,134 @@ class NetworkHelper(object):
return is_new, b_port_id, b_subnet_id, b_net_id
else:
return None, None, b_subnet_id, b_net_id
@staticmethod
def _get_create_dhcp_port_body(project_id, port, b_subnet_id,
b_net_id):
body = {
'port': {
'tenant_id': project_id,
'admin_state_up': True,
'name': port['id'],
'network_id': b_net_id,
'fixed_ips': [
{'subnet_id': b_subnet_id,
'ip_address': port['fixed_ips'][0]['ip_address']}
],
'mac_address': port['mac_address'],
'binding:profile': {},
'device_id': 'reserved_dhcp_port',
'device_owner': 'network:dhcp',
}
}
return body
def prepare_dhcp_port(self, ctx, project_id, b_pod, t_net_id, t_subnet_id,
b_net_id, b_subnet_id):
"""Create top dhcp port and map it to bottom dhcp port
:param ctx: tricircle context
:param project_id: project id
:param b_pod: dict of bottom pod
:param t_net_id: top network id
:param t_subnet_id: top subnet id
:param b_net_id: bottom network id
:param b_subnet_id: bottom subnet id
:return: None
"""
t_client = self._get_client()
b_client = self._get_client(b_pod['pod_name'])
t_dhcp_name = t_constants.dhcp_port_name % t_subnet_id
t_dhcp_port_body = {
'port': {
'tenant_id': project_id,
'admin_state_up': True,
'network_id': t_net_id,
'name': t_dhcp_name,
'binding:profile': {},
'device_id': 'reserved_dhcp_port',
'device_owner': 'network:dhcp',
}
}
if self.call_obj:
t_dhcp_port_body['port'].update(
{'mac_address': constants.ATTR_NOT_SPECIFIED,
'fixed_ips': constants.ATTR_NOT_SPECIFIED})
# NOTE(zhiyuan) for one subnet in different pods, we just create
# one dhcp port. though dhcp port in different pods will have
# the same IP, each dnsmasq daemon only takes care of VM IPs in
# its own pod, VM will not receive incorrect dhcp response
_, t_dhcp_port_id = self.prepare_top_element(
ctx, None, project_id, db_api.get_top_pod(ctx),
{'id': t_dhcp_name}, t_constants.RT_PORT, t_dhcp_port_body)
t_dhcp_port = t_client.get_ports(ctx, t_dhcp_port_id)
mappings = db_api.get_bottom_mappings_by_top_id(
ctx, t_dhcp_port['id'], t_constants.RT_PORT)
pod_list = [mapping[0]['pod_id'] for mapping in mappings]
if b_pod['pod_id'] in pod_list:
# mapping exists, skip this subnet
return
dhcp_port_filters = [
{'key': 'device_owner', 'comparator': 'eq',
'value': 'network:dhcp'},
{'key': 'network_id', 'comparator': 'eq',
'value': b_net_id},
]
dhcp_port_body = self._get_create_dhcp_port_body(
project_id, t_dhcp_port, b_subnet_id, b_net_id)
t_dhcp_ip = t_dhcp_port['fixed_ips'][0]['ip_address']
b_dhcp_port = None
try:
b_dhcp_port = b_client.create_ports(ctx, dhcp_port_body)
except Exception:
# examine if we conflicted with a dhcp port which was
# automatically created by bottom pod
b_dhcp_ports = b_client.list_ports(ctx,
dhcp_port_filters)
dhcp_port_match = False
for dhcp_port in b_dhcp_ports:
subnet_id = dhcp_port['fixed_ips'][0]['subnet_id']
ip = dhcp_port['fixed_ips'][0]['ip_address']
if b_subnet_id == subnet_id and t_dhcp_ip == ip:
with ctx.session.begin():
core.create_resource(
ctx, models.ResourceRouting,
{'top_id': t_dhcp_port['id'],
'bottom_id': dhcp_port['id'],
'pod_id': b_pod['pod_id'],
'project_id': project_id,
'resource_type': t_constants.RT_PORT})
dhcp_port_match = True
break
if not dhcp_port_match:
# so we didn't conflict with a dhcp port, raise exception
raise
if b_dhcp_port:
with ctx.session.begin():
core.create_resource(ctx, models.ResourceRouting,
{'top_id': t_dhcp_port['id'],
'bottom_id': b_dhcp_port['id'],
'pod_id': b_pod['pod_id'],
'project_id': project_id,
'resource_type': t_constants.RT_PORT})
# there is still one thing to do, there may be other dhcp ports
# created by bottom pod, we need to delete them
b_dhcp_ports = b_client.list_ports(ctx,
dhcp_port_filters)
remove_port_list = []
for dhcp_port in b_dhcp_ports:
subnet_id = dhcp_port['fixed_ips'][0]['subnet_id']
ip = dhcp_port['fixed_ips'][0]['ip_address']
if b_subnet_id == subnet_id and t_dhcp_ip != ip:
remove_port_list.append(dhcp_port['id'])
for dhcp_port_id in remove_port_list:
# NOTE(zhiyuan) dhcp agent will receive this port-delete
# notification and re-configure dhcp so our newly created
# dhcp port can be used
b_client.delete_ports(ctx, dhcp_port_id)

View File

@ -767,11 +767,6 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return net, subnet
def _get_bottom_elements(self, t_ctx, project_id, pod,
t_net, t_subnet, t_port):
return self.helper.get_bottom_elements(
t_ctx, project_id, pod, t_net, t_subnet, t_port)
def _get_bridge_interface(self, t_ctx, q_ctx, project_id, pod,
t_net_id, b_router_id, b_port_id, is_ew):
port_id = self.helper.get_bridge_interface(t_ctx, q_ctx, project_id,

View File

@ -37,6 +37,7 @@ from tricircle.common import xrpcapi
import tricircle.db.api as db_api
from tricircle.db import core
from tricircle.db import models
from tricircle.network import helper
LOG = logging.getLogger(__name__)
@ -49,6 +50,7 @@ class ServerController(rest.RestController):
def __init__(self, project_id):
self.project_id = project_id
self.clients = {constants.TOP: t_client.Client()}
self.helper = helper.NetworkHelper()
self.xjob_handler = xrpcapi.XJobAPI()
def _get_client(self, pod_name=constants.TOP):
@ -300,234 +302,30 @@ class ServerController(rest.RestController):
self.project_id, pod, {'id': _id},
_type, list_resources)
def _get_create_network_body(self, network):
body = {
'network': {
'tenant_id': self.project_id,
'name': utils.get_bottom_network_name(network),
'admin_state_up': True
}
}
network_type = network.get('provider:network_type')
if network_type == constants.NT_SHARED_VLAN:
body['network']['provider:network_type'] = 'vlan'
body['network']['provider:physical_network'] = network[
'provider:physical_network']
body['network']['provider:segmentation_id'] = network[
'provider:segmentation_id']
return body
def _get_create_subnet_body(self, subnet, bottom_net_id):
body = {
'subnet': {
'network_id': bottom_net_id,
'name': subnet['id'],
'ip_version': subnet['ip_version'],
'cidr': subnet['cidr'],
'gateway_ip': subnet['gateway_ip'],
'allocation_pools': subnet['allocation_pools'],
'enable_dhcp': subnet['enable_dhcp'],
'tenant_id': self.project_id
}
}
return body
def _get_create_port_body(self, port, subnet_map, bottom_net_id,
security_group_ids=None):
bottom_fixed_ips = []
for ip in port['fixed_ips']:
bottom_ip = {'subnet_id': subnet_map[ip['subnet_id']],
'ip_address': ip['ip_address']}
bottom_fixed_ips.append(bottom_ip)
body = {
'port': {
'tenant_id': self.project_id,
'admin_state_up': True,
'name': port['id'],
'network_id': bottom_net_id,
'mac_address': port['mac_address'],
'fixed_ips': bottom_fixed_ips
}
}
if security_group_ids:
body['port']['security_groups'] = security_group_ids
return body
def _get_create_dhcp_port_body(self, port, bottom_subnet_id,
bottom_net_id):
body = {
'port': {
'tenant_id': self.project_id,
'admin_state_up': True,
'name': port['id'],
'network_id': bottom_net_id,
'fixed_ips': [
{'subnet_id': bottom_subnet_id,
'ip_address': port['fixed_ips'][0]['ip_address']}
],
'mac_address': port['mac_address'],
'binding:profile': {},
'device_id': 'reserved_dhcp_port',
'device_owner': 'network:dhcp',
}
}
return body
def _prepare_neutron_element(self, context, pod, ele, _type, body):
def list_resources(t_ctx, q_ctx, pod_, ele_, _type_):
client = self._get_client(pod_['pod_name'])
if _type_ == constants.RT_NETWORK:
value = utils.get_bottom_network_name(ele_)
else:
value = ele_['id']
return client.list_resources(
_type_, t_ctx,
[{'key': 'name', 'comparator': 'eq',
'value': value}])
def create_resources(t_ctx, q_ctx, pod_, body_, _type_):
client = self._get_client(pod_['pod_name'])
return client.create_resources(_type_, t_ctx, body_)
# we don't need neutron context, so pass None
return t_lock.get_or_create_element(
context, None, self.project_id, pod, ele, _type, body,
list_resources, create_resources)
def _handle_network(self, context, pod, net, subnets, port=None,
top_sg_ids=None, bottom_sg_ids=None):
# network
net_body = self._get_create_network_body(net)
if net_body['network'].get('provider:network_type'):
# if network type specified, we need to switch to admin account
admin_context = t_context.get_admin_context()
_, bottom_net_id = self._prepare_neutron_element(
admin_context, pod, net, 'network', net_body)
else:
_, bottom_net_id = self._prepare_neutron_element(
context, pod, net, 'network', net_body)
(bottom_net_id,
subnet_map) = self.helper.prepare_bottom_network_subnets(
context, self.project_id, pod, net, subnets)
# subnet
subnet_map = {}
for subnet in subnets:
subnet_body = self._get_create_subnet_body(subnet, bottom_net_id)
_, bottom_subnet_id = self._prepare_neutron_element(
context, pod, subnet, 'subnet', subnet_body)
subnet_map[subnet['id']] = bottom_subnet_id
top_client = self._get_client()
top_port_body = {'port': {'network_id': net['id'],
'admin_state_up': True}}
if top_sg_ids:
top_port_body['port']['security_groups'] = top_sg_ids
# dhcp port
client = self._get_client(pod['pod_name'])
t_dhcp_port_filters = [
{'key': 'device_owner', 'comparator': 'eq',
'value': 'network:dhcp'},
{'key': 'network_id', 'comparator': 'eq',
'value': net['id']},
]
b_dhcp_port_filters = [
{'key': 'device_owner', 'comparator': 'eq',
'value': 'network:dhcp'},
{'key': 'network_id', 'comparator': 'eq',
'value': bottom_net_id},
]
top_dhcp_port_body = {
'port': {
'tenant_id': self.project_id,
'admin_state_up': True,
'name': 'dhcp_port',
'network_id': net['id'],
'binding:profile': {},
'device_id': 'reserved_dhcp_port',
'device_owner': 'network:dhcp',
}
}
t_dhcp_ports = top_client.list_ports(context, t_dhcp_port_filters)
t_subnet_dhcp_map = {}
for dhcp_port in t_dhcp_ports:
subnet_id = dhcp_port['fixed_ips'][0]['subnet_id']
t_subnet_dhcp_map[subnet_id] = dhcp_port
for t_subnet_id, b_subnet_id in subnet_map.iteritems():
if t_subnet_id in t_subnet_dhcp_map:
t_dhcp_port = t_subnet_dhcp_map[t_subnet_id]
else:
t_dhcp_port = top_client.create_ports(context,
top_dhcp_port_body)
mappings = db_api.get_bottom_mappings_by_top_id(
context, t_dhcp_port['id'], constants.RT_PORT)
pod_list = [mapping[0]['pod_id'] for mapping in mappings]
if pod['pod_id'] in pod_list:
# mapping exists, skip this subnet
continue
dhcp_port_body = self._get_create_dhcp_port_body(
t_dhcp_port, b_subnet_id, bottom_net_id)
t_dhcp_ip = t_dhcp_port['fixed_ips'][0]['ip_address']
b_dhcp_port = None
try:
b_dhcp_port = client.create_ports(context, dhcp_port_body)
except Exception:
# examine if we conflicted with a dhcp port which was
# automatically created by bottom pod
b_dhcp_ports = client.list_ports(context,
b_dhcp_port_filters)
dhcp_port_match = False
for dhcp_port in b_dhcp_ports:
subnet_id = dhcp_port['fixed_ips'][0]['subnet_id']
ip = dhcp_port['fixed_ips'][0]['ip_address']
if b_subnet_id == subnet_id and t_dhcp_ip == ip:
with context.session.begin():
core.create_resource(
context, models.ResourceRouting,
{'top_id': t_dhcp_port['id'],
'bottom_id': dhcp_port['id'],
'pod_id': pod['pod_id'],
'project_id': self.project_id,
'resource_type': constants.RT_PORT})
dhcp_port_match = True
break
if not dhcp_port_match:
# so we didn't conflict with a dhcp port, raise exception
raise
if b_dhcp_port:
with context.session.begin():
core.create_resource(context, models.ResourceRouting,
{'top_id': t_dhcp_port['id'],
'bottom_id': b_dhcp_port['id'],
'pod_id': pod['pod_id'],
'project_id': self.project_id,
'resource_type': constants.RT_PORT})
# there is still one thing to do, there may be other dhcp ports
# created by bottom pod, we need to delete them
b_dhcp_ports = client.list_ports(context,
b_dhcp_port_filters)
remove_port_list = []
for dhcp_port in b_dhcp_ports:
subnet_id = dhcp_port['fixed_ips'][0]['subnet_id']
ip = dhcp_port['fixed_ips'][0]['ip_address']
if b_subnet_id == subnet_id and t_dhcp_ip != ip:
remove_port_list.append(dhcp_port['id'])
for dhcp_port_id in remove_port_list:
# NOTE(zhiyuan) dhcp agent will receive this port-delete
# notification and re-configure dhcp so our newly created
# dhcp port can be used
client.delete_ports(context, dhcp_port_id)
# port
if not port:
port = top_client.create_ports(context, top_port_body)
port_body = self._get_create_port_body(
port, subnet_map, bottom_net_id, bottom_sg_ids)
port_body = self.helper.get_create_port_body(
self.project_id, port, subnet_map, bottom_net_id,
bottom_sg_ids)
else:
port_body = self._get_create_port_body(port, subnet_map,
bottom_net_id)
_, bottom_port_id = self._prepare_neutron_element(context, pod, port,
'port', port_body)
port_body = self.helper.get_create_port_body(
self.project_id, port, subnet_map, bottom_net_id)
_, bottom_port_id = self.helper.prepare_bottom_element(
context, self.project_id, pod, port, constants.RT_PORT, port_body)
return port['id'], bottom_port_id
def _handle_port(self, context, pod, port):
@ -571,8 +369,8 @@ class ServerController(rest.RestController):
'security_group': {
'name': t_sg['id'],
'description': t_sg['description']}}
is_new, b_sg_id = self._prepare_neutron_element(
context, pod, t_sg, constants.RT_SG, sg_body)
is_new, b_sg_id = self.helper.prepare_bottom_element(
context, self.project_id, pod, t_sg, constants.RT_SG, sg_body)
t_sg_ids.append(t_sg['id'])
is_news.append(is_new)
b_sg_ids.append(b_sg_id)

View File

@ -1501,15 +1501,16 @@ class PluginTest(unittest.TestCase,
mock_action.side_effect = None
fake_plugin.add_router_interface(q_ctx, t_router_id,
{'subnet_id': t_subnet_id})
# bottom interface and bridge port
self.assertEqual(2, len(BOTTOM1_PORTS))
# bottom dhcp port, bottom interface and bridge port
self.assertEqual(3, len(BOTTOM1_PORTS))
with t_ctx.session.begin():
entries = core.query_resource(t_ctx, models.ResourceRouting,
[{'key': 'resource_type',
'comparator': 'eq',
'value': 'port'}], [])
# one more entry, for bottom interface
self.assertEqual(entry_num + 3, len(entries))
# three more entries, for top and bottom dhcp ports and
# bottom interface
self.assertEqual(entry_num + 5, len(entries))
@patch.object(ipam_non_pluggable_backend.IpamNonPluggableBackend,
'_generate_ip', new=fake_generate_ip)
@ -1546,8 +1547,8 @@ class PluginTest(unittest.TestCase,
# test that we can success when bottom pod comes back
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})
# bottom interface and bridge port
self.assertEqual(2, len(BOTTOM1_PORTS))
# bottom dhcp port, bottom interface and bridge port
self.assertEqual(3, len(BOTTOM1_PORTS))
@patch.object(ipam_non_pluggable_backend.IpamNonPluggableBackend,
'_generate_ip', new=fake_generate_ip)

View File

@ -31,6 +31,7 @@ from tricircle.common import xrpcapi
from tricircle.db import api
from tricircle.db import core
from tricircle.db import models
from tricircle.network import helper
from tricircle.nova_apigw.controllers import server
@ -70,6 +71,7 @@ class FakeServerController(server.ServerController):
def __init__(self, project_id):
self.clients = {'t_region': FakeClient('t_region')}
self.project_id = project_id
self.helper = FakeHelper()
self.xjob_handler = xrpcapi.XJobAPI()
def _get_client(self, pod_name=None):
@ -81,6 +83,11 @@ class FakeServerController(server.ServerController):
return self.clients[pod_name]
class FakeHelper(helper.NetworkHelper):
def _get_client(self, pod_name=None):
return FakeClient(pod_name)
class FakeClient(object):
_res_map = {'top': {'network': TOP_NETS,
@ -98,6 +105,8 @@ class FakeClient(object):
'security_group': BOTTOM2_SGS}}
def __init__(self, pod_name):
if not pod_name:
pod_name = 't_region'
self.pod_name = pod_name
self.ip_suffix_gen = self._get_ip_suffix()
@ -122,6 +131,25 @@ class FakeClient(object):
def create_resources(self, _type, ctx, body):
if 'id' not in body[_type]:
body[_type]['id'] = uuidutils.generate_uuid()
if _type == 'port' and 'fixed_ips' not in body[_type]:
net_id = body['port']['network_id']
subnets = self._get_res_list('subnet')
fixed_ip_list = []
for subnet in subnets:
if subnet['network_id'] == net_id:
cidr = subnet['cidr']
ip_prefix = cidr[:cidr.rindex('.') + 1]
mac_prefix = 'fa:16:3e:96:41:0'
if 'device_owner' in body['port']:
ip = ip_prefix + '2'
body['port']['mac_address'] = mac_prefix + '2'
else:
suffix = self.ip_suffix_gen.next()
ip = ip_prefix + suffix
body['port']['mac_address'] = mac_prefix + suffix
fixed_ip_list.append({'ip_address': ip,
'subnet_id': subnet['id']})
body['port']['fixed_ips'] = fixed_ip_list
if _type == 'port' and 'fixed_ips' in body[_type]:
ip_dict = body[_type]['fixed_ips'][0]
self._check_port_ip_conflict(ip_dict['subnet_id'],
@ -177,26 +205,6 @@ class FakeClient(object):
index %= 3
def create_ports(self, ctx, body):
if 'fixed_ips' in body['port']:
return self.create_resources('port', ctx, body)
net_id = body['port']['network_id']
subnets = self._get_res_list('subnet')
fixed_ip_list = []
for subnet in subnets:
if subnet['network_id'] == net_id:
cidr = subnet['cidr']
ip_prefix = cidr[:cidr.rindex('.') + 1]
mac_prefix = 'fa:16:3e:96:41:0'
if 'device_owner' in body['port']:
ip = ip_prefix + '2'
body['port']['mac_address'] = mac_prefix + '2'
else:
suffix = self.ip_suffix_gen.next()
ip = ip_prefix + suffix
body['port']['mac_address'] = mac_prefix + suffix
fixed_ip_list.append({'ip_address': ip,
'subnet_id': subnet['id']})
body['port']['fixed_ips'] = fixed_ip_list
return self.create_resources('port', ctx, body)
def list_ports(self, ctx, filters):
@ -225,6 +233,11 @@ class FakeClient(object):
'subnet', ctx,
[{'key': 'id', 'comparator': 'eq', 'value': subnet_id}])[0]
def get_ports(self, ctx, port_id):
return self.list_resources(
'port', ctx,
[{'key': 'id', 'comparator': 'eq', 'value': port_id}])[0]
def create_servers(self, ctx, **body):
body['id'] = uuidutils.generate_uuid()
BOTTOM_SERVERS.append(body)
@ -378,25 +391,26 @@ class ServerTest(unittest.TestCase):
def test_prepare_neutron_element(self):
t_pod, b_pod = self._prepare_pod()
port = {'id': 'top_port_id'}
body = {'port': {'name': 'top_port_id'}}
is_new, bottom_port_id = self.controller._prepare_neutron_element(
self.context, b_pod, port, 'port', body)
net = {'id': 'top_net_id'}
body = {'network': {'name': 'top_net_id'}}
is_new, bottom_port_id = self.controller.helper.prepare_bottom_element(
self.context, self.project_id, b_pod, net, 'network', body)
mappings = api.get_bottom_mappings_by_top_id(self.context,
'top_port_id', 'port')
'top_net_id', 'network')
self.assertEqual(bottom_port_id, mappings[0][1])
@patch.object(FakeClient, 'create_resources')
def test_prepare_neutron_element_create_res_exception(self, mock_method):
mock_method.side_effect = FakeException()
t_pod, b_pod = self._prepare_pod()
port = {'id': 'top_port_id'}
body = {'port': {'name': 'top_port_id'}}
net = {'id': 'top_net_id'}
body = {'network': {'name': 'top_net_id'}}
self.assertRaises(FakeException,
self.controller._prepare_neutron_element,
self.context, b_pod, port, 'port', body)
self.controller.helper.prepare_bottom_element,
self.context, self.project_id, b_pod, net,
'network', body)
mappings = api.get_bottom_mappings_by_top_id(self.context,
'top_port_id', 'port')
'top_net_id', 'network')
self.assertEqual(0, len(mappings))
def _check_routes(self):
@ -408,8 +422,10 @@ class ServerTest(unittest.TestCase):
with self.context.session.begin():
routes = core.query_resource(self.context,
models.ResourceRouting, [], [])
self.assertEqual(4, len(routes))
actual = [[], [], [], []]
# bottom network, bottom subnet, bottom port, top dhcp, bottom dhcp
self.assertEqual(5, len(routes))
actual = [[], [], [], [], []]
actual[4].append(constants.dhcp_port_name % TOP_SUBNETS[0]['id'])
for region in ('t_region', 'b_region'):
actual[0].append(self.controller._get_client(
region).list_resources('network', self.context, [])[0]['id'])
@ -420,9 +436,14 @@ class ServerTest(unittest.TestCase):
if 'device_id' in t_ports[0]:
actual[2].append(t_ports[0]['id'])
actual[3].append(t_ports[1]['id'])
if region == 't_region':
actual[4].append(t_ports[0]['id'])
else:
actual[2].append(t_ports[1]['id'])
actual[3].append(t_ports[0]['id'])
if region == 't_region':
actual[4].append(t_ports[1]['id'])
expect = [[route['top_id'], route['bottom_id']] for route in routes]
self.assertItemsEqual(expect, actual)

View File

@ -387,8 +387,14 @@ class XManager(PeriodicTasks):
# only consider ipv4 address currently
t_subnet_id = t_port['fixed_ips'][0]['subnet_id']
t_subnet = t_client.get_subnets(ctx, t_subnet_id)
b_port_id = self.helper.get_bottom_elements(
ctx, project_id, b_pod, t_net, t_subnet, t_port)
(b_net_id,
subnet_map) = self.helper.prepare_bottom_network_subnets(
ctx, project_id, b_pod, t_net, [t_subnet])
port_body = self.helper.get_create_port_body(
project_id, t_port, subnet_map, b_net_id)
_, b_port_id = self.helper.prepare_bottom_element(
ctx, project_id, b_pod, t_port, constants.RT_PORT, port_body)
b_client.action_routers(ctx, 'add_interface', b_router_id,
{'port_id': b_port_id})
elif t_ports and b_ports: