diff --git a/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/__init__.py b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/__init__.py index b520e30668..a270f40388 100644 --- a/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/__init__.py +++ b/vmware_nsx/neutron/plugins/vmware/nsxlib/v3/__init__.py @@ -23,12 +23,22 @@ from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import client LOG = log.getLogger(__name__) +ROUTER_INTF_PORT_NAME = "Tier1-RouterDownLinkPort" + def get_edge_cluster(edge_cluster_uuid): resource = "edge-clusters/%s" % edge_cluster_uuid return client.get_resource(resource) +@utils.retry_upon_exception_nsxv3(nsx_exc.StaleRevision) +def update_resource_with_retry(resource, payload): + revised_payload = client.get_resource(resource) + for key_name in payload.keys(): + revised_payload[key_name] = payload[key_name] + return client.update_resource(resource, revised_payload) + + def create_logical_switch(display_name, transport_zone_id, tags, replication_mode=nsx_constants.MTEP, admin_state=True, vlan_id=None): @@ -145,8 +155,8 @@ def update_logical_port(lport_id, name=None, admin_state=None): return client.update_resource(resource, lport) -def create_logical_router(display_name, tags, edge_cluster_uuid=None, - tier_0=False): +def create_logical_router(display_name, tags, + edge_cluster_uuid=None, tier_0=False): # TODO(salv-orlando): If possible do not manage edge clusters in the main # plugin logic. router_type = (nsx_constants.ROUTER_TYPE_TIER0 if tier_0 else @@ -155,8 +165,6 @@ def create_logical_router(display_name, tags, edge_cluster_uuid=None, body = {'display_name': display_name, 'router_type': router_type, 'tags': tags} - # TODO(salv-orlando): raise if tier_0 but no edge_cluster_uuid was - # specified if edge_cluster_uuid: body['edge_cluster_id'] = edge_cluster_uuid return client.create_resource(resource, body) @@ -167,6 +175,11 @@ def get_logical_router(lrouter_id): return client.get_resource(resource) +def update_logical_router(lrouter_id, **kwargs): + resource = 'logical-routers/%s' % lrouter_id + return update_resource_with_retry(resource, kwargs) + + def delete_logical_router(lrouter_id): resource = 'logical-routers/%s/' % lrouter_id @@ -174,21 +187,70 @@ def delete_logical_router(lrouter_id): return client.delete_resource(resource) +def get_logical_router_port_by_ls_id(logical_switch_id): + resource = 'logical-router-ports?logical_switch_id=%s' % logical_switch_id + router_ports = client.get_resource(resource) + if int(router_ports['result_count']) >= 2: + raise nsx_exc.NsxPluginException( + err_msg=_("Can't support more than one logical router ports " + "on same logical switch %s ") % logical_switch_id) + elif int(router_ports['result_count']) == 1: + return router_ports['results'][0] + else: + err_msg = (_("Logical router link port not found on logical " + "switch %s") % logical_switch_id) + raise nsx_exc.ResourceNotFound(manager=client._get_manager_ip(), + operation=err_msg) + + +def create_logical_router_port_by_ls_id(logical_router_id, + ls_id, + logical_switch_port_id, + resource_type, + address_groups): + try: + port = get_logical_router_port_by_ls_id(ls_id) + except nsx_exc.ResourceNotFound: + return create_logical_router_port(logical_router_id, + ROUTER_INTF_PORT_NAME, + logical_switch_port_id, + resource_type, + address_groups) + else: + return update_logical_router_port(port['id'], subnets=address_groups) + + def create_logical_router_port(logical_router_id, + display_name, logical_switch_port_id, resource_type, - cidr_length, - ip_address): + address_groups): resource = 'logical-router-ports' - body = {'resource_type': resource_type, + body = {'display_name': display_name, + 'resource_type': resource_type, 'logical_router_id': logical_router_id, - 'subnets': [{"prefix_length": cidr_length, - "ip_addresses": [ip_address]}], + 'subnets': address_groups, 'linked_logical_switch_port_id': logical_switch_port_id} return client.create_resource(resource, body) +def update_logical_router_port_by_ls_id(logical_router_id, ls_id, + **payload): + port = get_logical_router_port_by_ls_id(ls_id) + return update_logical_router_port(port['id'], **payload) + + +def update_logical_router_port(logical_port_id, **kwargs): + resource = 'logical-router-ports/%s?detach=true' % logical_port_id + return update_resource_with_retry(resource, kwargs) + + +def delete_logical_router_port_by_ls_id(ls_id): + port = get_logical_router_port_by_ls_id(ls_id) + delete_logical_router_port(port['id']) + + def delete_logical_router_port(logical_port_id): resource = 'logical-router-ports/%s?detach=true' % logical_port_id client.delete_resource(resource) diff --git a/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py b/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py index 91656f3d0b..6740907c0a 100644 --- a/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py +++ b/vmware_nsx/neutron/plugins/vmware/plugins/nsx_v3_plugin.py @@ -43,11 +43,13 @@ from neutron.db import agentschedulers_db from neutron.db import db_base_plugin_v2 from neutron.db import external_net_db from neutron.db import extradhcpopt_db +from neutron.db import extraroute_db from neutron.db import l3_db +from neutron.db import l3_gwmode_db from neutron.db import models_v2 from neutron.db import portbindings_db from neutron.db import securitygroups_db -from neutron.i18n import _LE, _LW +from neutron.i18n import _LE, _LI, _LW from neutron.plugins.common import constants as plugin_const from neutron.plugins.common import utils as n_utils @@ -65,7 +67,8 @@ LOG = log.getLogger(__name__) class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, securitygroups_db.SecurityGroupDbMixin, external_net_db.External_net_db_mixin, - l3_db.L3_NAT_dbonly_mixin, + extraroute_db.ExtraRoute_db_mixin, + l3_gwmode_db.L3_NAT_db_mixin, portbindings_db.PortBindingMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin, extradhcpopt_db.ExtraDhcpOptMixin): @@ -77,9 +80,11 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, supported_extension_aliases = ["quotas", "binding", "extra_dhcp_opt", + "ext-gw-mode", "security-group", "provider", "external-net", + "extraroute", "router"] def __init__(self): @@ -320,6 +325,8 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, pnet._raise_if_updates_provider_attributes(net_data) updated_net = super(NsxV3Plugin, self).update_network(context, id, network) + self._process_l3_update(context, updated_net, network['network']) + self._extend_network_dict_provider(context, updated_net) if (not self._network_is_external(context, id) and 'name' in net_data or 'admin_state_up' in net_data): @@ -491,6 +498,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, _net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id( context.session, port_id) nsxlib.delete_logical_port(nsx_port_id) + self.disassociate_floatingips(context, port_id) ret_val = super(NsxV3Plugin, self).delete_port(context, port_id) return ret_val @@ -532,13 +540,32 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, return updated_port + def _extract_external_gw(self, context, router, is_extract=True): + r = router['router'] + gw_info = attributes.ATTR_NOT_SPECIFIED + # First extract the gateway info in case of updating + # gateway before edge is deployed. + if 'external_gateway_info' in r: + gw_info = r.get('external_gateway_info', {}) + if is_extract: + del r['external_gateway_info'] + network_id = (gw_info.get('network_id') if gw_info + else None) + if network_id: + ext_net = self._get_network(context, network_id) + if not ext_net.external: + msg = (_("Network '%s' is not a valid external network") % + network_id) + raise n_exc.BadRequest(resource='router', msg=msg) + return gw_info + def create_router(self, context, router): + # TODO(berlin): admin_state_up support + gw_info = self._extract_external_gw(context, router, is_extract=True) tags = utils.build_v3_tags_payload(router['router']) result = nsxlib.create_logical_router( - display_name=router['router'].get('name', 'a_router_with_no_name'), - tags=tags, - tier_0=True, - edge_cluster_uuid=cfg.CONF.nsx_v3.default_edge_cluster_uuid) + display_name=router['router'].get('name'), + tags=tags) with context.session.begin(): router = super(NsxV3Plugin, self).create_router( @@ -546,7 +573,21 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, nsx_db.add_neutron_nsx_router_mapping( context.session, router['id'], result['id']) - return router + if gw_info != attributes.ATTR_NOT_SPECIFIED: + try: + self._update_router_gw_info(context, router['id'], gw_info) + except nsx_exc.ManagerError: + with excutils.save_and_reraise_exception(): + LOG.error(_LE("Failed to set gateway info for router " + "being created: %s - removing router"), + router['id']) + self.delete_router(context, router['id']) + LOG.info(_LI("Create router failed while setting external " + "gateway. Router:%s has been removed from " + "DB and backend"), + router['id']) + + return self.get_router(context, router['id']) def delete_router(self, context, router_id): nsx_router_id = nsx_db.get_nsx_router_id(context.session, @@ -558,12 +599,12 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, # passed (and indeed the resource was removed from the Neutron DB try: nsxlib.delete_logical_router(nsx_router_id) - except nsx_exc.LogicalRouterNotFound: + except nsx_exc.ResourceNotFound: # If the logical router was not found on the backend do not worry # about it. The conditions has already been logged, so there is no # need to do further logging pass - except nsx_exc.NsxPluginException: + except nsx_exc.ManagerError: # if there is a failure in deleting the router do not fail the # operation, especially since the router object has already been # removed from the neutron DB. Take corrective steps to ensure the @@ -571,44 +612,92 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, # eventually removed. LOG.warning(_LW("Backend router deletion for neutron router %s " "failed. The object was however removed from the " - "Neutron datanase"), router_id) + "Neutron database"), router_id) return ret_val def update_router(self, context, router_id, router): - # TODO(arosen) - call to backend - return super(NsxV3Plugin, self).update_router(context, id, - router) + # TODO(berlin): admin_state_up support + try: + return super(NsxV3Plugin, self).update_router(context, router_id, + router) + except nsx_exc.ResourceNotFound: + with context.session.begin(subtransactions=True): + router_db = self._get_router(context, router_id) + router_db['status'] = const.NET_STATUS_ERROR + raise nsx_exc.NsxPluginException( + err_msg=(_("logical router %s not found at the backend") + % router_id)) + except nsx_exc.ManagerError: + raise nsx_exc.NsxPluginException( + err_msg=(_("Unable to update router %s at the backend") + % router_id)) + + def _get_router_interface_ports_by_network( + self, context, router_id, network_id): + port_filters = {'device_id': [router_id], + 'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF], + 'network_id': [network_id]} + return self.get_ports(context, filters=port_filters) + + def _get_ports_and_address_groups(self, context, router_id, network_id, + exclude_sub_ids=None): + exclude_sub_ids = [] if not exclude_sub_ids else exclude_sub_ids + address_groups = [] + ports = self._get_router_interface_ports_by_network( + context, router_id, network_id) + ports = [port for port in ports + if port['fixed_ips'] and + port['fixed_ips'][0]['subnet_id'] not in exclude_sub_ids] + for port in ports: + address_group = {} + gateway_ip = port['fixed_ips'][0]['ip_address'] + subnet = self.get_subnet(context, + port['fixed_ips'][0]['subnet_id']) + prefixlen = str(netaddr.IPNetwork(subnet['cidr']).prefixlen) + address_group['ip_addresses'] = [gateway_ip] + address_group['prefix_length'] = prefixlen + address_groups.append(address_group) + return (ports, address_groups) def add_router_interface(self, context, router_id, interface_info): - # NOTE(arosen): I think there is a bug here since I believe we - # can also get a port or ip here.... - subnet = self.get_subnet(context, interface_info['subnet_id']) - port = {'port': {'network_id': subnet['network_id'], 'name': '', - 'admin_state_up': True, 'device_id': '', - 'device_owner': l3_db.DEVICE_OWNER_ROUTER_INTF, - 'mac_address': attributes.ATTR_NOT_SPECIFIED, - 'fixed_ips': [{'subnet_id': subnet['id'], - 'ip_address': subnet['gateway_ip']}]}} - port = self.create_port(context, port) - _net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id( + # TODO(berlin): disallow multiple subnets attached to different routers + info = super(NsxV3Plugin, self).add_router_interface( + context, router_id, interface_info) + subnet = self.get_subnet(context, info['subnet_ids'][0]) + port = self.get_port(context, info['port_id']) + network_id = subnet['network_id'] + nsx_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id( context.session, port['id']) nsx_router_id = nsx_db.get_nsx_router_id(context.session, router_id) - result = nsxlib.create_logical_router_port( + _ports, address_groups = self._get_ports_and_address_groups( + context, router_id, network_id) + nsxlib.create_logical_router_port_by_ls_id( logical_router_id=nsx_router_id, + ls_id=nsx_net_id, logical_switch_port_id=nsx_port_id, resource_type="LogicalRouterDownLinkPort", - cidr_length=24, ip_address=subnet['gateway_ip']) - interface_info['port_id'] = port['id'] - del interface_info['subnet_id'] - result = super(NsxV3Plugin, self).add_router_interface( - context, router_id, interface_info) - return result + address_groups=address_groups) + return info def remove_router_interface(self, context, router_id, interface_info): - if 'subnet_id' in interface_info: + subnet = None + subnet_id = None + port_id = None + self._validate_interface_info(interface_info, for_removal=True) + if 'port_id' in interface_info: + port_id = interface_info['port_id'] + # find subnet_id - it is need for removing the SNAT rule + port = self._get_port(context, port_id) + if port.get('fixed_ips'): + subnet_id = port['fixed_ips'][0]['subnet_id'] + if not (port['device_owner'] in const.ROUTER_INTERFACE_OWNERS + and port['device_id'] == router_id): + raise l3.RouterInterfaceNotFound(router_id=router_id, + port_id=port_id) + elif 'subnet_id' in interface_info: subnet_id = interface_info['subnet_id'] subnet = self._get_subnet(context, subnet_id) rport_qry = context.session.query(models_v2.Port) @@ -623,9 +712,30 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, else: raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id, subnet_id=subnet_id) - _net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id( + try: + nsx_net_id, _nsx_port_id = nsx_db.get_nsx_switch_and_port_id( context.session, port_id) - nsxlib.delete_logical_router_port(nsx_port_id) + subnet = self.get_subnet(context, subnet_id) + ports, address_groups = self._get_ports_and_address_groups( + context, router_id, subnet['network_id'], + exclude_sub_ids=[subnet['id']]) + nsx_router_id = nsx_db.get_nsx_router_id( + context.session, router_id) + if len(ports) >= 1: + new_using_port_id = ports[0]['id'] + _net_id, new_nsx_port_id = nsx_db.get_nsx_switch_and_port_id( + context.session, new_using_port_id) + nsxlib.update_logical_router_port_by_ls_id( + nsx_router_id, nsx_net_id, + linked_logical_switch_port_id=new_nsx_port_id, + subnets=address_groups) + else: + nsxlib.delete_logical_router_port_by_ls_id(nsx_net_id) + except nsx_exc.ResourceNotFound: + LOG.error(_LE("router port on router %(router_id)s for net " + "%(net_id)s not found at the backend"), + {'router_id': router_id, + 'net_id': subnet['network_id']}) return super(NsxV3Plugin, self).remove_router_interface( context, router_id, interface_info) diff --git a/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py b/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py index 7b7d4932f4..b14d88903c 100644 --- a/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py +++ b/vmware_nsx/neutron/tests/unit/vmware/nsx_v3_mocks.py @@ -16,10 +16,13 @@ from oslo_utils import uuidutils +from vmware_nsx.neutron.plugins.vmware.common import exceptions as nsx_exc from vmware_nsx.neutron.plugins.vmware.common import nsx_constants FAKE_NAME = "fake_name" +DEFAULT_TIER0_ROUTER_UUID = "fake_default_tier0_router_uuid" +FAKE_MANAGER = "fake_manager_ip" def make_fake_switch(switch_uuid=None, tz_uuid=None, name=FAKE_NAME): @@ -179,22 +182,6 @@ def update_logical_port(lport_id, name=None, admin_state=None): return lport -def get_edge_cluster(edge_cluster_uuid): - FAKE_CLUSTER = { - "id": edge_cluster_uuid, - "members": [ - {"member_index": 0}, - {"member_index": 1}]} - return FAKE_CLUSTER - - -def get_logical_router(lrouter_uuid): - FAKE_LROUTER = { - "id": lrouter_uuid, - "edge_cluster_uuid": uuidutils.generate_uuid()} - return FAKE_LROUTER - - def add_rules_in_section(rules, section_id): for rule in rules: rule['id'] = uuidutils.generate_uuid() @@ -216,3 +203,135 @@ def update_resource(resource, data): def delete_resource(resource): pass + + +class NsxV3Mock(object): + def __init__(self, default_tier0_router_uuid=DEFAULT_TIER0_ROUTER_UUID): + self.logical_routers = {} + self.logical_router_ports = {} + self.logical_ports = {} + if default_tier0_router_uuid: + self.create_logical_router( + DEFAULT_TIER0_ROUTER_UUID, None, + edge_cluster_uuid="fake_edge_cluster_uuid", + tier_0=True) + + def get_edge_cluster(self, edge_cluster_uuid): + FAKE_CLUSTER = { + "id": edge_cluster_uuid, + "members": [ + {"member_index": 0}, + {"member_index": 1}]} + return FAKE_CLUSTER + + def create_logical_router(self, display_name, tags, + edge_cluster_uuid=None, + tier_0=False): + router_type = (nsx_constants.ROUTER_TYPE_TIER0 if tier_0 else + nsx_constants.ROUTER_TYPE_TIER1) + if display_name == DEFAULT_TIER0_ROUTER_UUID: + fake_router_uuid = DEFAULT_TIER0_ROUTER_UUID + else: + fake_router_uuid = uuidutils.generate_uuid() + result = {'display_name': display_name, + 'router_type': router_type, + 'tags': tags, + 'id': fake_router_uuid} + if edge_cluster_uuid: + result['edge_cluster_id'] = edge_cluster_uuid + self.logical_routers[fake_router_uuid] = result + return result + + def get_logical_router(self, lrouter_id): + if lrouter_id in self.logical_routers: + return self.logical_routers[lrouter_id] + else: + raise nsx_exc.ResourceNotFound(manager=FAKE_MANAGER, + operation="get_logical_router") + + def update_logical_router(self, lrouter_id, **kwargs): + if lrouter_id in self.logical_routers: + payload = self.logical_routers[lrouter_id] + payload.update(kwargs) + return payload + else: + raise nsx_exc.ResourceNotFound(manager=FAKE_MANAGER, + operation="update_logical_router") + + def delete_logical_router(self, lrouter_id): + if lrouter_id in self.logical_routers: + del self.logical_routers[lrouter_id] + else: + raise nsx_exc.ResourceNotFound(manager=FAKE_MANAGER, + operation="delete_logical_router") + + def get_logical_router_port_by_ls_id(self, logical_switch_id): + router_ports = [] + for router_port in self.logical_router_ports.values(): + ls_port_id = router_port['linked_logical_switch_port_id'] + port = self.get_logical_port(ls_port_id) + if port['logical_switch_id'] == logical_switch_id: + router_ports.append(router_port) + if len(router_ports) >= 2: + raise nsx_exc.NsxPluginException( + err_msg=_("Can't support more than one logical router ports " + "on same logical switch %s ") % logical_switch_id) + elif len(router_ports) == 1: + return router_ports[0] + else: + err_msg = (_("Logical router link port not found on logical " + "switch %s") % logical_switch_id) + raise nsx_exc.ResourceNotFound(manager=FAKE_MANAGER, + operation=err_msg) + + def create_logical_port(self, lswitch_id, vif_uuid, tags, + attachment_type=nsx_constants.ATTACHMENT_VIF, + admin_state=True, name=None, address_bindings=None, + parent_name=None, parent_tag=None): + fake_port = create_logical_port( + lswitch_id, vif_uuid, tags, + attachment_type=attachment_type, + admin_state=admin_state, name=name, + address_bindings=address_bindings, + parent_name=parent_name, parent_tag=parent_tag) + fake_port_uuid = fake_port['id'] + self.logical_ports[fake_port_uuid] = fake_port + return fake_port + + def get_logical_port(self, logical_port_id): + if logical_port_id in self.logical_ports: + return self.logical_ports[logical_port_id] + else: + raise nsx_exc.ResourceNotFound( + manager=FAKE_MANAGER, operation="get_logical_port") + + def create_logical_router_port(self, logical_router_id, + display_name, + logical_switch_port_id, + resource_type, + address_groups): + fake_router_port_uuid = uuidutils.generate_uuid() + body = {'id': fake_router_port_uuid, + 'display_name': display_name, + 'resource_type': resource_type, + 'logical_router_id': logical_router_id, + 'subnets': address_groups, + 'linked_logical_switch_port_id': logical_switch_port_id} + self.logical_router_ports[fake_router_port_uuid] = body + return body + + def update_logical_router_port(self, logical_port_id, **kwargs): + if logical_port_id in self.logical_router_ports: + payload = self.logical_router_ports[logical_port_id] + payload.update(kwargs) + return payload + else: + raise nsx_exc.ResourceNotFound( + manager=FAKE_MANAGER, operation="update_logical_router_port") + + def delete_logical_router_port(self, logical_port_id): + if logical_port_id in self.logical_router_ports: + del self.logical_router_ports[logical_port_id] + else: + raise nsx_exc.ResourceNotFound( + manager=FAKE_MANAGER, operation="update_logical_router_port") diff --git a/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py b/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py index c871e6787d..c75efd4858 100644 --- a/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py +++ b/vmware_nsx/neutron/tests/unit/vmware/test_nsx_v3_plugin.py @@ -15,12 +15,26 @@ import mock from oslo_config import cfg +import six +from neutron.api.v2 import attributes +from neutron import context +from neutron.extensions import external_net +from neutron.extensions import extraroute +from neutron.extensions import l3 +from neutron.extensions import l3_ext_gw_mode +from neutron.extensions import providernet as pnet +from neutron import manager import neutron.tests.unit.db.test_db_base_plugin_v2 as test_plugin from neutron.tests.unit.extensions import test_extra_dhcp_opt as test_dhcpopts +import neutron.tests.unit.extensions.test_extraroute as test_ext_route +import neutron.tests.unit.extensions.test_l3 as test_l3_plugin +import neutron.tests.unit.extensions.test_l3_ext_gw_mode as test_ext_gw_mode import neutron.tests.unit.extensions.test_securitygroup as ext_sg +from vmware_nsx.neutron.plugins.vmware.common import utils from vmware_nsx.neutron.plugins.vmware.nsxlib import v3 as nsxlib from vmware_nsx.neutron.plugins.vmware.nsxlib.v3 import dfw_api as firewall +from vmware_nsx.neutron.tests.unit import vmware from vmware_nsx.neutron.tests.unit.vmware import nsx_v3_mocks PLUGIN_NAME = ('vmware_nsx.neutron.plugins.vmware.' @@ -45,9 +59,6 @@ class NsxPluginV3TestCase(test_plugin.NeutronDbPluginV2TestCase): nsxlib.delete_logical_port = mock.Mock() nsxlib.get_logical_port = nsx_v3_mocks.get_logical_port nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port - # TODO(berlin): fill valid data - nsxlib.get_edge_cluster = nsx_v3_mocks.get_edge_cluster - nsxlib.get_logical_router = nsx_v3_mocks.get_logical_router firewall.add_rules_in_section = nsx_v3_mocks.add_rules_in_section firewall.nsclient.create_resource = nsx_v3_mocks.create_resource firewall.nsclient.update_resource = nsx_v3_mocks.update_resource @@ -56,6 +67,36 @@ class NsxPluginV3TestCase(test_plugin.NeutronDbPluginV2TestCase): super(NsxPluginV3TestCase, self).setUp(plugin=plugin, ext_mgr=ext_mgr) + self.v3_mock = nsx_v3_mocks.NsxV3Mock() + nsxlib.get_edge_cluster = self.v3_mock.get_edge_cluster + nsxlib.get_logical_router = self.v3_mock.get_logical_router + + def _create_network(self, fmt, name, admin_state_up, + arg_list=None, providernet_args=None, **kwargs): + data = {'network': {'name': name, + 'admin_state_up': admin_state_up, + 'tenant_id': self._tenant_id}} + # Fix to allow the router:external attribute and any other + # attributes containing a colon to be passed with + # a double underscore instead + kwargs = dict((k.replace('__', ':'), v) for k, v in kwargs.items()) + if external_net.EXTERNAL in kwargs: + arg_list = (external_net.EXTERNAL, ) + (arg_list or ()) + + attrs = kwargs + if providernet_args: + attrs.update(providernet_args) + for arg in (('admin_state_up', 'tenant_id', 'shared') + + (arg_list or ())): + # Arg must be present and not empty + if arg in kwargs and kwargs[arg]: + data['network'][arg] = kwargs[arg] + network_req = self.new_create_request('networks', data, fmt) + if (kwargs.get('set_context') and 'tenant_id' in kwargs): + # create a specific auth context for this request + network_req.environ['neutron.context'] = context.Context( + '', kwargs['tenant_id']) + return network_req.get_response(self.api) class TestNetworksV2(test_plugin.TestNetworksV2, NsxPluginV3TestCase): @@ -97,3 +138,111 @@ class DHCPOptsTestCase(test_dhcpopts.TestExtraDhcpOpt, NsxPluginV3TestCase): def setUp(self, plugin=None): super(test_dhcpopts.ExtraDhcpOptDBTestCase, self).setUp( plugin=PLUGIN_NAME) + + +class TestL3ExtensionManager(object): + + def get_resources(self): + # Simulate extension of L3 attribute map + # First apply attribute extensions + for key in l3.RESOURCE_ATTRIBUTE_MAP.keys(): + l3.RESOURCE_ATTRIBUTE_MAP[key].update( + l3_ext_gw_mode.EXTENDED_ATTRIBUTES_2_0.get(key, {})) + l3.RESOURCE_ATTRIBUTE_MAP[key].update( + extraroute.EXTENDED_ATTRIBUTES_2_0.get(key, {})) + # Finally add l3 resources to the global attribute map + attributes.RESOURCE_ATTRIBUTE_MAP.update( + l3.RESOURCE_ATTRIBUTE_MAP) + return l3.L3.get_resources() + + def get_actions(self): + return [] + + def get_request_extensions(self): + return [] + + +def backup_l3_attribute_map(): + """Return a backup of the original l3 attribute map.""" + return dict((res, attrs.copy()) for + (res, attrs) in six.iteritems(l3.RESOURCE_ATTRIBUTE_MAP)) + + +def restore_l3_attribute_map(map_to_restore): + """Ensure changes made by fake ext mgrs are reverted.""" + l3.RESOURCE_ATTRIBUTE_MAP = map_to_restore + + +class L3NatTest(test_l3_plugin.L3BaseForIntTests, NsxPluginV3TestCase): + + def _restore_l3_attribute_map(self): + l3.RESOURCE_ATTRIBUTE_MAP = self._l3_attribute_map_bk + + def setUp(self, plugin=PLUGIN_NAME, ext_mgr=None, + service_plugins=None): + self._l3_attribute_map_bk = backup_l3_attribute_map() + cfg.CONF.set_override('api_extensions_path', vmware.NSXEXT_PATH) + cfg.CONF.set_default('max_routes', 3) + self.addCleanup(restore_l3_attribute_map, self._l3_attribute_map_bk) + ext_mgr = ext_mgr or TestL3ExtensionManager() + super(L3NatTest, self).setUp( + plugin=plugin, ext_mgr=ext_mgr, service_plugins=service_plugins) + plugin_instance = manager.NeutronManager.get_plugin() + self._plugin_name = "%s.%s" % ( + plugin_instance.__module__, + plugin_instance.__class__.__name__) + self._plugin_class = plugin_instance.__class__ + nsxlib.create_logical_port = self.v3_mock.create_logical_port + nsxlib.create_logical_router = self.v3_mock.create_logical_router + nsxlib.update_logical_router = self.v3_mock.update_logical_router + nsxlib.delete_logical_router = self.v3_mock.delete_logical_router + nsxlib.get_logical_router_port_by_ls_id = ( + self.v3_mock.get_logical_router_port_by_ls_id) + nsxlib.create_logical_router_port = ( + self.v3_mock.create_logical_router_port) + nsxlib.update_logical_router_port = ( + self.v3_mock.update_logical_router_port) + nsxlib.delete_logical_router_port = ( + self.v3_mock.delete_logical_router_port) + + def _create_l3_ext_network( + self, physical_network=nsx_v3_mocks.DEFAULT_TIER0_ROUTER_UUID): + name = 'l3_ext_net' + net_type = utils.NetworkTypes.L3_EXT + providernet_args = {pnet.NETWORK_TYPE: net_type, + pnet.PHYSICAL_NETWORK: physical_network} + return self.network(name=name, + router__external=True, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK)) + + +class TestL3NatTestCase(L3NatTest, + test_l3_plugin.L3NatDBIntTestCase, + NsxPluginV3TestCase, + test_ext_route.ExtraRouteDBTestCaseBase): + + def _test_create_l3_ext_network( + self, physical_network=nsx_v3_mocks.DEFAULT_TIER0_ROUTER_UUID): + name = 'l3_ext_net' + net_type = utils.NetworkTypes.L3_EXT + expected = [('subnets', []), ('name', name), ('admin_state_up', True), + ('status', 'ACTIVE'), ('shared', False), + (external_net.EXTERNAL, True), + (pnet.NETWORK_TYPE, net_type), + (pnet.PHYSICAL_NETWORK, physical_network)] + with self._create_l3_ext_network(physical_network) as net: + for k, v in expected: + self.assertEqual(net['network'][k], v) + + def test_create_l3_ext_network_with_default_tier0(self): + self._test_create_l3_ext_network() + + def test_floatingip_with_invalid_create_port(self): + self._test_floatingip_with_invalid_create_port(self._plugin_name) + + +class ExtGwModeTestCase(L3NatTest, + test_ext_gw_mode.ExtGwModeIntTestCase): + pass