From de082eff4f97870a24844a3ef4e04391b8f65554 Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Fri, 22 Jul 2016 13:56:22 +0800 Subject: [PATCH] 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 --- requirements.txt | 1 + tricircle/common/constants.py | 2 + tricircle/network/helper.py | 293 +++++++++++++++--- tricircle/network/plugin.py | 5 - tricircle/nova_apigw/controllers/server.py | 232 +------------- tricircle/tests/unit/network/test_plugin.py | 13 +- .../nova_apigw/controllers/test_server.py | 85 +++-- tricircle/xjob/xmanager.py | 10 +- 8 files changed, 338 insertions(+), 303 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8b3e3161..84494124 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tricircle/common/constants.py b/tricircle/common/constants.py index d331d502..f619b6ec 100644 --- a/tricircle/common/constants.py +++ b/tricircle/common/constants.py @@ -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) diff --git a/tricircle/network/helper.py b/tricircle/network/helper.py index 26ed37c1..973877ca 100644 --- a/tricircle/network/helper.py +++ b/tricircle/network/helper.py @@ -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) diff --git a/tricircle/network/plugin.py b/tricircle/network/plugin.py index de1715bd..3d3ae4ea 100644 --- a/tricircle/network/plugin.py +++ b/tricircle/network/plugin.py @@ -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, diff --git a/tricircle/nova_apigw/controllers/server.py b/tricircle/nova_apigw/controllers/server.py index 5455c6f7..2d338138 100644 --- a/tricircle/nova_apigw/controllers/server.py +++ b/tricircle/nova_apigw/controllers/server.py @@ -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) diff --git a/tricircle/tests/unit/network/test_plugin.py b/tricircle/tests/unit/network/test_plugin.py index debbbac6..2927c9f4 100644 --- a/tricircle/tests/unit/network/test_plugin.py +++ b/tricircle/tests/unit/network/test_plugin.py @@ -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) diff --git a/tricircle/tests/unit/nova_apigw/controllers/test_server.py b/tricircle/tests/unit/nova_apigw/controllers/test_server.py index 0b987153..0b9ef9d0 100644 --- a/tricircle/tests/unit/nova_apigw/controllers/test_server.py +++ b/tricircle/tests/unit/nova_apigw/controllers/test_server.py @@ -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) diff --git a/tricircle/xjob/xmanager.py b/tricircle/xjob/xmanager.py index 5c8beafd..a521f1bf 100644 --- a/tricircle/xjob/xmanager.py +++ b/tricircle/xjob/xmanager.py @@ -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: