From 9b6d3b1b474eedc8d2dd71abfb4d22f01cfe2a0f Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Wed, 17 Jun 2015 13:50:00 +0300 Subject: [PATCH] LBaaSv2 driver Implement LBaaSv2 driver for NSXv Edge appliance load balancer. Includes TLS support for Edge appliance, and certificate management For SSL termination. Change-Id: I60093c0186cce3e99fb26e1fc6bd5175cbd1a560 --- tools/tox_install.sh | 10 + .../alembic_migrations/versions/HEADS | 2 +- .../mitaka/expand/312211a5725f_nsxv_lbv2.py | 68 ++ vmware_nsx/db/nsxv_db.py | 150 +++++ vmware_nsx/db/nsxv_models.py | 56 ++ vmware_nsx/plugins/nsx_v/vshield/vcns.py | 5 + .../plugins/nsx_v/vshield/vcns_driver.py | 6 +- vmware_nsx/services/lbaas/__init__.py | 0 vmware_nsx/services/lbaas/nsx_v/__init__.py | 0 .../services/lbaas/nsx_v/lbaas_common.py | 250 +++++++ .../services/lbaas/nsx_v/lbaas_const.py | 56 ++ .../services/lbaas/nsx_v/v1/__init__.py | 0 .../nsx_v/v1}/edge_loadbalancer_driver.py | 493 ++++---------- .../services/lbaas/nsx_v/v2/__init__.py | 0 .../services/lbaas/nsx_v/v2/base_mgr.py | 52 ++ .../nsx_v/v2/edge_loadbalancer_driver_v2.py | 36 ++ .../services/lbaas/nsx_v/v2/healthmon_mgr.py | 186 ++++++ .../services/lbaas/nsx_v/v2/listener_mgr.py | 278 ++++++++ .../lbaas/nsx_v/v2/loadbalancer_mgr.py | 89 +++ .../services/lbaas/nsx_v/v2/member_mgr.py | 173 +++++ .../services/lbaas/nsx_v/v2/pool_mgr.py | 143 ++++ .../nsx_v/test_edge_loadbalancer_driver.py | 247 ++----- .../nsx_v/test_edge_loadbalancer_driver_v2.py | 611 ++++++++++++++++++ .../tests/unit/nsx_v/test_lbaas_common.py | 173 +++++ 24 files changed, 2522 insertions(+), 562 deletions(-) create mode 100644 vmware_nsx/db/migration/alembic_migrations/versions/mitaka/expand/312211a5725f_nsxv_lbv2.py create mode 100644 vmware_nsx/services/lbaas/__init__.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/__init__.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/lbaas_common.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/lbaas_const.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v1/__init__.py rename vmware_nsx/{plugins/nsx_v/vshield => services/lbaas/nsx_v/v1}/edge_loadbalancer_driver.py (56%) create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/__init__.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/base_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/edge_loadbalancer_driver_v2.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/healthmon_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/listener_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/member_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v/v2/pool_mgr.py create mode 100644 vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py create mode 100644 vmware_nsx/tests/unit/nsx_v/test_lbaas_common.py diff --git a/tools/tox_install.sh b/tools/tox_install.sh index c04fd2137d..ad33ae5ff0 100755 --- a/tools/tox_install.sh +++ b/tools/tox_install.sh @@ -16,6 +16,7 @@ ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner neutron_installed=$(echo "import neutron" | python 2>/dev/null ; echo $?) networking_l2gw_installed=$(echo "import networking_l2gw" | python 2>/dev/null ; echo $?) +neutron_lbaas_installed=$(echo "import neutron_lbaas" | python 2>/dev/null ; echo $?) set -ex @@ -56,5 +57,14 @@ else pip_hardcode openstack/networking-l2gw#egg=networking-l2gw fi +if [ $neutron_lbaas_installed -eq 0 ]; then + echo "NEUTRON_LBAAS ALREADY INSTALLED" >> /tmp/tox_install.txt + echo "Neutron_lbaas already installed; using existing package" +elif [ -x "$ZUUL_CLONER" ]; then + zuul_cloner openstack/neutron-lbaas +else + pip_hardcode openstack/neutron-lbaas#egg=neutron-lbaas +fi + pip install -U $* exit $? diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/HEADS b/vmware_nsx/db/migration/alembic_migrations/versions/HEADS index 65bc3df995..c641378f6c 100644 --- a/vmware_nsx/db/migration/alembic_migrations/versions/HEADS +++ b/vmware_nsx/db/migration/alembic_migrations/versions/HEADS @@ -1,2 +1,2 @@ -279b70ac3ae8 +312211a5725f 3c88bdea3054 diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/mitaka/expand/312211a5725f_nsxv_lbv2.py b/vmware_nsx/db/migration/alembic_migrations/versions/mitaka/expand/312211a5725f_nsxv_lbv2.py new file mode 100644 index 0000000000..913ebdb528 --- /dev/null +++ b/vmware_nsx/db/migration/alembic_migrations/versions/mitaka/expand/312211a5725f_nsxv_lbv2.py @@ -0,0 +1,68 @@ +# Copyright 2015 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""nsxv_lbv2 + +Revision ID: 312211a5725f +Revises: 279b70ac3ae8 +Create Date: 2015-09-09 02:02:59.990122 + +""" + +# revision identifiers, used by Alembic. +revision = '312211a5725f' +down_revision = '279b70ac3ae8' +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table( + 'nsxv_lbaas_loadbalancer_bindings', + sa.Column('loadbalancer_id', sa.String(length=36), nullable=False), + sa.Column('edge_id', sa.String(length=36), nullable=False), + sa.Column('edge_fw_rule_id', sa.String(length=36), nullable=False), + sa.Column('vip_address', sa.String(length=36), nullable=False), + sa.PrimaryKeyConstraint('loadbalancer_id')) + op.create_table( + 'nsxv_lbaas_listener_bindings', + sa.Column('loadbalancer_id', sa.String(length=36), nullable=False), + sa.Column('listener_id', sa.String(length=36), nullable=False), + sa.Column('app_profile_id', sa.String(length=36), nullable=False), + sa.Column('vse_id', sa.String(length=36), nullable=False), + sa.PrimaryKeyConstraint('loadbalancer_id', 'listener_id')) + op.create_table( + 'nsxv_lbaas_pool_bindings', + sa.Column('loadbalancer_id', sa.String(length=36), nullable=False), + sa.Column('listener_id', sa.String(length=36), nullable=False), + sa.Column('pool_id', sa.String(length=36), nullable=False), + sa.Column('edge_pool_id', sa.String(length=36), nullable=False), + sa.PrimaryKeyConstraint('loadbalancer_id', 'listener_id', 'pool_id')) + op.create_table( + 'nsxv_lbaas_monitor_bindings', + sa.Column('loadbalancer_id', sa.String(length=36), nullable=False), + sa.Column('listener_id', sa.String(length=36), nullable=False), + sa.Column('pool_id', sa.String(length=36), nullable=False), + sa.Column('hm_id', sa.String(length=36), nullable=False), + sa.Column('edge_id', sa.String(length=36), nullable=False), + sa.Column('edge_mon_id', sa.String(length=36), nullable=False), + sa.PrimaryKeyConstraint('loadbalancer_id', 'listener_id', 'pool_id', + 'hm_id', 'edge_id')) + op.create_table( + 'nsxv_lbaas_certificate_bindings', + sa.Column('cert_id', sa.String(length=36), nullable=False), + sa.Column('edge_id', sa.String(length=36), nullable=False), + sa.Column('edge_cert_id', sa.String(length=36), nullable=False), + sa.PrimaryKeyConstraint('cert_id', 'edge_id')) diff --git a/vmware_nsx/db/nsxv_db.py b/vmware_nsx/db/nsxv_db.py index 65938effa0..f4779e4fd5 100644 --- a/vmware_nsx/db/nsxv_db.py +++ b/vmware_nsx/db/nsxv_db.py @@ -536,3 +536,153 @@ def get_vdr_dhcp_binding_by_vdr(session, vdr_router_id): def delete_vdr_dhcp_binding(session, vdr_router_id): return (session.query(nsxv_models.NsxvVdrDhcpBinding). filter_by(vdr_router_id=vdr_router_id).delete()) + + +def add_nsxv_lbaas_loadbalancer_binding( + session, loadbalancer_id, edge_id, edge_fw_rule_id, vip_address): + with session.begin(subtransactions=True): + binding = nsxv_models.NsxvLbaasLoadbalancerBinding( + loadbalancer_id=loadbalancer_id, + edge_id=edge_id, + edge_fw_rule_id=edge_fw_rule_id, + vip_address=vip_address) + session.add(binding) + return binding + + +def get_nsxv_lbaas_loadbalancer_binding(session, loadbalancer_id): + try: + return session.query( + nsxv_models.NsxvLbaasLoadbalancerBinding).filter_by( + loadbalancer_id=loadbalancer_id).one() + except exc.NoResultFound: + return + + +def del_nsxv_lbaas_loadbalancer_binding(session, loadbalancer_id): + return (session.query(nsxv_models.NsxvLbaasLoadbalancerBinding). + filter_by(loadbalancer_id=loadbalancer_id).delete()) + + +def add_nsxv_lbaas_listener_binding(session, loadbalancer_id, listener_id, + app_profile_id, vse_id): + with session.begin(subtransactions=True): + binding = nsxv_models.NsxvLbaasListenerBinding( + loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + app_profile_id=app_profile_id, + vse_id=vse_id) + session.add(binding) + return binding + + +def get_nsxv_lbaas_listener_binding(session, loadbalancer_id, listener_id): + try: + return session.query( + nsxv_models.NsxvLbaasListenerBinding).filter_by( + loadbalancer_id=loadbalancer_id, listener_id=listener_id).one() + except exc.NoResultFound: + return + + +def del_nsxv_lbaas_listener_binding(session, loadbalancer_id, listener_id): + return (session.query(nsxv_models.NsxvLbaasListenerBinding). + filter_by(loadbalancer_id=loadbalancer_id, + listener_id=listener_id).delete()) + + +def add_nsxv_lbaas_pool_binding(session, loadbalancer_id, listener_id, + pool_id, edge_pool_id): + with session.begin(subtransactions=True): + binding = nsxv_models.NsxvLbaasPoolBinding( + loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + pool_id=pool_id, + edge_pool_id=edge_pool_id) + session.add(binding) + return binding + + +def get_nsxv_lbaas_pool_binding(session, loadbalancer_id, listener_id, + pool_id): + try: + return session.query( + nsxv_models.NsxvLbaasPoolBinding).filter_by( + loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + pool_id=pool_id).one() + except exc.NoResultFound: + return + + +def del_nsxv_lbaas_pool_binding(session, loadbalancer_id, listener_id, + pool_id): + return (session.query(nsxv_models.NsxvLbaasPoolBinding). + filter_by(loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + pool_id=pool_id).delete()) + + +def add_nsxv_lbaas_monitor_binding(session, loadbalancer_id, listener_id, + pool_id, hm_id, edge_id, edge_mon_id): + with session.begin(subtransactions=True): + binding = nsxv_models.NsxvLbaasMonitorBinding( + loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + pool_id=pool_id, + hm_id=hm_id, + edge_id=edge_id, + edge_mon_id=edge_mon_id) + session.add(binding) + return binding + + +def get_nsxv_lbaas_monitor_binding(session, loadbalancer_id, listener_id, + pool_id, hm_id, edge_id): + try: + return session.query( + nsxv_models.NsxvLbaasMonitorBinding).filter_by( + loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + pool_id=pool_id, + hm_id=hm_id, + edge_id=edge_id).one() + except exc.NoResultFound: + return + + +def del_nsxv_lbaas_monitor_binding(session, loadbalancer_id, listener_id, + pool_id, hm_id, edge_id): + return (session.query(nsxv_models.NsxvLbaasMonitorBinding). + filter_by(loadbalancer_id=loadbalancer_id, + listener_id=listener_id, + pool_id=pool_id, + hm_id=hm_id, + edge_id=edge_id).delete()) + + +def add_nsxv_lbaas_certificate_binding(session, cert_id, edge_id, + edge_cert_id): + with session.begin(subtransactions=True): + binding = nsxv_models.NsxvLbaasCertificateBinding( + cert_id=cert_id, + edge_id=edge_id, + edge_cert_id=edge_cert_id) + session.add(binding) + return binding + + +def get_nsxv_lbaas_certificate_binding(session, cert_id, edge_id): + try: + return session.query( + nsxv_models.NsxvLbaasCertificateBinding).filter_by( + cert_id=cert_id, + edge_id=edge_id).one() + except exc.NoResultFound: + return + + +def del_nsxv_lbaas_certificate_binding(session, cert_id, edge_id): + return (session.query(nsxv_models.NsxvLbaasCertificateBinding). + filter_by(cert_id=cert_id, + edge_id=edge_id).delete()) diff --git a/vmware_nsx/db/nsxv_models.py b/vmware_nsx/db/nsxv_models.py index 4278c2741b..be3251237d 100644 --- a/vmware_nsx/db/nsxv_models.py +++ b/vmware_nsx/db/nsxv_models.py @@ -254,3 +254,59 @@ class NsxvVdrDhcpBinding(model_base.BASEV2): dhcp_edge_id, name='unique_nsxv_vdr_dhcp_bindings0dhcp_edge_id'), model_base.BASEV2.__table_args__) + + +class NsxvLbaasLoadbalancerBinding(model_base.BASEV2): + """Mapping between Edge LB and LBaaSv2""" + + __tablename__ = 'nsxv_lbaas_loadbalancer_bindings' + + loadbalancer_id = sa.Column(sa.String(36), primary_key=True) + edge_id = sa.Column(sa.String(36), nullable=False) + edge_fw_rule_id = sa.Column(sa.String(36), nullable=False) + vip_address = sa.Column(sa.String(36), nullable=False) + + +class NsxvLbaasListenerBinding(model_base.BASEV2): + """Mapping between Edge VSE and LBaaSv2""" + + __tablename__ = 'nsxv_lbaas_listener_bindings' + + loadbalancer_id = sa.Column(sa.String(36), primary_key=True) + listener_id = sa.Column(sa.String(36), primary_key=True) + app_profile_id = sa.Column(sa.String(36), nullable=False) + vse_id = sa.Column(sa.String(36), nullable=False) + + +class NsxvLbaasPoolBinding(model_base.BASEV2): + """Mapping between Edge Pool and LBaaSv2""" + + __tablename__ = 'nsxv_lbaas_pool_bindings' + + loadbalancer_id = sa.Column(sa.String(36), primary_key=True) + listener_id = sa.Column(sa.String(36), primary_key=True) + pool_id = sa.Column(sa.String(36), primary_key=True) + edge_pool_id = sa.Column(sa.String(36), nullable=False) + + +class NsxvLbaasMonitorBinding(model_base.BASEV2): + """Mapping between Edge Monitor and LBaaSv2""" + + __tablename__ = 'nsxv_lbaas_monitor_bindings' + + loadbalancer_id = sa.Column(sa.String(36), primary_key=True) + listener_id = sa.Column(sa.String(36), primary_key=True) + pool_id = sa.Column(sa.String(36), primary_key=True) + hm_id = sa.Column(sa.String(36), primary_key=True) + edge_id = sa.Column(sa.String(36), primary_key=True) + edge_mon_id = sa.Column(sa.String(36), nullable=False) + + +class NsxvLbaasCertificateBinding(model_base.BASEV2): + """Mapping between Edge certificate and LBaaSv2 object""" + + __tablename__ = 'nsxv_lbaas_certificate_bindings' + + cert_id = sa.Column(sa.String(36), primary_key=True) + edge_id = sa.Column(sa.String(36), primary_key=True) + edge_cert_id = sa.Column(sa.String(36), nullable=False) diff --git a/vmware_nsx/plugins/nsx_v/vshield/vcns.py b/vmware_nsx/plugins/nsx_v/vshield/vcns.py index 3b7d41c4ab..52403f3292 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/vcns.py +++ b/vmware_nsx/plugins/nsx_v/vshield/vcns.py @@ -792,3 +792,8 @@ class Vcns(object): """Enable HA in the given edge.""" uri = "/api/4.0/edges/%s/highavailability/config?async=true" % edge_id return self.do_request(HTTP_PUT, uri, request_config) + + def upload_edge_certificate(self, edge_id, request): + """Creates a certificate on the specified Edge appliance.""" + uri = '/api/2.0/services/truststore/certificate/%s' % edge_id + return self.do_request(HTTP_POST, uri, request, decode=True) diff --git a/vmware_nsx/plugins/nsx_v/vshield/vcns_driver.py b/vmware_nsx/plugins/nsx_v/vshield/vcns_driver.py index 869c6f9cda..7c73630e96 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/vcns_driver.py +++ b/vmware_nsx/plugins/nsx_v/vshield/vcns_driver.py @@ -19,18 +19,20 @@ import os from oslo_config import cfg from oslo_log import log as logging -from vmware_nsx.common import config # noqa from vmware_nsx.plugins.nsx_v.vshield import edge_appliance_driver from vmware_nsx.plugins.nsx_v.vshield import edge_firewall_driver -from vmware_nsx.plugins.nsx_v.vshield import edge_loadbalancer_driver from vmware_nsx.plugins.nsx_v.vshield.tasks import tasks from vmware_nsx.plugins.nsx_v.vshield import vcns +from vmware_nsx.services.lbaas.nsx_v.v1 import edge_loadbalancer_driver +from vmware_nsx.services.lbaas.nsx_v.v2 import ( + edge_loadbalancer_driver_v2 as lbaas_v2) LOG = logging.getLogger(__name__) class VcnsDriver(edge_appliance_driver.EdgeApplianceDriver, edge_loadbalancer_driver.EdgeLbDriver, + lbaas_v2.EdgeLoadbalancerDriverV2, edge_firewall_driver.EdgeFirewallDriver): def __init__(self, callbacks): diff --git a/vmware_nsx/services/lbaas/__init__.py b/vmware_nsx/services/lbaas/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/services/lbaas/nsx_v/__init__.py b/vmware_nsx/services/lbaas/nsx_v/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py new file mode 100644 index 0000000000..6781ee7d7a --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/lbaas_common.py @@ -0,0 +1,250 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import netaddr +import xml.etree.ElementTree as et + +from neutron.common import exceptions as n_exc + +from vmware_nsx.common import locking +from vmware_nsx.db import nsxv_db +from vmware_nsx.plugins.nsx_v.vshield import vcns as nsxv_api + +MEMBER_ID_PFX = 'member-' +LBAAS_FW_SECTION_NAME = 'LBaaS FW Rules' + + +def get_member_id(member_id): + return MEMBER_ID_PFX + member_id + + +def get_lbaas_edge_id_for_subnet(context, plugin, subnet_id): + """ + Grab the id of an Edge appliance that is connected to subnet_id. + """ + subnet = plugin.get_subnet(context, subnet_id) + net_id = subnet.get('network_id') + filters = {'network_id': [net_id], + 'device_owner': ['network:router_interface']} + attached_routers = plugin.get_ports(context.elevated(), + filters=filters, + fields=['device_id']) + + for attached_router in attached_routers: + router = plugin.get_router(context, attached_router['device_id']) + if router['router_type'] == 'exclusive': + rtr_bindings = nsxv_db.get_nsxv_router_binding(context.session, + router['id']) + return rtr_bindings['edge_id'] + + +def find_address_in_same_subnet(ip_addr, address_groups): + """ + Lookup an address group with a matching subnet to ip_addr. + If found, return address_group. + """ + for address_group in address_groups['addressGroups']: + net_addr = '%(primaryAddress)s/%(subnetPrefixLength)s' % address_group + if netaddr.IPAddress(ip_addr) in netaddr.IPNetwork(net_addr): + return address_group + + +def add_address_to_address_groups(ip_addr, address_groups): + """ + Add ip_addr as a secondary IP address to an address group which belongs to + the same subnet. + """ + address_group = find_address_in_same_subnet( + ip_addr, address_groups) + if address_group: + sec_addr = address_group.get('secondaryAddresses') + if not sec_addr: + sec_addr = { + 'type': 'secondary_addresses', + 'ipAddress': [ip_addr]} + else: + sec_addr['ipAddress'].append(ip_addr) + address_group['secondaryAddresses'] = sec_addr + return True + return False + + +def del_address_from_address_groups(ip_addr, address_groups): + """ + Delete ip_addr from secondary address list in address groups. + """ + address_group = find_address_in_same_subnet(ip_addr, address_groups) + if address_group: + sec_addr = address_group.get('secondaryAddresses') + if sec_addr and ip_addr in sec_addr['ipAddress']: + sec_addr['ipAddress'].remove(ip_addr) + return True + return False + + +def vip_as_secondary_ip(vcns, edge_id, vip, handler): + with locking.LockManager.get_lock(edge_id, external=True): + r = vcns.get_interfaces(edge_id)[1] + vnics = r.get('vnics', []) + for vnic in vnics: + if vnic['type'] == 'trunk': + for sub_interface in vnic.get('subInterfaces', {}).get( + 'subInterfaces', []): + address_groups = sub_interface.get('addressGroups') + if handler(vip, address_groups): + vcns.update_interface(edge_id, vnic) + return True + else: + address_groups = vnic.get('addressGroups') + if handler(vip, address_groups): + vcns.update_interface(edge_id, vnic) + return True + return False + + +def add_vip_as_secondary_ip(vcns, edge_id, vip): + """ + Edge appliance requires that a VIP will be configured as a primary + or a secondary IP address on an interface. + To do so, we locate an interface which is connected to the same subnet + that vip belongs to. + This can be a regular interface, on a sub-interface on a trunk. + """ + if not vip_as_secondary_ip(vcns, edge_id, vip, + add_address_to_address_groups): + + msg = _('Failed to add VIP %(vip)s as secondary IP on ' + 'Edge %(edge_id)s') % {'vip': vip, 'edge_id': edge_id} + raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) + + +def del_vip_as_secondary_ip(vcns, edge_id, vip): + """ + While removing vip, delete the secondary interface from Edge config. + """ + if not vip_as_secondary_ip(vcns, edge_id, vip, + del_address_from_address_groups): + + msg = _('Failed to delete VIP %(vip)s as secondary IP on ' + 'Edge %(edge_id)s') % {'vip': vip, 'edge_id': edge_id} + raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) + + +def extract_resource_id(location_uri): + """ + Edge assigns an ID for each resource that is being created: + it is postfixes the uri specified in the Location header. + This ID should be used while updating/deleting this resource. + """ + uri_elements = location_uri.split('/') + return uri_elements[-1] + + +def add_vip_fw_rule(vcns, edge_id, vip_id, ip_address): + fw_rule = { + 'firewallRules': [ + {'action': 'accept', 'destination': { + 'ipAddress': [ip_address]}, + 'enabled': True, + 'name': vip_id}]} + + with locking.LockManager.get_lock(edge_id, external=True): + h = vcns.add_firewall_rule(edge_id, fw_rule)[0] + fw_rule_id = extract_resource_id(h['location']) + + return fw_rule_id + + +def del_vip_fw_rule(vcns, edge_id, vip_fw_rule_id): + with locking.LockManager.get_lock(edge_id, external=True): + vcns.delete_firewall_rule(edge_id, vip_fw_rule_id) + + +def get_edge_ip_addresses(vcns, edge_id): + edge_ips = [] + r = vcns.get_interfaces(edge_id)[1] + vnics = r.get('vnics', []) + for vnic in vnics: + if vnic['type'] == 'trunk': + for sub_interface in vnic.get('subInterfaces', {}).get( + 'subInterfaces', []): + address_groups = sub_interface.get('addressGroups') + for address_group in address_groups['addressGroups']: + edge_ips.append(address_group['primaryAddress']) + + else: + address_groups = vnic.get('addressGroups') + for address_group in address_groups['addressGroups']: + edge_ips.append(address_group['primaryAddress']) + return edge_ips + + +def update_pool_fw_rule(vcns, pool_id, edge_id, section_id, member_ips): + edge_ips = get_edge_ip_addresses(vcns, edge_id) + + with locking.LockManager.get_lock('lbaas-fw-section', external=True): + section_uri = '%s/%s/%s' % (nsxv_api.FIREWALL_PREFIX, + 'layer3sections', + section_id) + xml_section = vcns.get_section(section_uri)[1] + section = et.fromstring(xml_section) + pool_rule = None + for rule in section.iter('rule'): + if rule.find('name').text == pool_id: + pool_rule = rule + if member_ips: + pool_rule.find('sources').find('source').find( + 'value').text = (','.join(edge_ips)) + pool_rule.find('destinations').find( + 'destination').find('value').text = ','.join( + member_ips) + else: + section.remove(pool_rule) + break + + if member_ips and pool_rule is None: + pool_rule = et.SubElement(section, 'rule') + et.SubElement(pool_rule, 'name').text = pool_id + et.SubElement(pool_rule, 'action').text = 'allow' + sources = et.SubElement(pool_rule, 'sources') + sources.attrib['excluded'] = 'false' + source = et.SubElement(sources, 'source') + et.SubElement(source, 'type').text = 'Ipv4Address' + et.SubElement(source, 'value').text = ','.join(edge_ips) + + destinations = et.SubElement(pool_rule, 'destinations') + destinations.attrib['excluded'] = 'false' + destination = et.SubElement(destinations, 'destination') + et.SubElement(destination, 'type').text = 'Ipv4Address' + et.SubElement(destination, 'value').text = ','.join(member_ips) + + vcns.update_section(section_uri, + et.tostring(section, encoding="us-ascii"), + None) + + +def get_lbaas_fw_section_id(vcns): + # Avoid concurrent creation of section by multiple neutron + # instances + with locking.LockManager.get_lock('lbaas-fw-section', + external=True): + fw_section_id = vcns.get_section_id(LBAAS_FW_SECTION_NAME) + if not fw_section_id: + section = et.Element('section') + section.attrib['name'] = LBAAS_FW_SECTION_NAME + sect = vcns.create_section('ip', et.tostring(section))[1] + fw_section_id = et.fromstring(sect).attrib['id'] + + return fw_section_id diff --git a/vmware_nsx/services/lbaas/nsx_v/lbaas_const.py b/vmware_nsx/services/lbaas/nsx_v/lbaas_const.py new file mode 100644 index 0000000000..72c8b66f29 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/lbaas_const.py @@ -0,0 +1,56 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +LB_METHOD_ROUND_ROBIN = 'ROUND_ROBIN' +LB_METHOD_LEAST_CONNECTIONS = 'LEAST_CONNECTIONS' +LB_METHOD_SOURCE_IP = 'SOURCE_IP' + +BALANCE_MAP = { + LB_METHOD_ROUND_ROBIN: 'round-robin', + LB_METHOD_LEAST_CONNECTIONS: 'leastconn', + LB_METHOD_SOURCE_IP: 'ip-hash'} + +LB_PROTOCOL_TCP = 'TCP' +LB_PROTOCOL_HTTP = 'HTTP' +LB_PROTOCOL_HTTPS = 'HTTPS' + +PROTOCOL_MAP = { + LB_PROTOCOL_TCP: 'tcp', + LB_PROTOCOL_HTTP: 'http', + LB_PROTOCOL_HTTPS: 'tcp'} + +LB_HEALTH_MONITOR_PING = 'PING' +LB_HEALTH_MONITOR_TCP = 'TCP' +LB_HEALTH_MONITOR_HTTP = 'HTTP' +LB_HEALTH_MONITOR_HTTPS = 'HTTPS' + +HEALTH_MONITOR_MAP = { + LB_HEALTH_MONITOR_PING: 'icmp', + LB_HEALTH_MONITOR_TCP: 'tcp', + LB_HEALTH_MONITOR_HTTP: 'http', + LB_HEALTH_MONITOR_HTTPS: 'tcp'} + +LB_SESSION_PERSISTENCE_SOURCE_IP = 'SOURCE_IP' +LB_SESSION_PERSISTENCE_HTTP_COOKIE = 'HTTP_COOKIE' +LB_SESSION_PERSISTENCE_APP_COOKIE = 'APP_COOKIE' + +SESSION_PERSISTENCE_METHOD_MAP = { + LB_SESSION_PERSISTENCE_SOURCE_IP: 'sourceip', + LB_SESSION_PERSISTENCE_APP_COOKIE: 'cookie', + LB_SESSION_PERSISTENCE_HTTP_COOKIE: 'cookie'} + +SESSION_PERSISTENCE_COOKIE_MAP = { + LB_SESSION_PERSISTENCE_APP_COOKIE: 'app', + LB_SESSION_PERSISTENCE_HTTP_COOKIE: 'insert'} diff --git a/vmware_nsx/services/lbaas/nsx_v/v1/__init__.py b/vmware_nsx/services/lbaas/nsx_v/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_loadbalancer_driver.py b/vmware_nsx/services/lbaas/nsx_v/v1/edge_loadbalancer_driver.py similarity index 56% rename from vmware_nsx/plugins/nsx_v/vshield/edge_loadbalancer_driver.py rename to vmware_nsx/services/lbaas/nsx_v/v1/edge_loadbalancer_driver.py index d233f57298..ce21c095c6 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_loadbalancer_driver.py +++ b/vmware_nsx/services/lbaas/nsx_v/v1/edge_loadbalancer_driver.py @@ -13,71 +13,20 @@ # License for the specific language governing permissions and limitations # under the License. -import xml.etree.ElementTree as et - -import netaddr -from oslo_log import log as logging -from oslo_utils import excutils - from neutron.common import exceptions as n_exc from neutron.i18n import _LE from neutron import manager from neutron.plugins.common import constants -from vmware_nsx.common import locking -from vmware_nsx.db import nsxv_db -from vmware_nsx.plugins.nsx_v.vshield.common import ( - exceptions as nsxv_exc) -from vmware_nsx.plugins.nsx_v.vshield import vcns as nsxv_api +from oslo_log import log as logging +from oslo_utils import excutils +from vmware_nsx.common import locking +from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as nsxv_exc +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common +from vmware_nsx.services.lbaas.nsx_v import lbaas_const as lb_const LOG = logging.getLogger(__name__) -LB_METHOD_ROUND_ROBIN = 'ROUND_ROBIN' -LB_METHOD_LEAST_CONNECTIONS = 'LEAST_CONNECTIONS' -LB_METHOD_SOURCE_IP = 'SOURCE_IP' - -LB_PROTOCOL_TCP = 'TCP' -LB_PROTOCOL_HTTP = 'HTTP' -LB_PROTOCOL_HTTPS = 'HTTPS' - -LB_HEALTH_MONITOR_PING = 'PING' -LB_HEALTH_MONITOR_TCP = 'TCP' -LB_HEALTH_MONITOR_HTTP = 'HTTP' -LB_HEALTH_MONITOR_HTTPS = 'HTTPS' - -LB_SESSION_PERSISTENCE_SOURCE_IP = 'SOURCE_IP' -LB_SESSION_PERSISTENCE_HTTP_COOKIE = 'HTTP_COOKIE' -LB_SESSION_PERSISTENCE_APP_COOKIE = 'APP_COOKIE' - -BALANCE_MAP = { - LB_METHOD_ROUND_ROBIN: 'round-robin', - LB_METHOD_LEAST_CONNECTIONS: 'leastconn', - LB_METHOD_SOURCE_IP: 'ip-hash'} - -PROTOCOL_MAP = { - LB_PROTOCOL_TCP: 'tcp', - LB_PROTOCOL_HTTP: 'http', - LB_PROTOCOL_HTTPS: 'tcp'} - -HEALTH_MONITOR_MAP = { - LB_HEALTH_MONITOR_PING: 'icmp', - LB_HEALTH_MONITOR_TCP: 'tcp', - LB_HEALTH_MONITOR_HTTP: 'http', - LB_HEALTH_MONITOR_HTTPS: 'tcp'} - -SESSION_PERSISTENCE_METHOD_MAP = { - LB_SESSION_PERSISTENCE_SOURCE_IP: 'sourceip', - LB_SESSION_PERSISTENCE_APP_COOKIE: 'cookie', - LB_SESSION_PERSISTENCE_HTTP_COOKIE: 'cookie'} - -SESSION_PERSISTENCE_COOKIE_MAP = { - LB_SESSION_PERSISTENCE_APP_COOKIE: 'app', - LB_SESSION_PERSISTENCE_HTTP_COOKIE: 'insert'} - -LBAAS_FW_SECTION_NAME = 'LBaaS FW Rules' - -MEMBER_ID_PFX = 'member-' - def convert_lbaas_pool(lbaas_pool): """ @@ -87,7 +36,7 @@ def convert_lbaas_pool(lbaas_pool): 'name': 'pool_' + lbaas_pool['id'], 'description': lbaas_pool.get('description', lbaas_pool.get('name')), - 'algorithm': BALANCE_MAP.get( + 'algorithm': lb_const.BALANCE_MAP.get( lbaas_pool.get('lb_method'), 'round-robin'), 'transparent': False } @@ -110,25 +59,27 @@ def convert_lbaas_app_profile(name, sess_persist, protocol): } # Since SSL Termination is not supported right now, so just use # sslPassthrough method if the protocol is HTTPS. - if protocol == LB_PROTOCOL_HTTPS: + if protocol == lb_const.LB_PROTOCOL_HTTPS: vcns_app_profile['sslPassthrough'] = True persist_type = sess_persist.get('type') if persist_type: # If protocol is not HTTP, only source_ip is supported - if (protocol != LB_PROTOCOL_HTTP and - persist_type != LB_SESSION_PERSISTENCE_SOURCE_IP): + if (protocol != lb_const.LB_PROTOCOL_HTTP and + persist_type != lb_const.LB_SESSION_PERSISTENCE_SOURCE_IP): msg = (_('Invalid %(protocol)s persistence method: %(type)s') % {'protocol': protocol, 'type': persist_type}) raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) persistence = { - 'method': SESSION_PERSISTENCE_METHOD_MAP.get(persist_type)} - if persist_type in SESSION_PERSISTENCE_COOKIE_MAP: + 'method': + lb_const.SESSION_PERSISTENCE_METHOD_MAP.get(persist_type)} + if persist_type in lb_const.SESSION_PERSISTENCE_COOKIE_MAP: persistence.update({ 'cookieName': sess_persist.get('cookie_name', 'default_cookie_name'), - 'cookieMode': SESSION_PERSISTENCE_COOKIE_MAP[persist_type]}) + 'cookieMode': + lb_const.SESSION_PERSISTENCE_COOKIE_MAP[persist_type]}) vcns_app_profile['persistence'] = persistence return vcns_app_profile @@ -159,7 +110,7 @@ def convert_lbaas_member(member): 'weight': member['weight'], 'port': member['protocol_port'], 'monitorPort': member['protocol_port'], - 'name': get_member_id(member['id']), + 'name': lb_common.get_member_id(member['id']), 'condition': 'enabled' if member['admin_state_up'] else 'disabled'} @@ -168,8 +119,7 @@ def convert_lbaas_monitor(monitor): Transform OpenStack health monitor dict to NSXv health monitor dict. """ mon = { - 'type': HEALTH_MONITOR_MAP.get( - monitor['type'], 'icmp'), + 'type': lb_const.HEALTH_MONITOR_MAP.get(monitor['type'], 'icmp'), 'interval': monitor['delay'], 'timeout': monitor['timeout'], 'maxRetries': monitor['max_retries'], @@ -183,72 +133,6 @@ def convert_lbaas_monitor(monitor): return mon -def extract_resource_id(location_uri): - """ - Edge assigns an ID for each resource that is being created: - it is postfixes the uri specified in the Location header. - This ID should be used while updating/deleting this resource. - """ - uri_elements = location_uri.split('/') - return uri_elements[-1] - - -def get_subnet_primary_ip(ip_addr, address_groups): - """ - Retrieve the primary IP of an interface that's attached to the same subnet. - """ - addr_group = find_address_in_same_subnet(ip_addr, address_groups) - return addr_group['primaryAddress'] if addr_group else None - - -def find_address_in_same_subnet(ip_addr, address_groups): - """ - Lookup an address group with a matching subnet to ip_addr. - If found, return address_group. - """ - for address_group in address_groups['addressGroups']: - net_addr = '%(primaryAddress)s/%(subnetPrefixLength)s' % address_group - if netaddr.IPAddress(ip_addr) in netaddr.IPNetwork(net_addr): - return address_group - - -def add_address_to_address_groups(ip_addr, address_groups): - """ - Add ip_addr as a secondary IP address to an address group which belongs to - the same subnet. - """ - address_group = find_address_in_same_subnet( - ip_addr, address_groups) - if address_group: - sec_addr = address_group.get('secondaryAddresses') - if not sec_addr: - sec_addr = { - 'type': 'secondary_addresses', - 'ipAddress': [ip_addr]} - else: - sec_addr['ipAddress'].append(ip_addr) - address_group['secondaryAddresses'] = sec_addr - return True - return False - - -def del_address_from_address_groups(ip_addr, address_groups): - """ - Delete ip_addr from secondary address list in address groups. - """ - address_group = find_address_in_same_subnet(ip_addr, address_groups) - if address_group: - sec_addr = address_group.get('secondaryAddresses') - if sec_addr and ip_addr in sec_addr['ipAddress']: - sec_addr['ipAddress'].remove(ip_addr) - return True - return False - - -def get_member_id(member_id): - return MEMBER_ID_PFX + member_id - - class EdgeLbDriver(object): def __init__(self): super(EdgeLbDriver, self).__init__() @@ -257,7 +141,7 @@ class EdgeLbDriver(object): self.vcns = None self._fw_section_id = None self._lb_plugin = None - self._lb_driver_prop = None + self._lbv1_driver_prop = None def _get_lb_plugin(self): if not self._lb_plugin: @@ -266,220 +150,70 @@ class EdgeLbDriver(object): return self._lb_plugin @property - def _lb_driver(self): - if not self._lb_driver_prop: + def lbv1_driver(self): + if not self._lbv1_driver_prop: plugin = self._get_lb_plugin() - self._lb_driver_prop = plugin.drivers['vmwareedge'] + self._lbv1_driver_prop = plugin.drivers['vmwareedge'] - return self._lb_driver_prop + return self._lbv1_driver_prop def _get_lbaas_fw_section_id(self): if not self._fw_section_id: - # Avoid concurrent creation of section by multiple neutron - # instances - with locking.LockManager.get_lock('lbaas-section-creation'): - fw_section_id = self.vcns.get_section_id(LBAAS_FW_SECTION_NAME) - if not fw_section_id: - section = et.Element('section') - section.attrib['name'] = LBAAS_FW_SECTION_NAME - sect = self.vcns.create_section('ip', - et.tostring(section))[1] - fw_section_id = et.fromstring(sect).attrib['id'] - self._fw_section_id = fw_section_id + self._fw_section_id = lb_common.get_lbaas_fw_section_id(self.vcns) return self._fw_section_id - def _get_lb_edge_id(self, context, subnet_id): - """ - Grab the id of an Edge appliance that is connected to subnet_id. - """ - subnet = self.callbacks.plugin.get_subnet(context, subnet_id) - net_id = subnet.get('network_id') - filters = {'network_id': [net_id], - 'device_owner': ['network:router_interface']} - attached_routers = self.callbacks.plugin.get_ports( - context.elevated(), filters=filters, - fields=['device_id']) - - for attached_router in attached_routers: - router = self.callbacks.plugin.get_router( - context, attached_router['device_id']) - if router['router_type'] == 'exclusive': - rtr_bindings = nsxv_db.get_nsxv_router_binding( - context.session, router['id']) - return rtr_bindings['edge_id'] - - def _vip_as_secondary_ip(self, edge_id, vip, handler): - with locking.LockManager.get_lock(edge_id): - r = self.vcns.get_interfaces(edge_id)[1] - vnics = r.get('vnics', []) - for vnic in vnics: - if vnic['type'] == 'trunk': - for sub_interface in vnic.get('subInterfaces').get( - 'subInterfaces'): - address_groups = sub_interface.get('addressGroups') - if handler(vip, address_groups): - self.vcns.update_interface(edge_id, vnic) - return True - else: - address_groups = vnic.get('addressGroups') - if handler(vip, address_groups): - self.vcns.update_interface(edge_id, vnic) - return True - return False - - def _add_vip_as_secondary_ip(self, edge_id, vip): - """ - Edge appliance requires that a VIP will be configured as a primary - or a secondary IP address on an interface. - To do so, we locate an interface which is connected to the same subnet - that vip belongs to. - This can be a regular interface, on a sub-interface on a trunk. - """ - if not self._vip_as_secondary_ip( - edge_id, vip, add_address_to_address_groups): - - msg = _('Failed to add VIP %(vip)s as secondary IP on ' - 'Edge %(edge_id)s') % {'vip': vip, 'edge_id': edge_id} - raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) - - def _del_vip_as_secondary_ip(self, edge_id, vip): - """ - While removing vip, delete the secondary interface from Edge config. - """ - if not self._vip_as_secondary_ip( - edge_id, vip, del_address_from_address_groups): - - msg = _('Failed to delete VIP %(vip)s as secondary IP on ' - 'Edge %(edge_id)s') % {'vip': vip, 'edge_id': edge_id} - raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) - - def _get_edge_ips(self, edge_id): - edge_ips = [] - r = self.vcns.get_interfaces(edge_id)[1] - vnics = r.get('vnics', []) - for vnic in vnics: - if vnic['type'] == 'trunk': - for sub_interface in vnic.get('subInterfaces').get( - 'subInterfaces'): - address_groups = sub_interface.get('addressGroups') - for address_group in address_groups['addressGroups']: - edge_ips.append(address_group['primaryAddress']) - - else: - address_groups = vnic.get('addressGroups') - for address_group in address_groups['addressGroups']: - edge_ips.append(address_group['primaryAddress']) - return edge_ips - - def _update_pool_fw_rule(self, context, pool_id, edge_id, - operation=None, address=None): - edge_ips = self._get_edge_ips(edge_id) - + def _get_pool_member_ips(self, context, pool_id, operation, address): plugin = self._get_lb_plugin() - with locking.LockManager.get_lock('lbaas-fw-section'): - members = plugin.get_members( - context, - filters={'pool_id': [pool_id]}, - fields=['address']) - member_ips = [member['address'] for member in members] - if operation == 'add' and address not in member_ips: - member_ips.append(address) - elif operation == 'del' and address in member_ips: - member_ips.remove(address) + members = plugin.get_members( + context, + filters={'pool_id': [pool_id]}, + fields=['address']) + member_ips = {member['address'] for member in members} + if operation == 'add': + member_ips.add(address) + elif operation == 'del' and address in member_ips: + member_ips.remove(address) - section_uri = '%s/%s/%s' % (nsxv_api.FIREWALL_PREFIX, - 'layer3sections', - self._get_lbaas_fw_section_id()) - xml_section = self.vcns.get_section(section_uri)[1] - section = et.fromstring(xml_section) - pool_rule = None - for rule in section.iter('rule'): - if rule.find('name').text == pool_id: - pool_rule = rule - if member_ips: - pool_rule.find('sources').find('source').find( - 'value').text = (','.join(edge_ips)) - pool_rule.find('destinations').find( - 'destination').find('value').text = ','.join( - member_ips) - else: - section.remove(pool_rule) - break - - if member_ips and pool_rule is None: - pool_rule = et.SubElement(section, 'rule') - et.SubElement(pool_rule, 'name').text = pool_id - et.SubElement(pool_rule, 'action').text = 'allow' - sources = et.SubElement(pool_rule, 'sources') - sources.attrib['excluded'] = 'false' - source = et.SubElement(sources, 'source') - et.SubElement(source, 'type').text = 'Ipv4Address' - et.SubElement(source, 'value').text = ','.join(edge_ips) - - destinations = et.SubElement(pool_rule, 'destinations') - destinations.attrib['excluded'] = 'false' - destination = et.SubElement(destinations, 'destination') - et.SubElement(destination, 'type').text = 'Ipv4Address' - et.SubElement(destination, 'value').text = ','.join(member_ips) - - self.vcns.update_section(section_uri, - et.tostring(section, encoding="us-ascii"), - None) - - def _add_vip_fw_rule(self, edge_id, vip_id, ip_address): - fw_rule = { - 'firewallRules': [ - {'action': 'accept', 'destination': { - 'ipAddress': [ip_address]}, - 'enabled': True, - 'name': vip_id}]} - - with locking.LockManager.get_lock(edge_id): - h = self.vcns.add_firewall_rule(edge_id, fw_rule)[0] - fw_rule_id = extract_resource_id(h['location']) - - return fw_rule_id - - def _del_vip_fw_rule(self, edge_id, vip_fw_rule_id): - with locking.LockManager.get_lock(edge_id): - self.vcns.delete_firewall_rule(edge_id, vip_fw_rule_id) + return list(member_ips) def create_pool(self, context, pool): LOG.debug('Creating pool %s', pool) - edge_id = self._get_lb_edge_id(context, pool['subnet_id']) + edge_id = lb_common.get_lbaas_edge_id_for_subnet( + context, self.callbacks.plugin, pool['subnet_id']) if edge_id is None: - self._lb_driver.pool_failed(context, pool) + self.lbv1_driver.pool_failed(context, pool) msg = _( 'No suitable Edge found for subnet %s') % pool['subnet_id'] raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) edge_pool = convert_lbaas_pool(pool) try: - with locking.LockManager.get_lock(edge_id): + with locking.LockManager.get_lock(edge_id, external=True): h = self.vcns.create_pool(edge_id, edge_pool)[0] - edge_pool_id = extract_resource_id(h['location']) - self._lb_driver.create_pool_successful( + edge_pool_id = lb_common.extract_resource_id(h['location']) + self.lbv1_driver.create_pool_successful( context, pool, edge_id, edge_pool_id) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.pool_failed(context, pool) + self.lbv1_driver.pool_failed(context, pool) LOG.error(_LE('Failed to create pool %s'), pool['id']) def update_pool(self, context, old_pool, pool, pool_mapping): LOG.debug('Updating pool %s to %s', old_pool, pool) edge_pool = convert_lbaas_pool(pool) try: - with locking.LockManager.get_lock(pool_mapping['edge_id']): + with locking.LockManager.get_lock(pool_mapping['edge_id'], + external=True): self.vcns.update_pool(pool_mapping['edge_id'], pool_mapping['edge_pool_id'], edge_pool) - self._lb_driver.pool_successful(context, pool) + self.lbv1_driver.pool_successful(context, pool) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.pool_failed(context, pool) + self.lbv1_driver.pool_failed(context, pool) LOG.error(_LE('Failed to update pool %s'), pool['id']) def delete_pool(self, context, pool, pool_mapping): @@ -487,23 +221,24 @@ class EdgeLbDriver(object): if pool_mapping: try: - with locking.LockManager.get_lock(pool_mapping['edge_id']): + with locking.LockManager.get_lock(pool_mapping['edge_id'], + external=True): self.vcns.delete_pool(pool_mapping['edge_id'], pool_mapping['edge_pool_id']) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.pool_failed(context, pool) + self.lbv1_driver.pool_failed(context, pool) LOG.error(_LE('Failed to delete pool %s'), pool['id']) else: LOG.error(_LE('No mapping found for pool %s'), pool['id']) - self._lb_driver.delete_pool_successful(context, pool) + self.lbv1_driver.delete_pool_successful(context, pool) def create_vip(self, context, vip, pool_mapping): LOG.debug('Create VIP %s', vip) app_profile = convert_lbaas_app_profile( - vip['id'], vip.get('session_persistence') or {}, + vip['id'], vip.get('session_persistence', {}), vip.get('protocol')) if not pool_mapping: @@ -512,34 +247,35 @@ class EdgeLbDriver(object): raise n_exc.BadRequest(resource='edge-lbaas', msg=msg) edge_id = pool_mapping['edge_id'] - app_profile_id = None try: - with locking.LockManager.get_lock(edge_id): + with locking.LockManager.get_lock(edge_id, external=True): h = (self.vcns.create_app_profile(edge_id, app_profile))[0] - app_profile_id = extract_resource_id(h['location']) + app_profile_id = lb_common.extract_resource_id(h['location']) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.vip_failed(context, vip) + self.lbv1_driver.vip_failed(context, vip) LOG.error(_LE('Failed to create app profile on edge: %s'), edge_id) edge_vip = convert_lbaas_vip(vip, app_profile_id, pool_mapping) try: - self._add_vip_as_secondary_ip(edge_id, vip['address']) - with locking.LockManager.get_lock(edge_id): + lb_common.add_vip_as_secondary_ip(self.vcns, edge_id, + vip['address']) + with locking.LockManager.get_lock(edge_id, external=True): h = self.vcns.create_vip(edge_id, edge_vip)[0] - edge_vip_id = extract_resource_id(h['location']) - edge_fw_rule_id = self._add_vip_fw_rule(edge_id, vip['id'], - vip['address']) - self._lb_driver.create_vip_successful( + edge_vip_id = lb_common.extract_resource_id(h['location']) + edge_fw_rule_id = lb_common.add_vip_fw_rule(self.vcns, + edge_id, vip['id'], + vip['address']) + self.lbv1_driver.create_vip_successful( context, vip, edge_id, app_profile_id, edge_vip_id, edge_fw_rule_id) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.vip_failed(context, vip) + self.lbv1_driver.vip_failed(context, vip) LOG.error(_LE('Failed to create vip on Edge: %s'), edge_id) - with locking.LockManager.get_lock(edge_id): + with locking.LockManager.get_lock(edge_id, external=True): self.vcns.delete_app_profile(edge_id, app_profile_id) def update_vip(self, context, old_vip, vip, pool_mapping, vip_mapping): @@ -549,26 +285,26 @@ class EdgeLbDriver(object): edge_vip_id = vip_mapping['edge_vse_id'] app_profile_id = vip_mapping['edge_app_profile_id'] app_profile = convert_lbaas_app_profile( - vip['name'], vip.get('session_persistence') or {}, + vip['name'], vip.get('session_persistence', {}), vip.get('protocol')) try: - with locking.LockManager.get_lock(edge_id): + with locking.LockManager.get_lock(edge_id, external=True): self.vcns.update_app_profile(edge_id, app_profile_id, app_profile) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.vip_failed(context, vip) + self.lbv1_driver.vip_failed(context, vip) LOG.error(_LE('Failed to update app profile on edge: %s'), edge_id) edge_vip = convert_lbaas_vip(vip, app_profile_id, pool_mapping) try: - with locking.LockManager.get_lock(edge_id): + with locking.LockManager.get_lock(edge_id, external=True): self.vcns.update_vip(edge_id, edge_vip_id, edge_vip) - self._lb_driver.vip_successful(context, vip) + self.lbv1_driver.vip_successful(context, vip) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.vip_failed(context, vip) + self.lbv1_driver.vip_failed(context, vip) LOG.error(_LE('Failed to update vip on edge: %s'), edge_id) def delete_vip(self, context, vip, vip_mapping): @@ -582,36 +318,40 @@ class EdgeLbDriver(object): app_profile_id = vip_mapping['edge_app_profile_id'] try: - with locking.LockManager.get_lock(edge_id): + with locking.LockManager.get_lock(edge_id, external=True): self.vcns.delete_vip(edge_id, edge_vse_id) - self._del_vip_as_secondary_ip(edge_id, vip['address']) - self._del_vip_fw_rule(edge_id, vip_mapping['edge_fw_rule_id']) + lb_common.del_vip_as_secondary_ip(self.vcns, edge_id, + vip['address']) + lb_common.del_vip_fw_rule(self.vcns, edge_id, + vip_mapping['edge_fw_rule_id']) + except nsxv_exc.ResourceNotFound: LOG.error(_LE('vip not found on edge: %s'), edge_id) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.vip_failed(context, vip) + self.lbv1_driver.vip_failed(context, vip) LOG.error( _LE('Failed to delete vip on edge: %s'), edge_id) try: - with locking.LockManager.get_lock(edge_id): + with locking.LockManager.get_lock(edge_id, external=True): self.vcns.delete_app_profile(edge_id, app_profile_id) except nsxv_exc.ResourceNotFound: LOG.error(_LE('app profile not found on edge: %s'), edge_id) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.vip_failed(context, vip) + self.lbv1_driver.vip_failed(context, vip) LOG.error( _LE('Failed to delete app profile on Edge: %s'), edge_id) - self._lb_driver.delete_vip_successful(context, vip) + self.lbv1_driver.delete_vip_successful(context, vip) def create_member(self, context, member, pool_mapping): LOG.debug('Creating member %s', member) - with locking.LockManager.get_lock(pool_mapping['edge_id']): + with locking.LockManager.get_lock(pool_mapping['edge_id'], + external=True): edge_pool = self.vcns.get_pool(pool_mapping['edge_id'], pool_mapping['edge_pool_id'])[1] edge_member = convert_lbaas_member(member) @@ -627,28 +367,31 @@ class EdgeLbDriver(object): pool_mapping['edge_pool_id'], edge_pool) - self._update_pool_fw_rule(context, member['pool_id'], - pool_mapping['edge_id'], - 'add', - member['address']) - self._lb_driver.member_successful(context, member) + member_ips = self._get_pool_member_ips( + context, member['pool_id'], 'add', member['address']) + lb_common.update_pool_fw_rule( + self.vcns, member['pool_id'], pool_mapping['edge_id'], + self._get_lbaas_fw_section_id(), member_ips) + + self.lbv1_driver.member_successful(context, member) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.member_failed(context, member) + self.lbv1_driver.member_failed(context, member) LOG.error(_LE('Failed to create member on edge: %s'), pool_mapping['edge_id']) def update_member(self, context, old_member, member, pool_mapping): LOG.debug('Updating member %s to %s', old_member, member) - with locking.LockManager.get_lock(pool_mapping['edge_id']): + with locking.LockManager.get_lock(pool_mapping['edge_id'], + external=True): edge_pool = self.vcns.get_pool(pool_mapping['edge_id'], pool_mapping['edge_pool_id'])[1] edge_member = convert_lbaas_member(member) for i, m in enumerate(edge_pool['member']): - if m['name'] == get_member_id(member['id']): + if m['name'] == lb_common.get_member_id(member['id']): edge_pool['member'][i] = edge_member break @@ -656,10 +399,10 @@ class EdgeLbDriver(object): self.vcns.update_pool(pool_mapping['edge_id'], pool_mapping['edge_pool_id'], edge_pool) - self._lb_driver.member_successful(context, member) + self.lbv1_driver.member_successful(context, member) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.member_failed(context, member) + self.lbv1_driver.member_failed(context, member) LOG.error(_LE('Failed to update member on edge: %s'), pool_mapping['edge_id']) @@ -667,13 +410,14 @@ class EdgeLbDriver(object): LOG.debug('Deleting member %s', member) if pool_mapping: - with locking.LockManager.get_lock(pool_mapping['edge_id']): + with locking.LockManager.get_lock(pool_mapping['edge_id'], + external=True): edge_pool = self.vcns.get_pool( pool_mapping['edge_id'], pool_mapping['edge_pool_id'])[1] for i, m in enumerate(edge_pool['member']): - if m['name'] == get_member_id(member['id']): + if m['name'] == lb_common.get_member_id(member['id']): edge_pool['member'].pop(i) break @@ -681,13 +425,15 @@ class EdgeLbDriver(object): self.vcns.update_pool(pool_mapping['edge_id'], pool_mapping['edge_pool_id'], edge_pool) - self._update_pool_fw_rule(context, member['pool_id'], - pool_mapping['edge_id'], - 'del', - member['address']) + member_ips = self._get_pool_member_ips( + context, member['pool_id'], 'del', member['address']) + lb_common.update_pool_fw_rule( + self.vcns, member['pool_id'], pool_mapping['edge_id'], + self._get_lbaas_fw_section_id(), member_ips) + except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.member_failed(context, member) + self.lbv1_driver.member_failed(context, member) LOG.error(_LE('Failed to update member on edge: %s'), pool_mapping['edge_id']) @@ -699,7 +445,8 @@ class EdgeLbDriver(object): LOG.debug('Create HM %s', health_monitor) edge_mon_id = None - with locking.LockManager.get_lock(pool_mapping['edge_id']): + with locking.LockManager.get_lock(pool_mapping['edge_id'], + external=True): # 1st, we find if we already have a pool with the same monitor, on # the same Edge appliance. # If there is no pool on this Edge which is already associated with @@ -711,10 +458,10 @@ class EdgeLbDriver(object): try: h = self.vcns.create_health_monitor( pool_mapping['edge_id'], edge_monitor)[0] - edge_mon_id = extract_resource_id(h['location']) + edge_mon_id = lb_common.extract_resource_id(h['location']) except nsxv_exc.VcnsApiException: - self._lb_driver.pool_health_monitor_failed(context, + self.lbv1_driver.pool_health_monitor_failed(context, health_monitor, pool_id) with excutils.save_and_reraise_exception(): @@ -737,14 +484,14 @@ class EdgeLbDriver(object): except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.pool_health_monitor_failed(context, + self.lbv1_driver.pool_health_monitor_failed(context, health_monitor, pool_id) LOG.error( _LE('Failed to associate monitor on edge: %s'), pool_mapping['edge_id']) - self._lb_driver.create_pool_health_monitor_successful( + self.lbv1_driver.create_pool_health_monitor_successful( context, health_monitor, pool_id, pool_mapping['edge_id'], edge_mon_id) @@ -755,7 +502,8 @@ class EdgeLbDriver(object): edge_monitor = convert_lbaas_monitor(health_monitor) try: - with locking.LockManager.get_lock(mon_mapping['edge_id']): + with locking.LockManager.get_lock(mon_mapping['edge_id'], + external=True): self.vcns.update_health_monitor( mon_mapping['edge_id'], mon_mapping['edge_monitor_id'], @@ -763,14 +511,14 @@ class EdgeLbDriver(object): except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.pool_health_monitor_failed(context, + self.lbv1_driver.pool_health_monitor_failed(context, health_monitor, pool_id) LOG.error( _LE('Failed to update monitor on edge: %s'), mon_mapping['edge_id']) - self._lb_driver.pool_health_monitor_successful(context, + self.lbv1_driver.pool_health_monitor_successful(context, health_monitor, pool_id) @@ -782,7 +530,8 @@ class EdgeLbDriver(object): if not mon_mapping: return - with locking.LockManager.get_lock(pool_mapping['edge_id']): + with locking.LockManager.get_lock(pool_mapping['edge_id'], + external=True): edge_pool = self.vcns.get_pool(edge_id, pool_mapping['edge_pool_id'])[1] edge_pool['monitorId'].remove(mon_mapping['edge_monitor_id']) @@ -793,7 +542,7 @@ class EdgeLbDriver(object): edge_pool) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.pool_health_monitor_failed(context, + self.lbv1_driver.pool_health_monitor_failed(context, health_monitor, pool_id) LOG.error( @@ -808,13 +557,13 @@ class EdgeLbDriver(object): mon_mapping['edge_monitor_id']) except nsxv_exc.VcnsApiException: with excutils.save_and_reraise_exception(): - self._lb_driver.pool_health_monitor_failed( + self.lbv1_driver.pool_health_monitor_failed( context, health_monitor, pool_id) LOG.error( _LE('Failed to delete monitor on edge: %s'), mon_mapping['edge_id']) - self._lb_driver.delete_pool_health_monitor_successful( + self.lbv1_driver.delete_pool_health_monitor_successful( context, health_monitor, pool_id, mon_mapping) def stats(self, context, pool_id, pool_mapping): @@ -849,7 +598,7 @@ class EdgeLbDriver(object): member_stats = {} for member in pool_stats.get('member', []): - member_id = member['name'][len(MEMBER_ID_PFX):] + member_id = member['name'][len(lb_common.MEMBER_ID_PFX):] if member_map[member_id] != 'ERROR': member_stats[member_id] = { 'status': ('INACTIVE' @@ -865,7 +614,7 @@ class EdgeLbDriver(object): 'total_connections': 0} def is_edge_in_use(self, edge_id): - return self._lb_driver.is_edge_in_use(edge_id) + return self.lbv1_driver.is_edge_in_use(edge_id) def is_subnet_in_use(self, context, subnet_id): plugin = self._get_lb_plugin() diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/__init__.py b/vmware_nsx/services/lbaas/nsx_v/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/base_mgr.py b/vmware_nsx/services/lbaas/nsx_v/v2/base_mgr.py new file mode 100644 index 0000000000..a4963f8414 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/v2/base_mgr.py @@ -0,0 +1,52 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron import manager +from neutron.plugins.common import constants + + +class EdgeLoadbalancerBaseManager(object): + _lbv2_driver = None + _core_plugin = None + + def __init__(self, vcns_driver): + super(EdgeLoadbalancerBaseManager, self).__init__() + self.vcns_driver = vcns_driver + + def _get_plugin(self, plugin_type): + loaded_plugins = manager.NeutronManager.get_service_plugins() + return loaded_plugins[plugin_type] + + @property + def lbv2_driver(self): + if not EdgeLoadbalancerBaseManager._lbv2_driver: + plugin = self._get_plugin( + constants.LOADBALANCERV2) + EdgeLoadbalancerBaseManager._lbv2_driver = ( + plugin.drivers['vmwareedge']) + + return EdgeLoadbalancerBaseManager._lbv2_driver + + @property + def core_plugin(self): + if not EdgeLoadbalancerBaseManager._core_plugin: + EdgeLoadbalancerBaseManager._core_plugin = ( + self._get_plugin(constants.CORE)) + + return EdgeLoadbalancerBaseManager._core_plugin + + @property + def vcns(self): + return self.vcns_driver.vcns diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/edge_loadbalancer_driver_v2.py b/vmware_nsx/services/lbaas/nsx_v/v2/edge_loadbalancer_driver_v2.py new file mode 100644 index 0000000000..302e8dd8c2 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/v2/edge_loadbalancer_driver_v2.py @@ -0,0 +1,36 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from vmware_nsx.services.lbaas.nsx_v.v2 import healthmon_mgr as hm_mgr +from vmware_nsx.services.lbaas.nsx_v.v2 import listener_mgr +from vmware_nsx.services.lbaas.nsx_v.v2 import loadbalancer_mgr as lb_mgr +from vmware_nsx.services.lbaas.nsx_v.v2 import member_mgr +from vmware_nsx.services.lbaas.nsx_v.v2 import pool_mgr + +LOG = logging.getLogger(__name__) + + +class EdgeLoadbalancerDriverV2(object): + @log_helpers.log_method_call + def __init__(self): + self.loadbalancer = lb_mgr.EdgeLoadBalancerManager(self) + self.listener = listener_mgr.EdgeListenerManager(self) + self.pool = pool_mgr.EdgePoolManager(self) + self.member = member_mgr.EdgeMemberManager(self) + self.healthmonitor = hm_mgr.EdgeHealthMonitorManager(self) diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/healthmon_mgr.py b/vmware_nsx/services/lbaas/nsx_v/v2/healthmon_mgr.py new file mode 100644 index 0000000000..0d2454b2c0 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/v2/healthmon_mgr.py @@ -0,0 +1,186 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.i18n import _LE +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx.common import locking +from vmware_nsx.db import nsxv_db +from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as nsxv_exc +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common +from vmware_nsx.services.lbaas.nsx_v import lbaas_const as lb_const +from vmware_nsx.services.lbaas.nsx_v.v2 import base_mgr + +LOG = logging.getLogger(__name__) + + +class EdgeHealthMonitorManager(base_mgr.EdgeLoadbalancerBaseManager): + + def _convert_lbaas_monitor(self, hm): + """ + Transform OpenStack health monitor dict to NSXv health monitor dict. + """ + mon = { + 'type': lb_const.HEALTH_MONITOR_MAP.get(hm.type, 'icmp'), + 'interval': hm.delay, + 'timeout': hm.timeout, + 'maxRetries': hm.max_retries, + 'name': hm.id} + + if hm.http_method: + mon['method'] = hm.http_method + + if hm.url_path: + mon['url'] = hm.url_path + return mon + + @log_helpers.log_method_call + def __init__(self, vcns_driver): + super(EdgeHealthMonitorManager, self).__init__(vcns_driver) + + @log_helpers.log_method_call + def create(self, context, hm): + listener = hm.pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, hm.pool.id) + + edge_id = lb_binding['edge_id'] + edge_pool_id = pool_binding['edge_pool_id'] + + hm_binding = nsxv_db.get_nsxv_lbaas_monitor_binding( + context.session, lb_id, listener.id, hm.pool.id, hm.id, edge_id) + edge_mon_id = None + + if hm_binding: + edge_mon_id = hm_binding['edge_mon_id'] + else: + edge_monitor = self._convert_lbaas_monitor(hm) + try: + with locking.LockManager.get_lock(edge_id, external=True): + h = self.vcns.create_health_monitor(edge_id, + edge_monitor)[0] + edge_mon_id = lb_common.extract_resource_id(h['location']) + + nsxv_db.add_nsxv_lbaas_monitor_binding( + context.session, lb_id, listener.id, hm.pool.id, hm.id, + edge_id, edge_mon_id) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.health_monitor.failed_completion( + context, hm) + LOG.error(_LE('Failed to create health monitor on edge: %s' + ), edge_id) + + try: + # Associate monitor with Edge pool + with locking.LockManager.get_lock(edge_id, external=True): + edge_pool = self.vcns.get_pool(edge_id, edge_pool_id)[1] + if edge_pool.get('monitorId'): + edge_pool['monitorId'].append(edge_mon_id) + else: + edge_pool['monitorId'] = [edge_mon_id] + + self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.health_monitor.failed_completion(context, hm) + LOG.error( + _LE('Failed to create health monitor on edge: %s'), + edge_id) + + self.lbv2_driver.health_monitor.successful_completion(context, hm) + + @log_helpers.log_method_call + def update(self, context, old_hm, new_hm): + listener = new_hm.pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + + edge_id = lb_binding['edge_id'] + + hm_binding = nsxv_db.get_nsxv_lbaas_monitor_binding( + context.session, lb_id, listener.id, new_hm.pool.id, new_hm.id, + edge_id) + + edge_monitor = self._convert_lbaas_monitor(new_hm) + + try: + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.update_health_monitor(edge_id, + hm_binding['edge_mon_id'], + edge_monitor) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.health_monitor.failed_completion(context, + new_hm) + LOG.error( + _LE('Failed to update monitor on edge: %s'), edge_id) + + self.lbv2_driver.health_monitor.successful_completion(context, new_hm) + + @log_helpers.log_method_call + def delete(self, context, hm): + listener = hm.pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, hm.pool.id) + + edge_id = lb_binding['edge_id'] + edge_pool_id = pool_binding['edge_pool_id'] + + hm_binding = nsxv_db.get_nsxv_lbaas_monitor_binding( + context.session, lb_id, listener.id, hm.pool.id, hm.id, edge_id) + + edge_pool = self.vcns.get_pool(edge_id, edge_pool_id)[1] + edge_pool['monitorId'].remove(hm_binding['edge_mon_id']) + + try: + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.health_monitor.failed_completion(context, hm) + LOG.error( + _LE('Failed to delete monitor mapping on edge: %s'), + edge_id) + + # If this monitor is not used on this edge anymore, delete it + if not edge_pool['monitorId']: + try: + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.delete_health_monitor(hm_binding['edge_id'], + hm_binding['edge_mon_id']) + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.health_monitor.failed_completion(context, + hm) + LOG.error( + _LE('Failed to delete monitor on edge: %s'), edge_id) + + nsxv_db.del_nsxv_lbaas_monitor_binding( + context.session, lb_id, listener.id, hm.pool.id, hm.id, edge_id) + self.lbv2_driver.health_monitor.successful_completion( + context, hm, delete=True) diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/listener_mgr.py b/vmware_nsx/services/lbaas/nsx_v/v2/listener_mgr.py new file mode 100644 index 0000000000..7843c6be9f --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/v2/listener_mgr.py @@ -0,0 +1,278 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.i18n import _LE +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx.common import exceptions as nsxv_exc +from vmware_nsx.common import locking +from vmware_nsx.db import nsxv_db +from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as vcns_exc +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common +from vmware_nsx.services.lbaas.nsx_v import lbaas_const as lb_const +from vmware_nsx.services.lbaas.nsx_v.v2 import base_mgr + +LOG = logging.getLogger(__name__) + + +def listener_to_edge_app_profile(listener, edge_cert_id): + edge_app_profile = { + 'insertXForwardedFor': False, + 'name': listener.id, + 'serverSslEnabled': False, + 'sslPassthrough': False, + 'template': listener.protocol, + } + + if listener.protocol == 'HTTPS': + if edge_cert_id: + edge_app_profile['clientSsl'] = { + 'caCertificate': [], + 'clientAuth': 'ignore', + 'crlCertificate': [], + 'serviceCertificate': [edge_cert_id]} + else: + edge_app_profile['sslPassthrough'] = True + + if listener.default_pool: + persistence = None + if listener.pool.sessionpersistence: + persistence = { + 'method': + lb_const.SESSION_PERSISTENCE_METHOD_MAP.get( + listener.pool.sessionpersistence.type)} + + if (listener.pool.sessionpersistence.type in + lb_const.SESSION_PERSISTENCE_COOKIE_MAP): + persistence.update({ + 'cookieName': getattr(listener.pool.sessionpersistence, + 'cookie_name', + 'default_cookie_name'), + 'cookieMode': lb_const.SESSION_PERSISTENCE_COOKIE_MAP[ + listener.pool.sessionpersistence.type]}) + + edge_app_profile['persistence'] = persistence + + return edge_app_profile + + +def listener_to_edge_vse(listener, vip_address, default_pool, app_profile_id): + return { + 'name': 'vip_' + listener.id, + 'description': listener.description, + 'ipAddress': vip_address, + 'protocol': listener.protocol, + 'port': listener.protocol_port, + 'connectionLimit': max(0, listener.connection_limit), + 'defaultPoolId': default_pool, + 'applicationProfileId': app_profile_id} + + +class EdgeListenerManager(base_mgr.EdgeLoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self, vcns_driver): + super(EdgeListenerManager, self).__init__(vcns_driver) + + def _upload_certificate(self, context, edge_id, cert_id, certificate): + cert_binding = nsxv_db.get_nsxv_lbaas_certificate_binding( + context.session, cert_id, edge_id) + if cert_binding: + return cert_binding['edge_cert_id'] + + request = { + 'pemEncoding': certificate.get_certificate(), + 'privateKey': certificate.get_private_key()} + passphrase = certificate.get_private_key_passphrase() + if passphrase: + request['passphrase'] = passphrase + cert_obj = self.vcns.upload_edge_certificate(edge_id, request)[1] + cert_list = cert_obj.get('certificates', {}) + if cert_list: + edge_cert_id = cert_list[0]['objectId'] + else: + error = _("Failed to upload a certificate to edge %s") % edge_id + raise nsxv_exc.NsxPluginException(err_msg=error) + nsxv_db.add_nsxv_lbaas_certificate_binding( + context.session, cert_id, edge_id, edge_cert_id) + return edge_cert_id + + @log_helpers.log_method_call + def create(self, context, listener, certificate=None): + default_pool = None + + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + edge_id = lb_binding['edge_id'] + + if listener.default_pool and listener.default_pool.id: + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, listener.default_pool.id) + if pool_binding: + default_pool = pool_binding['edge_pool_id'] + + edge_cert_id = None + if certificate: + try: + edge_cert_id = self._upload_certificate( + context, edge_id, listener.default_tls_container_id, + certificate) + except Exception: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.listener.failed_completion(context, + listener) + + app_profile = listener_to_edge_app_profile(listener, edge_cert_id) + app_profile_id = None + + try: + with locking.LockManager.get_lock(edge_id, external=True): + h = (self.vcns.create_app_profile(edge_id, app_profile))[0] + app_profile_id = lb_common.extract_resource_id(h['location']) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.listener.failed_completion(context, listener) + LOG.error(_LE('Failed to create app profile on edge: %s'), + lb_binding['edge_id']) + + vse = listener_to_edge_vse(listener, lb_binding['vip_address'], + default_pool, + app_profile_id) + + try: + with locking.LockManager.get_lock(edge_id, external=True): + h = self.vcns.create_vip(edge_id, vse)[0] + edge_vse_id = lb_common.extract_resource_id(h['location']) + + nsxv_db.add_nsxv_lbaas_listener_binding(context.session, + lb_id, + listener.id, + app_profile_id, + edge_vse_id) + self.lbv2_driver.listener.successful_completion(context, listener) + + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.listener.failed_completion(context, listener) + LOG.error(_LE('Failed to create vip on Edge: %s'), edge_id) + self.vcns.delete_app_profile(edge_id, app_profile_id) + + @log_helpers.log_method_call + def update(self, context, old_listener, new_listener, certificate=None): + + default_pool = None + if new_listener.default_pool and new_listener.default_pool.id: + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, new_listener.default_pool.id) + if pool_binding: + default_pool = pool_binding['edge_pool_id'] + + lb_id = new_listener.loadbalancer_id + listener_binding = nsxv_db.get_nsxv_lbaas_listener_binding( + context.session, lb_id, new_listener.id) + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + edge_id = lb_binding['edge_id'] + + edge_cert_id = None + if certificate: + if (old_listener.default_tls_container_id != + new_listener.default_tls_container_id): + try: + edge_cert_id = self._upload_certificate( + context, edge_id, + new_listener.default_tls_container_id, + certificate) + except Exception: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.listener.failed_completion( + context, new_listener) + else: + cert_binding = nsxv_db.get_nsxv_lbaas_certificate_binding( + context.session, new_listener.default_tls_container_id, + edge_id) + edge_cert_id = cert_binding['edge_cert_id'] + + app_profile_id = listener_binding['app_profile_id'] + app_profile = listener_to_edge_app_profile(new_listener, edge_cert_id) + + try: + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.update_app_profile( + edge_id, app_profile_id, app_profile) + + vse = listener_to_edge_vse(new_listener, + lb_binding['vip_address'], + default_pool, + app_profile_id) + + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.update_vip(edge_id, listener_binding['vse_id'], vse) + + self.lbv2_driver.listener.successful_completion(context, + new_listener) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.listener.failed_completion(context, + new_listener) + LOG.error(_LE('Failed to update app profile on edge: %s'), + edge_id) + + @log_helpers.log_method_call + def delete(self, context, listener): + lb_id = listener.loadbalancer_id + listener_binding = nsxv_db.get_nsxv_lbaas_listener_binding( + context.session, lb_id, listener.id) + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + + if lb_binding and listener_binding: + edge_id = lb_binding['edge_id'] + edge_vse_id = listener_binding['vse_id'] + app_profile_id = listener_binding['app_profile_id'] + + try: + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.delete_vip(edge_id, edge_vse_id) + + except vcns_exc.ResourceNotFound: + LOG.error(_LE('vip not found on edge: %s'), edge_id) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.listener.failed_completion(context, + listener) + LOG.error( + _LE('Failed to delete vip on edge: %s'), edge_id) + + try: + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.delete_app_profile(edge_id, app_profile_id) + except vcns_exc.ResourceNotFound: + LOG.error(_LE('app profile not found on edge: %s'), edge_id) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.listener.failed_completion(context, + listener) + LOG.error( + _LE('Failed to delete app profile on Edge: %s'), + edge_id) + + nsxv_db.del_nsxv_lbaas_listener_binding(context.session, lb_id, + listener.id) + + self.lbv2_driver.listener.successful_completion( + context, listener, delete=True) diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py b/vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py new file mode 100644 index 0000000000..3be3c063f6 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/v2/loadbalancer_mgr.py @@ -0,0 +1,89 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.i18n import _LE +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx.db import nsxv_db +from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as nsxv_exc +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common +from vmware_nsx.services.lbaas.nsx_v.v2 import base_mgr + +LOG = logging.getLogger(__name__) + + +class EdgeLoadBalancerManager(base_mgr.EdgeLoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self, vcns_driver): + super(EdgeLoadBalancerManager, self).__init__(vcns_driver) + + @log_helpers.log_method_call + def create(self, context, lb): + try: + edge_id = lb_common.get_lbaas_edge_id_for_subnet( + context, self.core_plugin, lb.vip_subnet_id) + + lb_common.add_vip_as_secondary_ip(self.vcns, edge_id, + lb.vip_address) + edge_fw_rule_id = lb_common.add_vip_fw_rule( + self.vcns, edge_id, lb.id, lb.vip_address) + + nsxv_db.add_nsxv_lbaas_loadbalancer_binding( + context.session, lb.id, edge_id, edge_fw_rule_id, + lb.vip_address) + self.lbv2_driver.load_balancer.successful_completion(context, lb) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.load_balancer.failed_completion(context, lb) + LOG.error(_LE('Failed to create pool %s'), lb.id) + + @log_helpers.log_method_call + def update(self, context, old_lb, new_lb): + self.lbv2_driver.load_balancer.successful_completion(context, new_lb) + + @log_helpers.log_method_call + def delete(self, context, lb): + try: + binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb.id) + lb_common.del_vip_fw_rule(self.vcns, binding['edge_id'], + binding['edge_fw_rule_id']) + lb_common.del_vip_as_secondary_ip(self.vcns, binding['edge_id'], + lb.vip_address) + nsxv_db.del_nsxv_lbaas_loadbalancer_binding(context.session, lb.id) + self.lbv2_driver.load_balancer.successful_completion( + context, lb, delete=True) + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.load_balancer.failed_completion(context, lb) + LOG.error(_LE('Failed to delete pool %s'), lb.id) + + @log_helpers.log_method_call + def refresh(self, context, lb): + # TODO(kobis): implememnt + pass + + @log_helpers.log_method_call + def stats(self, context, lb): + # TODO(kobis): implement + stats = {'bytes_in': 0, + 'bytes_out': 0, + 'active_connections': 0, + 'total_connections': 0} + + return stats diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/member_mgr.py b/vmware_nsx/services/lbaas/nsx_v/v2/member_mgr.py new file mode 100644 index 0000000000..4556e9af30 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/v2/member_mgr.py @@ -0,0 +1,173 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.i18n import _LE +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx.common import locking +from vmware_nsx.db import nsxv_db +from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as nsxv_exc +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common +from vmware_nsx.services.lbaas.nsx_v.v2 import base_mgr + +LOG = logging.getLogger(__name__) + + +class EdgeMemberManager(base_mgr.EdgeLoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self, vcns_driver): + super(EdgeMemberManager, self).__init__(vcns_driver) + self._fw_section_id = None + + def _get_pool_member_ips(self, pool, operation, address): + member_ips = [member.address for member in pool.members] + if operation == 'add' and address not in member_ips: + member_ips.append(address) + elif operation == 'del' and address in member_ips: + member_ips.remove(address) + return member_ips + + def _get_lbaas_fw_section_id(self): + if not self._fw_section_id: + self._fw_section_id = lb_common.get_lbaas_fw_section_id(self.vcns) + return self._fw_section_id + + @log_helpers.log_method_call + def create(self, context, member): + listener = member.pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, member.pool_id) + + edge_id = lb_binding['edge_id'] + edge_pool_id = pool_binding['edge_pool_id'] + with locking.LockManager.get_lock(edge_id, external=True): + edge_pool = self.vcns.get_pool(edge_id, edge_pool_id)[1] + edge_member = { + 'ipAddress': member.address, + 'weight': member.weight, + 'port': member.protocol_port, + 'monitorPort': member.protocol_port, + 'name': lb_common.get_member_id(member.id), + 'condition': + 'enabled' if member.admin_state_up else 'disabled'} + + if edge_pool.get('member'): + edge_pool['member'].append(edge_member) + else: + edge_pool['member'] = [edge_member] + + try: + self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) + + member_ips = self._get_pool_member_ips(member.pool, 'add', + member.address) + lb_common.update_pool_fw_rule(self.vcns, member.pool_id, + edge_id, + self._get_lbaas_fw_section_id(), + member_ips) + + self.lbv2_driver.member.successful_completion(context, member) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.member.failed_completion(context, member) + LOG.error(_LE('Failed to create member on edge: %s'), + edge_id) + + @log_helpers.log_method_call + def update(self, context, old_member, new_member): + listener = new_member.pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding(context.session, + lb_id, listener.id, + new_member.pool_id) + + edge_id = lb_binding['edge_id'] + edge_pool_id = pool_binding['edge_pool_id'] + + edge_member = { + 'ipAddress': new_member.address, + 'weight': new_member.weight, + 'port': new_member.protocol_port, + 'monitorPort': new_member.protocol_port, + 'name': lb_common.get_member_id(new_member.id), + 'condition': + 'enabled' if new_member.admin_state_up else 'disabled'} + + with locking.LockManager.get_lock(edge_id, external=True): + edge_pool = self.vcns.get_pool(edge_id, edge_pool_id)[1] + + if edge_pool.get('member'): + for i, m in enumerate(edge_pool['member']): + if m['name'] == lb_common.get_member_id(new_member.id): + edge_pool['member'][i] = edge_member + break + + try: + self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) + + self.lbv2_driver.member.successful_completion( + context, new_member) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.member.failed_completion( + context, new_member) + LOG.error(_LE('Failed to update member on edge: %s'), + edge_id) + else: + LOG.error(_LE('Pool %(pool_id)s on Edge %(edge_id)s has no ' + 'members to update') + % {'pool_id': new_member.pool.id, + 'edge_id': edge_id}) + + @log_helpers.log_method_call + def delete(self, context, member): + listener = member.pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, member.pool_id) + + edge_id = lb_binding['edge_id'] + edge_pool_id = pool_binding['edge_pool_id'] + + with locking.LockManager.get_lock(edge_id, external=True): + edge_pool = self.vcns.get_pool(edge_id, edge_pool_id)[1] + + for i, m in enumerate(edge_pool['member']): + if m['name'] == lb_common.get_member_id(member.id): + edge_pool['member'].pop(i) + break + + try: + self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) + + self.lbv2_driver.member.successful_completion( + context, member, delete=True) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.member.failed_completion(context, member) + LOG.error(_LE('Failed to delete member on edge: %s'), + edge_id) diff --git a/vmware_nsx/services/lbaas/nsx_v/v2/pool_mgr.py b/vmware_nsx/services/lbaas/nsx_v/v2/pool_mgr.py new file mode 100644 index 0000000000..e15ff02f06 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v/v2/pool_mgr.py @@ -0,0 +1,143 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.i18n import _LE +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx.common import locking +from vmware_nsx.db import nsxv_db +from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as nsxv_exc +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common +from vmware_nsx.services.lbaas.nsx_v import lbaas_const as lb_const +from vmware_nsx.services.lbaas.nsx_v.v2 import base_mgr +from vmware_nsx.services.lbaas.nsx_v.v2 import listener_mgr + +LOG = logging.getLogger(__name__) + + +class EdgePoolManager(base_mgr.EdgeLoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self, vcns_driver): + super(EdgePoolManager, self).__init__(vcns_driver) + + @log_helpers.log_method_call + def create(self, context, pool): + + edge_pool = { + 'name': 'pool_' + pool.id, + 'description': getattr(pool, 'description', getattr(pool, 'name')), + 'algorithm': lb_const.BALANCE_MAP.get(pool.lb_algorithm, + 'round-robin'), + 'transparent': False + } + + listener = pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + listener_binding = nsxv_db.get_nsxv_lbaas_listener_binding( + context.session, lb_id, listener.id) + + edge_id = lb_binding['edge_id'] + + try: + with locking.LockManager.get_lock(edge_id, external=True): + h = self.vcns.create_pool(edge_id, edge_pool)[0] + edge_pool_id = lb_common.extract_resource_id(h['location']) + nsxv_db.add_nsxv_lbaas_pool_binding(context.session, lb_id, + listener.id, + pool.id, + edge_pool_id) + + # Associate listener with pool + vse = listener_mgr.listener_to_edge_vse( + listener, + lb_binding['vip_address'], + edge_pool_id, + listener_binding['app_profile_id']) + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.update_vip(edge_id, listener_binding['vse_id'], vse) + + self.lbv2_driver.pool.successful_completion(context, pool) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.pool.failed_completion(context, pool) + LOG.error(_LE('Failed to create pool %s'), pool['id']) + + @log_helpers.log_method_call + def update(self, context, old_pool, new_pool): + edge_pool = { + 'name': 'pool_' + new_pool.id, + 'description': getattr(new_pool, 'description', + getattr(new_pool, 'name')), + 'algorithm': lb_const.BALANCE_MAP.get( + new_pool.lb_algorithm, 'round-robin'), + 'transparent': False + } + + listener = new_pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, new_pool.id) + + edge_id = lb_binding['edge_id'] + edge_pool_id = pool_binding['edge_pool_id'] + + try: + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.update_pool(edge_id, edge_pool_id, edge_pool) + + self.lbv2_driver.pool.successful_completion(context, new_pool) + + except nsxv_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.pool.failed_completion(context, new_pool) + LOG.error(_LE('Failed to update pool %s'), new_pool['id']) + + @log_helpers.log_method_call + def delete(self, context, pool): + listener = pool.listener + lb_id = listener.loadbalancer_id + lb_binding = nsxv_db.get_nsxv_lbaas_loadbalancer_binding( + context.session, lb_id) + pool_binding = nsxv_db.get_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, pool.id) + listener_binding = nsxv_db.get_nsxv_lbaas_listener_binding( + context.session, lb_id, listener.id) + + edge_id = lb_binding['edge_id'] + edge_pool_id = pool_binding['edge_pool_id'] + + try: + vse = listener_mgr.listener_to_edge_vse( + listener, + lb_binding['vip_address'], + None, + listener_binding['app_profile_id']) + with locking.LockManager.get_lock(edge_id, external=True): + self.vcns.update_vip(edge_id, listener_binding['vse_id'], vse) + self.vcns.delete_pool(edge_id, edge_pool_id) + self.lbv2_driver.pool.successful_completion( + context, pool, delete=True) + nsxv_db.del_nsxv_lbaas_pool_binding( + context.session, lb_id, listener.id, pool.id) + except nsxv_exc.VcnsApiException: + self.lbv2_driver.pool.failed_completion(context, pool) + LOG.error(_LE('Failed to delete pool %s'), pool['id']) diff --git a/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver.py b/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver.py index d268c483f5..623df9ba2f 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver.py +++ b/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver.py @@ -19,6 +19,7 @@ from neutron import context from neutron.tests import base from vmware_nsx.plugins.nsx_v.vshield import vcns_driver +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common EDGE_PROVIDER = ('LOADBALANCER:vmwareedge:neutron.services.' 'loadbalancer.drivers.vmware.edge_driver.' @@ -62,51 +63,6 @@ def lbaas_pool_maker(**kwargs): return lbaas_dict -def if_list_maker(ip_list): - if_list = { - 'vnics': [ - {'index': 0, 'name': 'external', 'addressGroups': { - 'addressGroups': [ - {'subnetMask': '255.255.255.0', - 'primaryAddress': '172.24.4.2', - 'subnetPrefixLength': '24'}]}, - 'portgroupName': 'VM Network', 'label': 'vNic_0', - 'type': 'uplink', 'portgroupId': 'network-13'}, - {'index': 1, 'name': 'internal1', 'addressGroups': { - 'addressGroups': [ - {'subnetPrefixLength': '24', - 'secondaryAddresses': { - 'ipAddress': ip_list, - 'type': 'secondary_addresses'}, - 'primaryAddress': '10.0.0.1', - 'subnetMask': '255.255.255.0'}]}, - 'portgroupName': 'pg1234', - 'label': 'vNic_1', 'type': 'internal', - 'portgroupId': 'virtualwire-31'}, - {'index': 2, 'name': 'vnic2', - 'addressGroups': {'addressGroups': []}, - 'label': 'vNic_2', 'type': 'internal'}, - {'index': 3, 'name': 'vnic3', - 'addressGroups': {'addressGroups': []}, - 'label': 'vNic_3', 'type': 'internal'}]} - return if_list - - -def if_maker(ip_list): - intf = { - 'index': 1, 'name': 'internal1', 'addressGroups': { - 'addressGroups': [ - {'subnetPrefixLength': '24', - 'secondaryAddresses': { - 'ipAddress': ip_list, - 'type': 'secondary_addresses'}, - 'primaryAddress': '10.0.0.1', - 'subnetMask': '255.255.255.0'}]}, - 'portgroupName': 'pg1234', 'label': 'vNic_1', - 'type': 'internal', 'portgroupId': 'virtualwire-31'} - return intf - - def lbaas_vip_maker(**kwargs): lbaas_vip = { 'status': 'PENDING_CREATE', @@ -160,22 +116,37 @@ def lbaas_hmon_maker(**kwargs): return hmon -def firewall_section_maker(ip_list_str): - return ( - '
' + POOL_ID + - 'allow' - 'Ipv4Address10.0.0.1,11.0.0.1' - '' - 'Ipv4Address' + ip_list_str + - '
') - - class TestEdgeLbDriver(base.BaseTestCase): def setUp(self): super(TestEdgeLbDriver, self).setUp() self.context = context.get_admin_context() - self.edge_driver = vcns_driver.VcnsDriver(self) - self.edge_driver._lb_driver_prop = mock.Mock() + callbacks = mock.Mock() + callbacks.plugin = mock.Mock() + self.edge_driver = vcns_driver.VcnsDriver(callbacks) + self.edge_driver._lbv1_driver_prop = mock.Mock() + self._temp_get_lbaas_edge_id_for_subnet = ( + lb_common.get_lbaas_edge_id_for_subnet) + self._temp_update_pool_fw_rule = lb_common.update_pool_fw_rule + self._temp_add_vip_as_secondary_ip = lb_common.add_vip_as_secondary_ip + self._temp_add_vip_fw_rule = lb_common.add_vip_fw_rule + self._temp_del_vip_as_secondary_ip = lb_common.del_vip_as_secondary_ip + self._temp_del_vip_fw_rule = lb_common.del_vip_fw_rule + lb_common.get_lbaas_edge_id_for_subnet = mock.Mock() + lb_common.update_pool_fw_rule = mock.Mock() + lb_common.add_vip_as_secondary_ip = mock.Mock() + lb_common.add_vip_fw_rule = mock.Mock() + lb_common.del_vip_as_secondary_ip = mock.Mock() + lb_common.del_vip_fw_rule = mock.Mock() + + def tearDown(self): + super(TestEdgeLbDriver, self).tearDown() + lb_common.get_lbaas_edge_id_for_subnet = ( + self._temp_get_lbaas_edge_id_for_subnet) + lb_common.update_pool_fw_rule = self._temp_update_pool_fw_rule + lb_common.add_vip_as_secondary_ip = self._temp_add_vip_as_secondary_ip + lb_common.add_vip_fw_rule = self._temp_add_vip_fw_rule + lb_common.del_vip_as_secondary_ip = self._temp_del_vip_as_secondary_ip + lb_common.del_vip_fw_rule = self._temp_del_vip_fw_rule def _mock_edge_driver(self, attr): return mock.patch.object(self.edge_driver, attr) @@ -183,8 +154,8 @@ class TestEdgeLbDriver(base.BaseTestCase): def _mock_edge_driver_vcns(self, attr): return mock.patch.object(self.edge_driver.vcns, attr) - def _mock_edge_lb_driver(self, attr): - return mock.patch.object(self.edge_driver._lb_driver, attr) + def _mock_edge_lbv1_driver(self, attr): + return mock.patch.object(self.edge_driver.lbv1_driver, attr) def test_create_pool(self): lbaas_pool = lbaas_pool_maker() @@ -193,12 +164,11 @@ class TestEdgeLbDriver(base.BaseTestCase): 'transparent': False, 'name': 'pool_' + POOL_ID, 'algorithm': 'round-robin', 'description': ''} - with self._mock_edge_driver('_get_lb_edge_id') as _get_lb_edge_id,\ - self._mock_edge_lb_driver( - 'create_pool_successful') as create_pool_successful,\ + with self._mock_edge_lbv1_driver( + 'create_pool_successful') as create_pool_successful, \ self._mock_edge_driver_vcns('create_pool') as create_pool: - _get_lb_edge_id.return_value = EDGE_ID + lb_common.get_lbaas_edge_id_for_subnet.return_value = EDGE_ID create_pool.return_value = ({'location': 'x/' + EDGE_POOL_ID}, None) @@ -217,7 +187,7 @@ class TestEdgeLbDriver(base.BaseTestCase): 'algorithm': 'leastconn', 'description': ''} pool_mapping = {'edge_id': EDGE_ID, 'edge_pool_id': EDGE_POOL_ID} - with self._mock_edge_lb_driver('pool_successful') as pool_successful,\ + with self._mock_edge_lbv1_driver('pool_successful') as pool_successful,\ self._mock_edge_driver_vcns('update_pool') as update_pool: self.edge_driver.update_pool( @@ -231,38 +201,13 @@ class TestEdgeLbDriver(base.BaseTestCase): pool_mapping = {'edge_id': EDGE_ID, 'edge_pool_id': EDGE_POOL_ID} with self._mock_edge_driver_vcns('delete_pool'),\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'delete_pool_successful') as mock_delete_successful: self.edge_driver.delete_pool( self.context, lbaas_pool, pool_mapping) mock_delete_successful.assert_called_with(self.context, lbaas_pool) - def test__add_vip_as_secondary_ip(self): - update_if = if_maker(['10.0.0.6', '10.0.0.8']) - - with self._mock_edge_driver_vcns('get_interfaces') as mock_get_if,\ - self._mock_edge_driver_vcns( - 'update_interface') as mock_update_if: - - mock_get_if.return_value = (None, if_list_maker(['10.0.0.6'])) - - self.edge_driver._add_vip_as_secondary_ip(EDGE_ID, '10.0.0.8') - mock_update_if.assert_called_with(EDGE_ID, update_if) - - def test__del_vip_as_secondary_ip(self): - update_if = if_maker(['10.0.0.6']) - - with self._mock_edge_driver_vcns('get_interfaces') as mock_get_if,\ - self._mock_edge_driver_vcns( - 'update_interface') as mock_update_if: - - mock_get_if.return_value = (None, if_list_maker(['10.0.0.6', - '10.0.0.8'])) - - self.edge_driver._del_vip_as_secondary_ip(EDGE_ID, '10.0.0.8') - mock_update_if.assert_called_with(EDGE_ID, update_if) - def test_create_vip(self): lbaas_vip = lbaas_vip_maker() edge_app_prof = { @@ -277,24 +222,22 @@ class TestEdgeLbDriver(base.BaseTestCase): pool_mapping = {'edge_id': EDGE_ID, 'edge_pool_id': EDGE_POOL_ID} - with self._mock_edge_driver('_add_vip_as_secondary_ip'),\ - self._mock_edge_driver_vcns( + with self._mock_edge_driver_vcns( 'create_app_profile') as mock_create_app_profile,\ self._mock_edge_driver_vcns('create_vip') as mock_create_vip,\ - self._mock_edge_driver( - '_add_vip_fw_rule') as mock_add_fw_rule,\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'create_vip_successful') as mock_vip_successful: mock_create_app_profile.return_value = ( {'location': 'x/' + APP_PROFILE_ID}, None) mock_create_vip.return_value = ( {'location': 'x/' + EDGE_VSE_ID}, None) - mock_add_fw_rule.return_value = EDGE_FW_RULE_ID + lb_common.add_vip_fw_rule.return_value = EDGE_FW_RULE_ID self.edge_driver.create_vip(self.context, lbaas_vip, pool_mapping) mock_create_app_profile.assert_called_with(EDGE_ID, edge_app_prof) - mock_add_fw_rule.assert_called_with(EDGE_ID, VIP_ID, '10.0.0.8') + lb_common.add_vip_fw_rule.assert_called_with( + self.edge_driver.vcns, EDGE_ID, VIP_ID, '10.0.0.8') mock_create_vip.assert_called_with(EDGE_ID, edge_vip) mock_vip_successful.assert_called_with( self.context, lbaas_vip, EDGE_ID, APP_PROFILE_ID, EDGE_VSE_ID, @@ -322,7 +265,7 @@ class TestEdgeLbDriver(base.BaseTestCase): with self._mock_edge_driver_vcns('update_vip') as mock_upd_vip,\ self._mock_edge_driver_vcns( 'update_app_profile') as mock_upd_app_prof,\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'vip_successful') as mock_vip_successful: self.edge_driver.update_vip(self.context, vip_from, vip_to, @@ -339,19 +282,17 @@ class TestEdgeLbDriver(base.BaseTestCase): 'edge_app_profile_id': APP_PROFILE_ID, 'edge_fw_rule_id': EDGE_FW_RULE_ID} - with self._mock_edge_driver('_del_vip_as_secondary_ip'),\ - self._mock_edge_driver_vcns( + with self._mock_edge_driver_vcns( 'delete_app_profile') as mock_del_app_profile,\ self._mock_edge_driver_vcns('delete_vip') as mock_del_vip,\ - self._mock_edge_driver( - '_del_vip_fw_rule') as mock_del_fw_rule,\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'delete_vip_successful') as mock_del_successful: self.edge_driver.delete_vip(self.context, lbaas_vip, vip_mapping) mock_del_app_profile.assert_called_with(EDGE_ID, APP_PROFILE_ID) mock_del_vip.assert_called_with(EDGE_ID, EDGE_VSE_ID) - mock_del_fw_rule.assert_called_with(EDGE_ID, EDGE_FW_RULE_ID) + lb_common.del_vip_fw_rule.assert_called_with( + self.edge_driver.vcns, EDGE_ID, EDGE_FW_RULE_ID) mock_del_successful.assert_called_with(self.context, lbaas_vip) def test_create_member(self): @@ -367,12 +308,17 @@ class TestEdgeLbDriver(base.BaseTestCase): pool_mapping = {'edge_id': EDGE_ID, 'edge_pool_id': EDGE_POOL_ID} with self._mock_edge_driver_vcns('update_pool'),\ - self._mock_edge_driver('_update_pool_fw_rule'),\ self._mock_edge_driver_vcns('get_pool') as mock_get_pool,\ - self._mock_edge_lb_driver( + self._mock_edge_driver( + '_get_pool_member_ips') as mock_get_ips, \ + self._mock_edge_driver( + '_get_lbaas_fw_section_id') as mock_fw_sect, \ + self._mock_edge_lbv1_driver( 'member_successful') as mock_member_successful: mock_get_pool.return_value = (None, edge_pool) + mock_get_ips.return_value = ['10.0.0.4'] + mock_fw_sect.return_value = 10010 self.edge_driver.create_member(self.context, lbaas_member, pool_mapping) edge_pool['member'].append(edge_member) @@ -395,7 +341,7 @@ class TestEdgeLbDriver(base.BaseTestCase): with self._mock_edge_driver_vcns('get_pool') as mock_get_pool,\ self._mock_edge_driver_vcns( 'update_pool') as mock_update_pool,\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'member_successful') as mock_member_successful: mock_get_pool.return_value = (None, edge_pool) @@ -426,93 +372,20 @@ class TestEdgeLbDriver(base.BaseTestCase): with self._mock_edge_driver('_get_lb_plugin') as mock_get_lb_plugin,\ self._mock_edge_driver_vcns('get_pool') as mock_get_pool,\ + self._mock_edge_driver( + '_get_lbaas_fw_section_id') as mock_fw_sect, \ self._mock_edge_driver_vcns( - 'update_pool') as mock_update_pool,\ - self._mock_edge_driver('_update_pool_fw_rule'): + 'update_pool') as mock_update_pool: mock_get_pool.return_value = (None, edge_pool) self.edge_driver.delete_member(self.context, lbaas_member, pool_mapping) + mock_fw_sect.return_value = 10010 mock_get_lb_plugin.return_value = mock_lb_plugin edge_pool['member'] = [] mock_update_pool.assert_called_with(EDGE_ID, EDGE_POOL_ID, edge_pool) - def test__update_pool_fw_rule_add(self): - edge_fw_section = ( - '
' + POOL_ID + - 'allow' - 'Ipv4Address10.0.0.1,11.0.0.1' - '' - 'Ipv4Address10.0.0.10' - '
') - edge_fw_updated_section = ( - '
' + POOL_ID + - 'allow' - 'Ipv4Address10.0.0.1,11.0.0.1' - '' - 'Ipv4Address10.0.0.10,11.0.0.10' - '
') - - mock_lb_plugin = mock.Mock() - - with self._mock_edge_driver('_get_lb_plugin') as mock_get_lb_plugin,\ - self._mock_edge_driver('_get_edge_ips') as mock_get_edge_ips,\ - self._mock_edge_driver_vcns( - 'get_section') as mock_get_section,\ - self._mock_edge_driver( - '_get_lbaas_fw_section_id') as mock_get_section_id,\ - self._mock_edge_driver_vcns( - 'update_section') as mock_update_section: - - mock_get_section_id.return_value = '1111' - mock_get_edge_ips.return_value = ['10.0.0.1', '11.0.0.1'] - mock_get_lb_plugin.return_value = mock_lb_plugin - mock_lb_plugin.get_members.return_value = [{'address': - '10.0.0.10'}] - mock_get_section.return_value = (None, edge_fw_section) - self.edge_driver._update_pool_fw_rule( - self.context, POOL_ID, EDGE_ID, 'add', '11.0.0.10') - mock_update_section.assert_called_with( - '/api/4.0/firewall/globalroot-0/config/layer3sections/1111', - edge_fw_updated_section, None) - - def test__update_pool_fw_rule_del(self): - edge_fw_section = firewall_section_maker('10.0.0.10,11.0.0.10') - edge_fw_updated_section = firewall_section_maker('10.0.0.10') - - mock_lb_plugin = mock.Mock() - - with self._mock_edge_driver('_get_edge_ips') as mock_get_edge_ips,\ - self._mock_edge_driver( - '_get_lb_plugin') as mock_get_lb_plugin,\ - self._mock_edge_driver_vcns( - 'get_section') as mock_get_section,\ - self._mock_edge_driver( - '_get_lbaas_fw_section_id') as mock_get_section_id,\ - self._mock_edge_driver_vcns( - 'update_section') as mock_update_section: - - mock_get_section_id.return_value = '1111' - mock_get_edge_ips.return_value = ['10.0.0.1', '11.0.0.1'] - mock_get_lb_plugin.return_value = mock_lb_plugin - mock_lb_plugin.get_members.return_value = [ - {'address': '10.0.0.10'}, {'address': '11.0.0.10'}] - mock_get_section.return_value = (None, edge_fw_section) - self.edge_driver._update_pool_fw_rule( - self.context, POOL_ID, EDGE_ID, 'del', '11.0.0.10') - mock_update_section.assert_called_with( - '/api/4.0/firewall/globalroot-0/config/layer3sections/1111', - edge_fw_updated_section, None) - - def test__get_edge_ips(self): - get_if_list = if_list_maker(['10.0.0.6']) - - with mock.patch.object(self.edge_driver.vcns, 'get_interfaces', - return_value=(None, get_if_list)): - ip_list = self.edge_driver._get_edge_ips(EDGE_ID) - self.assertEqual(['172.24.4.2', '10.0.0.1'], ip_list) - def test_create_pool_health_monitor(self): hmon = lbaas_hmon_maker() edge_hm = {'maxRetries': 5, 'interval': 5, 'type': 'icmp', @@ -528,7 +401,7 @@ class TestEdgeLbDriver(base.BaseTestCase): self._mock_edge_driver_vcns('get_pool') as mock_get_pool,\ self._mock_edge_driver_vcns( 'create_health_monitor') as mock_create_mon,\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'create_pool_health_monitor_successful') as ( mock_create_successful): @@ -555,7 +428,7 @@ class TestEdgeLbDriver(base.BaseTestCase): with self._mock_edge_driver_vcns( 'update_health_monitor') as mock_update_mon,\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'pool_health_monitor_successful') as mock_hmon_successful: self.edge_driver.update_pool_health_monitor( @@ -579,7 +452,7 @@ class TestEdgeLbDriver(base.BaseTestCase): self._mock_edge_driver_vcns('get_pool') as mock_get_pool,\ self._mock_edge_driver_vcns( 'delete_health_monitor') as mock_del_mon,\ - self._mock_edge_lb_driver( + self._mock_edge_lbv1_driver( 'delete_pool_health_monitor_successful') as ( mock_del_successful): diff --git a/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py b/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py new file mode 100644 index 0000000000..2871b69487 --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_v/test_edge_loadbalancer_driver_v2.py @@ -0,0 +1,611 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron import context + +import mock +from neutron.tests import base +from neutron_lbaas.services.loadbalancer import data_models as lb_models + +from vmware_nsx.db import nsxv_db +from vmware_nsx.plugins.nsx_v.vshield import vcns_driver +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common +from vmware_nsx.services.lbaas.nsx_v.v2 import base_mgr + + +LB_VIP = '10.0.0.10' +LB_EDGE_ID = 'edge-x' +LB_ID = 'xxx-xxx' +LB_TENANT_ID = 'yyy-yyy' +LB_VIP_FWR_ID = 'fwr-1' +LB_BINDING = {'loadbalancer_id': LB_ID, + 'edge_id': LB_EDGE_ID, + 'edge_fw_rule_id': LB_VIP_FWR_ID, + 'vip_address': LB_VIP} +LISTENER_ID = 'xxx-111' +EDGE_APP_PROFILE_ID = 'appp-x' +EDGE_APP_PROF_DEF = {'sslPassthrough': False, 'insertXForwardedFor': False, + 'serverSslEnabled': False, 'name': LISTENER_ID, + 'template': 'HTTP'} +EDGE_VIP_ID = 'vip-aaa' +EDGE_VIP_DEF = {'protocol': 'HTTP', 'name': 'vip_' + LISTENER_ID, + 'connectionLimit': 0, 'defaultPoolId': None, + 'ipAddress': LB_VIP, 'port': 80, + 'applicationProfileId': EDGE_APP_PROFILE_ID, 'description': ''} +LISTENER_BINDING = {'loadbalancer_id': LB_ID, + 'listener_id': LISTENER_ID, + 'app_profile_id': EDGE_APP_PROFILE_ID, + 'vse_id': EDGE_VIP_ID} +POOL_ID = 'ppp-qqq' +EDGE_POOL_ID = 'pool-xx' +EDGE_POOL_DEF = {'transparent': False, 'name': 'pool_' + POOL_ID, + 'algorithm': 'round-robin', 'description': ''} +POOL_BINDING = {'loadbalancer_id': LB_ID, + 'listener_id': LISTENER_ID, + 'pool_id': POOL_ID, + 'edge_pool_id': EDGE_POOL_ID} +MEMBER_ID = 'mmm-mmm' +MEMBER_ADDRESS = '10.0.0.200' +EDGE_MEMBER_DEF = {'monitorPort': 80, 'name': 'member-' + MEMBER_ID, + 'weight': 1, 'ipAddress': MEMBER_ADDRESS, 'port': 80, + 'condition': 'disabled'} +POOL_FW_SECT = '10001' +HM_ID = 'hhh-mmm' +EDGE_HM_ID = 'hm-xx' +EDGE_HM_DEF = {'maxRetries': 1, 'interval': 3, 'type': 'icmp', 'name': HM_ID, + 'timeout': 3} + +HM_BINDING = {'loadbalancer_id': LB_ID, + 'listener_id': LISTENER_ID, + 'pool_id': POOL_ID, + 'hm_id': HM_ID, + 'edge_id': LB_EDGE_ID, + 'edge_mon_id': EDGE_HM_ID} + + +class BaseTestEdgeLbaasV2(base.BaseTestCase): + def _tested_entity(self): + return None + + def setUp(self): + super(BaseTestEdgeLbaasV2, self).setUp() + + self.context = context.get_admin_context() + callbacks = mock.Mock() + callbacks.plugin = mock.Mock() + self.edge_driver = vcns_driver.VcnsDriver(callbacks) + + self.lbv2_driver = mock.Mock() + self.core_plugin = mock.Mock() + base_mgr.EdgeLoadbalancerBaseManager._lbv2_driver = self.lbv2_driver + base_mgr.EdgeLoadbalancerBaseManager._core_plugin = self.core_plugin + self._patch_lb_plugin(self.lbv2_driver, self._tested_entity) + + self.lb = lb_models.LoadBalancer(LB_ID, LB_TENANT_ID, 'lb-name', '', + 'some-subnet', 'port-id', LB_VIP) + self.listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, + 'l-name', '', None, LB_ID, + 'HTTP', protocol_port=80, + loadbalancer=self.lb) + self.pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool-name', '', + None, 'HTTP', 'ROUND_ROBIN', + listener=self.listener) + self.member = lb_models.Member(MEMBER_ID, LB_TENANT_ID, POOL_ID, + MEMBER_ADDRESS, 80, 1, pool=self.pool) + self.hm = lb_models.HealthMonitor(HM_ID, LB_TENANT_ID, 'PING', 3, 3, + 1, pool=self.pool) + + def tearDown(self): + self._unpatch_lb_plugin(self.lbv2_driver, self._tested_entity) + super(BaseTestEdgeLbaasV2, self).tearDown() + + def _patch_lb_plugin(self, lb_plugin, manager): + self.real_manager = getattr(lb_plugin, manager) + lb_manager = mock.patch.object(lb_plugin, manager).start() + mock.patch.object(lb_manager, 'create').start() + mock.patch.object(lb_manager, 'update').start() + mock.patch.object(lb_manager, 'delete').start() + mock.patch.object(lb_manager, 'successful_completion').start() + + def _unpatch_lb_plugin(self, lb_plugin, manager): + setattr(lb_plugin, manager, self.real_manager) + + +class TestEdgeLbaasV2Loadbalancer(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Loadbalancer, self).setUp() + + @property + def _tested_entity(self): + return 'load_balancer' + + def test_create(self): + with mock.patch.object(lb_common, 'get_lbaas_edge_id_for_subnet' + ) as mock_get_edge, \ + mock.patch.object(lb_common, 'add_vip_as_secondary_ip' + ) as mock_vip_sec_ip, \ + mock.patch.object(lb_common, 'add_vip_fw_rule' + ) as mock_add_vip_fwr, \ + mock.patch.object(nsxv_db, 'add_nsxv_lbaas_loadbalancer_binding' + ) as mock_db_binding: + mock_get_edge.return_value = LB_EDGE_ID + mock_add_vip_fwr.return_value = LB_VIP_FWR_ID + + self.edge_driver.loadbalancer.create(self.context, self.lb) + + mock_vip_sec_ip.assert_called_with(self.edge_driver.vcns, + LB_EDGE_ID, + LB_VIP) + mock_add_vip_fwr.assert_called_with(self.edge_driver.vcns, + LB_EDGE_ID, + LB_ID, + LB_VIP) + mock_db_binding.assert_called_with(self.context.session, + LB_ID, + LB_EDGE_ID, + LB_VIP_FWR_ID, + LB_VIP) + mock_successful_completion = ( + self.lbv2_driver.load_balancer.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.lb) + + def test_update(self): + new_lb = lb_models.LoadBalancer(LB_ID, 'yyy-yyy', 'lb-name', 'heh-huh', + 'some-subnet', 'port-id', LB_VIP) + + self.edge_driver.loadbalancer.update(self.context, self.lb, new_lb) + + mock_successful_completion = ( + self.lbv2_driver.load_balancer.successful_completion) + mock_successful_completion.assert_called_with(self.context, new_lb) + + def test_delete(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_binding, \ + mock.patch.object(lb_common, 'del_vip_fw_rule') as mock_del_fwr, \ + mock.patch.object(lb_common, 'del_vip_as_secondary_ip' + ) as mock_vip_sec_ip, \ + mock.patch.object(nsxv_db, 'del_nsxv_lbaas_loadbalancer_binding', + ) as mock_del_binding: + mock_get_binding.return_value = LB_BINDING + + self.edge_driver.loadbalancer.delete(self.context, self.lb) + + mock_del_fwr.assert_called_with(self.edge_driver.vcns, + LB_EDGE_ID, + LB_VIP_FWR_ID) + mock_vip_sec_ip.assert_called_with(self.edge_driver.vcns, + LB_EDGE_ID, + LB_VIP) + mock_del_binding.assert_called_with(self.context.session, + LB_ID) + mock_successful_completion = ( + self.lbv2_driver.load_balancer.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.lb, + delete=True) + + def test_stats(self): + pass + + def test_refresh(self): + pass + + +class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Listener, self).setUp() + + @property + def _tested_entity(self): + return 'listener' + + def test_create(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(self.edge_driver.vcns, 'create_app_profile' + ) as mock_create_app_prof, \ + mock.patch.object(self.edge_driver.vcns, 'create_vip' + ) as mock_create_vip, \ + mock.patch.object(nsxv_db, 'add_nsxv_lbaas_listener_binding' + ) as mock_add_binding: + mock_get_lb_binding.return_value = LB_BINDING + mock_create_app_prof.return_value = ( + {'location': 'x/' + EDGE_APP_PROFILE_ID}, None) + mock_create_vip.return_value = ( + {'location': 'x/' + EDGE_VIP_ID}, None) + + self.edge_driver.listener.create(self.context, self.listener) + + mock_create_app_prof.assert_called_with(LB_EDGE_ID, + EDGE_APP_PROF_DEF) + mock_create_vip.assert_called_with(LB_EDGE_ID, + EDGE_VIP_DEF) + mock_add_binding.assert_called_with( + self.context.session, LB_ID, LISTENER_ID, EDGE_APP_PROFILE_ID, + EDGE_VIP_ID) + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.listener) + + def test_update(self): + new_listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, + 'l-name', '', None, LB_ID, + 'HTTP', protocol_port=8000, + loadbalancer=self.lb) + + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_listener_binding' + ) as mock_get_listener_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(self.edge_driver.vcns, 'update_app_profile' + ) as mock_upd_app_prof, \ + mock.patch.object(self.edge_driver.vcns, 'update_vip' + ) as mock_upd_vip: + mock_get_listener_binding.return_value = LISTENER_BINDING + mock_get_lb_binding.return_value = LB_BINDING + + self.edge_driver.listener.update( + self.context, self.listener, new_listener) + + mock_upd_app_prof.assert_called_with(LB_EDGE_ID, + EDGE_APP_PROFILE_ID, + EDGE_APP_PROF_DEF) + + edge_vip_def = EDGE_VIP_DEF.copy() + edge_vip_def['port'] = 8000 + mock_upd_vip.assert_called_with(LB_EDGE_ID, EDGE_VIP_ID, + edge_vip_def) + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_listener) + + def test_delete(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_listener_binding' + ) as mock_get_listener_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(self.edge_driver.vcns, 'delete_vip' + ) as mock_del_vip, \ + mock.patch.object(self.edge_driver.vcns, 'delete_app_profile' + ) as mock_del_app_prof, \ + mock.patch.object(nsxv_db, 'del_nsxv_lbaas_listener_binding' + ) as mock_del_binding: + mock_get_listener_binding.return_value = LISTENER_BINDING + mock_get_lb_binding.return_value = LB_BINDING + + self.edge_driver.listener.delete(self.context, self.listener) + + mock_del_vip.assert_called_with(LB_EDGE_ID, EDGE_VIP_ID) + mock_del_app_prof.assert_called_with(LB_EDGE_ID, + EDGE_APP_PROFILE_ID) + mock_del_binding.assert_called_with(self.context.session, + LB_ID, LISTENER_ID) + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.listener, + delete=True) + + +class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Pool, self).setUp() + + @property + def _tested_entity(self): + return 'pool' + + def test_create(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_listener_binding' + ) as mock_get_listener_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(self.edge_driver.vcns, 'create_pool' + ) as mock_create_pool, \ + mock.patch.object(nsxv_db, 'add_nsxv_lbaas_pool_binding' + ) as mock_add_binding, \ + mock.patch.object(self.edge_driver.vcns, 'update_vip' + ) as mock_upd_vip: + mock_get_listener_binding.return_value = LISTENER_BINDING + mock_get_lb_binding.return_value = LB_BINDING + mock_create_pool.return_value = ( + {'location': 'x/' + EDGE_POOL_ID}, None) + + self.edge_driver.pool.create(self.context, self.pool) + + mock_create_pool.assert_called_with(LB_EDGE_ID, + EDGE_POOL_DEF.copy()) + mock_add_binding.assert_called_with(self.context.session, + LB_ID, LISTENER_ID, POOL_ID, + EDGE_POOL_ID) + edge_vip_def = EDGE_VIP_DEF.copy() + edge_vip_def['defaultPoolId'] = EDGE_POOL_ID + mock_upd_vip.assert_called_with(LB_EDGE_ID, EDGE_VIP_ID, + edge_vip_def) + mock_successful_completion = ( + self.lbv2_driver.pool.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.pool) + + def test_update(self): + new_pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool-name', '', + None, 'HTTP', 'LEAST_CONNECTIONS', + listener=self.listener) + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding,\ + mock.patch.object(self.edge_driver.vcns, 'update_pool' + ) as mock_upd_pool: + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + + self.edge_driver.pool.update(self.context, self.pool, new_pool) + + edge_pool_def = EDGE_POOL_DEF.copy() + edge_pool_def['algorithm'] = 'leastconn' + mock_upd_pool.assert_called_with( + LB_EDGE_ID, EDGE_POOL_ID, edge_pool_def) + mock_successful_completion = ( + self.lbv2_driver.pool.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_pool) + + def test_delete(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding,\ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_listener_binding' + ) as mock_get_listener_binding, \ + mock.patch.object(self.edge_driver.vcns, 'update_vip' + ) as mock_upd_vip, \ + mock.patch.object(self.edge_driver.vcns, 'delete_pool' + ) as mock_del_pool, \ + mock.patch.object(nsxv_db, 'del_nsxv_lbaas_pool_binding' + ) as mock_del_binding: + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_listener_binding.return_value = LISTENER_BINDING + + self.edge_driver.pool.delete(self.context, self.pool) + + mock_upd_vip.assert_called_with(LB_EDGE_ID, EDGE_VIP_ID, + EDGE_VIP_DEF) + mock_del_pool.assert_called_with(LB_EDGE_ID, EDGE_POOL_ID) + mock_del_binding.assert_called_with( + self.context.session, LB_ID, LISTENER_ID, POOL_ID) + mock_successful_completion = ( + self.lbv2_driver.pool.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.pool, + delete=True) + + +class TestEdgeLbaasV2Member(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Member, self).setUp() + + @property + def _tested_entity(self): + return 'member' + + def test_create(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.edge_driver.vcns, 'get_pool' + ) as mock_get_pool, \ + mock.patch.object(self.edge_driver.vcns, 'update_pool' + ) as mock_update_pool, \ + mock.patch.object(self.edge_driver.member, + '_get_lbaas_fw_section_id' + ) as mock_get_sect, \ + mock.patch.object(lb_common, 'update_pool_fw_rule' + ) as mock_upd_fw: + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_pool.return_value = (None, EDGE_POOL_DEF.copy()) + mock_get_sect.return_value = POOL_FW_SECT + + self.edge_driver.member.create(self.context, self.member) + + edge_pool_def = EDGE_POOL_DEF.copy() + edge_pool_def['member'] = [EDGE_MEMBER_DEF] + mock_update_pool.assert_called_with( + LB_EDGE_ID, EDGE_POOL_ID, edge_pool_def) + mock_upd_fw.assert_called_with(self.edge_driver.vcns, POOL_ID, + LB_EDGE_ID, POOL_FW_SECT, + [MEMBER_ADDRESS]) + mock_successful_completion = ( + self.lbv2_driver.member.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.member) + + def test_update(self): + new_member = lb_models.Member(MEMBER_ID, LB_TENANT_ID, POOL_ID, + MEMBER_ADDRESS, 8000, 1, True, + pool=self.pool) + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.edge_driver.vcns, 'get_pool' + ) as mock_get_pool, \ + mock.patch.object(self.edge_driver.vcns, 'update_pool' + ) as mock_update_pool: + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + edge_pool_def = EDGE_POOL_DEF.copy() + edge_pool_def['member'] = [EDGE_MEMBER_DEF] + mock_get_pool.return_value = (None, edge_pool_def) + + self.edge_driver.member.update(self.context, self.member, + new_member) + + edge_member_def = EDGE_MEMBER_DEF.copy() + edge_member_def['port'] = 8000 + edge_member_def['monitorPort'] = 8000 + edge_member_def['condition'] = 'enabled' + edge_pool_def['member'] = [edge_member_def] + mock_update_pool.assert_called_with( + LB_EDGE_ID, EDGE_POOL_ID, edge_pool_def) + mock_successful_completion = ( + self.lbv2_driver.member.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_member) + + def test_delete(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.edge_driver.vcns, 'get_pool' + ) as mock_get_pool, \ + mock.patch.object(self.edge_driver.vcns, 'update_pool' + ) as mock_update_pool: + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + edge_pool_def = EDGE_POOL_DEF.copy() + edge_pool_def['member'] = [EDGE_MEMBER_DEF] + mock_get_pool.return_value = (None, edge_pool_def) + + self.edge_driver.member.delete(self.context, self.member) + + edge_pool_def['member'] = [] + mock_update_pool.assert_called_with( + LB_EDGE_ID, EDGE_POOL_ID, edge_pool_def) + mock_successful_completion = ( + self.lbv2_driver.member.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.member, + delete=True) + + +class TestEdgeLbaasV2HealthMonitor(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2HealthMonitor, self).setUp() + + @property + def _tested_entity(self): + return 'health_monitor' + + def test_create(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_monitor_binding' + ) as mock_get_mon_binding, \ + mock.patch.object(self.edge_driver.vcns, 'create_health_monitor' + ) as mock_create_hm, \ + mock.patch.object(nsxv_db, 'add_nsxv_lbaas_monitor_binding' + ) as mock_add_hm_binding, \ + mock.patch.object(self.edge_driver.vcns, 'get_pool' + ) as mock_get_pool, \ + mock.patch.object(self.edge_driver.vcns, 'update_pool' + ) as mock_update_pool: + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_mon_binding.return_value = None + mock_create_hm.return_value = ( + {'location': 'x/' + EDGE_HM_ID}, None) + mock_get_pool.return_value = (None, EDGE_POOL_DEF.copy()) + + self.edge_driver.healthmonitor.create(self.context, self.hm) + + mock_create_hm.assert_called_with(LB_EDGE_ID, EDGE_HM_DEF) + mock_add_hm_binding.assert_called_with( + self.context.session, LB_ID, LISTENER_ID, POOL_ID, HM_ID, + LB_EDGE_ID, EDGE_HM_ID) + edge_pool_def = EDGE_POOL_DEF.copy() + edge_pool_def['monitorId'] = [EDGE_HM_ID] + mock_update_pool.assert_called_with( + LB_EDGE_ID, EDGE_POOL_ID, edge_pool_def) + mock_successful_completion = ( + self.lbv2_driver.health_monitor.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.hm) + + def test_update(self): + new_hm = lb_models.HealthMonitor(HM_ID, LB_TENANT_ID, 'PING', 3, 3, + 3, pool=self.pool) + + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_monitor_binding' + ) as mock_get_mon_binding, \ + mock.patch.object(self.edge_driver.vcns, 'update_health_monitor' + ) as mock_upd_hm: + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_mon_binding.return_value = HM_BINDING + + self.edge_driver.healthmonitor.update( + self.context, self.hm, new_hm) + + edge_hm_def = EDGE_HM_DEF.copy() + edge_hm_def['maxRetries'] = 3 + mock_upd_hm.assert_called_with(LB_EDGE_ID, EDGE_HM_ID, edge_hm_def) + mock_successful_completion = ( + self.lbv2_driver.health_monitor.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_hm) + + def test_delete(self): + with mock.patch.object(nsxv_db, 'get_nsxv_lbaas_loadbalancer_binding' + ) as mock_get_lb_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(nsxv_db, 'get_nsxv_lbaas_monitor_binding' + ) as mock_get_mon_binding, \ + mock.patch.object(self.edge_driver.vcns, 'delete_health_monitor' + ) as mock_del_hm, \ + mock.patch.object(self.edge_driver.vcns, 'get_pool' + ) as mock_get_pool, \ + mock.patch.object(self.edge_driver.vcns, 'update_pool' + ) as mock_update_pool, \ + mock.patch.object(nsxv_db, 'del_nsxv_lbaas_monitor_binding' + ) as mock_del_binding: + + mock_get_lb_binding.return_value = LB_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_mon_binding.return_value = HM_BINDING + edge_pool_def = EDGE_POOL_DEF.copy() + edge_pool_def['monitorId'] = [EDGE_HM_ID] + mock_get_pool.return_value = (None, edge_pool_def) + + self.edge_driver.healthmonitor.delete( + self.context, self.hm) + + mock_del_hm.assert_called_with(LB_EDGE_ID, EDGE_HM_ID) + edge_pool_def['monitorId'] = [] + mock_update_pool.assert_called_with( + LB_EDGE_ID, EDGE_POOL_ID, edge_pool_def) + mock_del_binding.assert_called_with(self.context.session, LB_ID, + LISTENER_ID, POOL_ID, HM_ID, + LB_EDGE_ID) + mock_successful_completion = ( + self.lbv2_driver.health_monitor.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.hm, + delete=True) diff --git a/vmware_nsx/tests/unit/nsx_v/test_lbaas_common.py b/vmware_nsx/tests/unit/nsx_v/test_lbaas_common.py new file mode 100644 index 0000000000..29a58f59e9 --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_v/test_lbaas_common.py @@ -0,0 +1,173 @@ +# Copyright 2015 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from neutron.tests import base + +from vmware_nsx.plugins.nsx_v.vshield import vcns_driver +from vmware_nsx.services.lbaas.nsx_v import lbaas_common as lb_common + + +EDGE_ID = 'edge-x' +POOL_ID = 'b3dfb476-6fdf-4ddd-b6bd-e86ae78dc30b' + + +def firewall_section_maker(if_ip_list, vip_ip_list): + return ( + '
' + POOL_ID + + 'allow' + 'Ipv4Address' + ','.join(if_ip_list) + + '' + 'Ipv4Address' + + ','.join(vip_ip_list) + '' + '
') + + +def if_maker(ip_list): + intf = { + 'index': 1, 'name': 'internal1', 'addressGroups': { + 'addressGroups': [ + {'subnetPrefixLength': '24', + 'secondaryAddresses': { + 'ipAddress': ip_list, + 'type': 'secondary_addresses'}, + 'primaryAddress': '10.0.0.1', + 'subnetMask': '255.255.255.0'}]}, + 'portgroupName': 'pg1234', 'label': 'vNic_1', + 'type': 'internal', 'portgroupId': 'virtualwire-31'} + return intf + + +def if_list_maker(ip_list): + if_list = { + 'vnics': [ + {'index': 0, 'name': 'external', 'addressGroups': { + 'addressGroups': [ + {'subnetMask': '255.255.255.0', + 'primaryAddress': '172.24.4.2', + 'subnetPrefixLength': '24'}]}, + 'portgroupName': 'VM Network', 'label': 'vNic_0', + 'type': 'uplink', 'portgroupId': 'network-13'}, + {'index': 1, 'name': 'internal1', 'addressGroups': { + 'addressGroups': [ + {'subnetPrefixLength': '24', + 'secondaryAddresses': { + 'ipAddress': ip_list, + 'type': 'secondary_addresses'}, + 'primaryAddress': '10.0.0.1', + 'subnetMask': '255.255.255.0'}]}, + 'portgroupName': 'pg1234', + 'label': 'vNic_1', 'type': 'internal', + 'portgroupId': 'virtualwire-31'}, + {'index': 2, 'name': 'vnic2', + 'addressGroups': {'addressGroups': []}, + 'label': 'vNic_2', 'type': 'internal'}, + {'index': 3, 'name': 'vnic3', + 'addressGroups': {'addressGroups': []}, + 'label': 'vNic_3', 'type': 'internal'}]} + return if_list + + +class TestLbaasCommon(base.BaseTestCase): + def setUp(self): + super(TestLbaasCommon, self).setUp() + callbacks = mock.Mock() + callbacks.plugin = mock.Mock() + self.edge_driver = vcns_driver.VcnsDriver(callbacks) + self.edge_driver._lb_driver_prop = mock.Mock() + + def _mock_edge_driver_vcns(self, attr): + return mock.patch.object(self.edge_driver.vcns, attr) + + def test_update_pool_fw_rule_add(self): + vip_ips = ['10.0.0.1', '11.0.0.1'] + member_ips = ['10.0.0.10', '11.0.0.10'] + edge_fw_section = firewall_section_maker(vip_ips, ['10.0.0.10']) + edge_fw_updated_section = firewall_section_maker(vip_ips, member_ips) + + with self._mock_edge_driver_vcns( + 'get_section') as mock_get_section,\ + self._mock_edge_driver_vcns( + 'update_section') as mock_update_section: + + tmp_get_ips = lb_common.get_edge_ip_addresses + lb_common.get_edge_ip_addresses = mock.Mock() + lb_common.get_edge_ip_addresses.return_value = vip_ips + + mock_get_section.return_value = (None, edge_fw_section) + lb_common.update_pool_fw_rule( + self.edge_driver.vcns, POOL_ID, EDGE_ID, '1111', member_ips) + mock_update_section.assert_called_with( + '/api/4.0/firewall/globalroot-0/config/layer3sections/1111', + edge_fw_updated_section, None) + lb_common.get_edge_ip_addresses = tmp_get_ips + + def test_update_pool_fw_rule_del(self): + vip_ips = ['10.0.0.1', '11.0.0.1'] + member_ips = ['10.0.0.10'] + edge_fw_section = firewall_section_maker(vip_ips, ['10.0.0.10', + '11.0.0.10']) + edge_fw_updated_section = firewall_section_maker(vip_ips, member_ips) + + with self._mock_edge_driver_vcns('get_section') as mock_get_section, \ + self._mock_edge_driver_vcns( + 'update_section') as mock_update_section: + + tmp_get_ips = lb_common.get_edge_ip_addresses + lb_common.get_edge_ip_addresses = mock.Mock() + lb_common.get_edge_ip_addresses.return_value = vip_ips + mock_get_section.return_value = (None, edge_fw_section) + lb_common.update_pool_fw_rule( + self.edge_driver.vcns, POOL_ID, EDGE_ID, '1111', member_ips) + mock_update_section.assert_called_with( + '/api/4.0/firewall/globalroot-0/config/layer3sections/1111', + edge_fw_updated_section, None) + lb_common.get_edge_ip_addresses = tmp_get_ips + + def test_add_vip_as_secondary_ip(self): + update_if = if_maker(['10.0.0.6', '10.0.0.8']) + + with self._mock_edge_driver_vcns('get_interfaces') as mock_get_if,\ + self._mock_edge_driver_vcns( + 'update_interface') as mock_update_if: + + mock_get_if.return_value = (None, if_list_maker(['10.0.0.6'])) + + lb_common.add_vip_as_secondary_ip( + self.edge_driver.vcns, EDGE_ID, '10.0.0.8') + mock_update_if.assert_called_with(EDGE_ID, update_if) + + def test_del_vip_as_secondary_ip(self): + update_if = if_maker(['10.0.0.6']) + + with self._mock_edge_driver_vcns('get_interfaces') as mock_get_if,\ + self._mock_edge_driver_vcns( + 'update_interface') as mock_update_if: + + mock_get_if.return_value = (None, if_list_maker(['10.0.0.6', + '10.0.0.8'])) + + lb_common.del_vip_as_secondary_ip( + self.edge_driver.vcns, EDGE_ID, '10.0.0.8') + mock_update_if.assert_called_with(EDGE_ID, update_if) + + def test_get_edge_ip_addresses(self): + get_if_list = if_list_maker(['10.0.0.6']) + + with mock.patch.object(self.edge_driver.vcns, 'get_interfaces', + return_value=(None, get_if_list)): + ip_list = lb_common.get_edge_ip_addresses(self.edge_driver.vcns, + EDGE_ID) + self.assertEqual(['172.24.4.2', '10.0.0.1'], ip_list)