From: linb Date: Tue, 20 Aug 2013 05:35:10 +0000 (+0800) Subject: Support advanced NVP LBaaS Service X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=18d991a6b20300f9b601638383829e7cbd58b697;p=openstack-build%2Fneutron-build.git Support advanced NVP LBaaS Service The patch adds NVP advanced LBaaS service support for NVP with VCNS: * NVP LBaaS is an advanced Service of NVP depending on NVP advanced service router - Once an advanced router id created, one corresponding vshield edge will be deployed, and then we can configure LB service On the vshield edge * NVP LBaaS service plugin still uses LBaaS DB service logic, while finally calling vShield Edge to support FWaaS service - When creating VIP object, service attaches the object to the advanced router with routedserviceinsertion service. Then before pushing VIP VCNS call, the server would first pushing associated POOL VCNS call and associated Monitor VCNS call to vShield Edge. Deleting VIP is opposite operation - Refering to CUD operation of other objects, service would first find the associated VIP object and then find the edge bound to the router which vip inserted. Then service would push corresponding VCNS call to Vshield Edge * on driver part, the driver will first convert the object to VSM known object input, and then send a synchronous JSON calling to VSM, and receive the result Implements: blueprint nvp-lbaas-plugin Change-Id: Iec41f2dc103daddf3bed4d09c147df3865b3dccd --- diff --git a/neutron/db/migration/alembic_migrations/versions/3d6fae8b70b0_nvp_lbaas_plugin.py b/neutron/db/migration/alembic_migrations/versions/3d6fae8b70b0_nvp_lbaas_plugin.py new file mode 100644 index 000000000..cc4807fa1 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/3d6fae8b70b0_nvp_lbaas_plugin.py @@ -0,0 +1,82 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 OpenStack Foundation +# +# 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. +# + +"""nvp lbaas plugin + +Revision ID: 3d6fae8b70b0 +Revises: 3ed8f075e38a +Create Date: 2013-09-13 19:34:41.522665 + +""" + +# revision identifiers, used by Alembic. +revision = '3d6fae8b70b0' +down_revision = '3ed8f075e38a' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin' +] + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + 'vcns_edge_pool_bindings', + sa.Column('pool_id', sa.String(length=36), nullable=False), + sa.Column('edge_id', sa.String(length=36), nullable=False), + sa.Column('pool_vseid', sa.String(length=36), nullable=True), + sa.ForeignKeyConstraint(['pool_id'], ['pools.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('pool_id', 'edge_id') + ) + op.create_table( + 'vcns_edge_monitor_bindings', + sa.Column('monitor_id', sa.String(length=36), nullable=False), + sa.Column('edge_id', sa.String(length=36), nullable=False), + sa.Column('monitor_vseid', sa.String(length=36), nullable=True), + sa.ForeignKeyConstraint(['monitor_id'], ['healthmonitors.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('monitor_id', 'edge_id') + ) + op.create_table( + 'vcns_edge_vip_bindings', + sa.Column('vip_id', sa.String(length=36), nullable=False), + sa.Column('edge_id', sa.String(length=36), nullable=True), + sa.Column('vip_vseid', sa.String(length=36), nullable=True), + sa.Column('app_profileid', sa.String(length=36), nullable=True), + sa.ForeignKeyConstraint(['vip_id'], ['vips.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('vip_id') + ) + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('vcns_edge_vip_bindings') + op.drop_table('vcns_edge_monitor_bindings') + op.drop_table('vcns_edge_pool_bindings') diff --git a/neutron/plugins/nicira/NeutronServicePlugin.py b/neutron/plugins/nicira/NeutronServicePlugin.py index 7161341ed..576e04730 100644 --- a/neutron/plugins/nicira/NeutronServicePlugin.py +++ b/neutron/plugins/nicira/NeutronServicePlugin.py @@ -22,8 +22,10 @@ from oslo.config import cfg from neutron.common import exceptions as q_exc from neutron.db.firewall import firewall_db from neutron.db import l3_db +from neutron.db.loadbalancer import loadbalancer_db from neutron.db import routedserviceinsertion_db as rsi_db from neutron.extensions import firewall as fw_ext +from neutron.openstack.common import excutils from neutron.openstack.common import log as logging from neutron.plugins.common import constants as service_constants from neutron.plugins.nicira.common import config # noqa @@ -39,6 +41,7 @@ from neutron.plugins.nicira.vshield.common import ( constants as vcns_const) from neutron.plugins.nicira.vshield.common.constants import RouterStatus from neutron.plugins.nicira.vshield.common import exceptions +from neutron.plugins.nicira.vshield.tasks.constants import TaskState from neutron.plugins.nicira.vshield.tasks.constants import TaskStatus from neutron.plugins.nicira.vshield import vcns_driver from sqlalchemy.orm import exc as sa_exc @@ -73,12 +76,15 @@ class NvpAdvancedPlugin(sr_db.ServiceRouter_mixin, NeutronPlugin.NvpPluginV2, rsi_db.RoutedServiceInsertionDbMixin, firewall_db.Firewall_db_mixin, + loadbalancer_db.LoadBalancerPluginDb ): + supported_extension_aliases = ( NeutronPlugin.NvpPluginV2.supported_extension_aliases + [ "service-router", "routed-service-insertion", - "fwaas" + "fwaas", + "lbaas" ]) def __init__(self): @@ -257,7 +263,7 @@ class NvpAdvancedPlugin(sr_db.ServiceRouter_mixin, binding['edge_id'], snat, dnat) - def _update_interface(self, context, router): + def _update_interface(self, context, router, sync=False): addr, mask, nexthop = self._get_external_attachment_info( context, router) @@ -267,14 +273,20 @@ class NvpAdvancedPlugin(sr_db.ServiceRouter_mixin, for fip in fip_db: if fip.fixed_port_id: secondary.append(fip.floating_ip_address) + #Add all vip addresses bound on the router + vip_addrs = self._get_all_vip_addrs_by_router_id(context, + router['id']) + secondary.extend(vip_addrs) binding = vcns_db.get_vcns_router_binding(context.session, router['id']) - self.vcns_driver.update_interface( + task = self.vcns_driver.update_interface( router['id'], binding['edge_id'], vcns_const.EXTERNAL_VNIC_INDEX, self.vcns_driver.external_network, addr, mask, secondary=secondary) + if sync: + task.wait(TaskState.RESULT) def _update_router_gw_info(self, context, router_id, info): if not self._is_advanced_service_router(context, router_id): @@ -1006,6 +1018,483 @@ class NvpAdvancedPlugin(sr_db.ServiceRouter_mixin, context, fwr['id'], edge_id) return fwp + # + # LBAAS service plugin implementation + # + def _get_edge_id_by_vip_id(self, context, vip_id): + try: + router_binding = self._get_resource_router_id_bindings( + context, loadbalancer_db.Vip, resource_ids=[vip_id])[0] + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to find the edge with " + "vip_id: %s"), vip_id) + service_binding = vcns_db.get_vcns_router_binding( + context.session, router_binding.router_id) + return service_binding.edge_id + + def _get_all_vip_addrs_by_router_id( + self, context, router_id): + vip_bindings = self._get_resource_router_id_bindings( + context, loadbalancer_db.Vip, router_ids=[router_id]) + vip_addrs = [] + for vip_binding in vip_bindings: + vip = self.get_vip(context, vip_binding.resource_id) + vip_addrs.append(vip.get('address')) + return vip_addrs + + def _add_router_service_insertion_binding(self, context, resource_id, + router_id, + model): + res = { + 'id': resource_id, + 'router_id': router_id + } + self._process_create_resource_router_id(context, res, + model) + + def _resource_set_status(self, context, model, id, status, obj=None, + pool_id=None): + with context.session.begin(subtransactions=True): + try: + qry = context.session.query(model) + if issubclass(model, loadbalancer_db.PoolMonitorAssociation): + res = qry.filter_by(monitor_id=id, + pool_id=pool_id).one() + else: + res = qry.filter_by(id=id).one() + if status == service_constants.PENDING_UPDATE and ( + res.get('status') == service_constants.PENDING_DELETE): + msg = (_("Operation can't be performed, Since resource " + "%(model)s : %(id)s is in DELETEing status!") % + {'model': model, + 'id': id}) + LOG.error(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + else: + res.status = status + except sa_exc.NoResultFound: + msg = (_("Resource %(model)s : %(id)s not found!") % + {'model': model, + 'id': id}) + LOG.exception(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + if obj: + obj['status'] = status + + def _vcns_create_pool_and_monitors(self, context, pool_id, **kwargs): + pool = self.get_pool(context, pool_id) + edge_id = kwargs.get('edge_id') + if not edge_id: + edge_id = self._get_edge_id_by_vip_id( + context, pool['vip_id']) + #Check wheter the pool is already created on the router + #in case of future's M:N relation between Pool and Vip + + #Check associated HealthMonitors and then create them + for monitor_id in pool.get('health_monitors'): + hm = self.get_health_monitor(context, monitor_id) + try: + self.vcns_driver.create_health_monitor( + context, edge_id, hm) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to create healthmonitor " + "associated with pool id: %s!") % pool_id) + for monitor_ide in pool.get('health_monitors'): + if monitor_ide == monitor_id: + break + self.vcns_driver.delete_health_monitor( + context, monitor_ide, edge_id) + #Create the pool on the edge + members = [ + super(NvpAdvancedPlugin, self).get_member( + context, member_id) + for member_id in pool.get('members') + ] + try: + self.vcns_driver.create_pool(context, edge_id, pool, members) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to create pool on vshield edge")) + self.vcns_driver.delete_pool( + context, pool_id, edge_id) + for monitor_id in pool.get('health_monitors'): + self.vcns_driver.delete_health_monitor( + context, monitor_id, edge_id) + + def _vcns_update_pool(self, context, pool, **kwargs): + edge_id = self._get_edge_id_by_vip_id(context, pool['vip_id']) + members = kwargs.get('members') + if not members: + members = [ + super(NvpAdvancedPlugin, self).get_member( + context, member_id) + for member_id in pool.get('members') + ] + self.vcns_driver.update_pool(context, edge_id, pool, members) + + def create_vip(self, context, vip): + LOG.debug(_("create_vip() called")) + router_id = vip['vip'].get(vcns_const.ROUTER_ID) + if not router_id: + msg = _("router_id is not provided!") + LOG.error(msg) + raise q_exc.BadRequest(resource='router', msg=msg) + + if not self._is_advanced_service_router(context, router_id): + msg = _("router_id: %s is not an advanced router!") % router_id + LOG.error(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + + #Check whether the vip port is an external port + subnet_id = vip['vip']['subnet_id'] + network_id = self.get_subnet(context, subnet_id)['network_id'] + ext_net = self._get_network(context, network_id) + if not ext_net.external: + msg = (_("Network '%s' is not a valid external " + "network") % network_id) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + + v = super(NvpAdvancedPlugin, self).create_vip(context, vip) + #Get edge_id for the resource + router_binding = vcns_db.get_vcns_router_binding( + context.session, + router_id) + edge_id = router_binding.edge_id + #Add vip_router binding + self._add_router_service_insertion_binding(context, v['id'], + router_id, + loadbalancer_db.Vip) + #Create the vip port on vShield Edge + router = self._get_router(context, router_id) + self._update_interface(context, router, sync=True) + #Create the vip and associated pool/monitor on the corresponding edge + try: + self._vcns_create_pool_and_monitors( + context, v['pool_id'], edge_id=edge_id) + self.vcns_driver.create_vip(context, edge_id, v) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to create vip!")) + self._delete_resource_router_id_binding( + context, v['id'], loadbalancer_db.Vip) + super(NvpAdvancedPlugin, self).delete_vip(context, v['id']) + self._resource_set_status(context, loadbalancer_db.Vip, + v['id'], service_constants.ACTIVE, v) + + return v + + def update_vip(self, context, id, vip): + edge_id = self._get_edge_id_by_vip_id(context, id) + old_vip = self.get_vip(context, id) + vip['vip']['status'] = service_constants.PENDING_UPDATE + v = super(NvpAdvancedPlugin, self).update_vip(context, id, vip) + if old_vip['pool_id'] != v['pool_id']: + self.vcns_driver.delete_vip(context, id) + #Delete old pool/monitor on the edge + #TODO(linb): Factor out procedure for removing pool and health + #separate method + old_pool = self.get_pool(context, old_vip['pool_id']) + self.vcns_driver.delete_pool( + context, old_vip['pool_id'], edge_id) + for monitor_id in old_pool.get('health_monitors'): + self.vcns_driver.delete_health_monitor( + context, monitor_id, edge_id) + #Create new pool/monitor object on the edge + #TODO(linb): add exception handle if error + self._vcns_create_pool_and_monitors( + context, v['pool_id'], edge_id=edge_id) + self.vcns_driver.create_vip(context, edge_id, v) + return v + try: + self.vcns_driver.update_vip(context, v) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update vip with id: %s!"), id) + self._resource_set_status(context, loadbalancer_db.Vip, + id, service_constants.ERROR, v) + + self._resource_set_status(context, loadbalancer_db.Vip, + v['id'], service_constants.ACTIVE, v) + return v + + def delete_vip(self, context, id): + v = self.get_vip(context, id) + self._resource_set_status( + context, loadbalancer_db.Vip, + id, service_constants.PENDING_DELETE) + try: + self.vcns_driver.delete_vip(context, id) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to delete vip with id: %s!"), id) + self._resource_set_status(context, loadbalancer_db.Vip, + id, service_constants.ERROR) + edge_id = self._get_edge_id_by_vip_id(context, id) + #Check associated HealthMonitors and then delete them + pool = self.get_pool(context, v['pool_id']) + self.vcns_driver.delete_pool(context, v['pool_id'], edge_id) + for monitor_id in pool.get('health_monitors'): + #TODO(linb): do exception handle if error + self.vcns_driver.delete_health_monitor( + context, monitor_id, edge_id) + + router_binding = self._get_resource_router_id_binding( + context, loadbalancer_db.Vip, resource_id=id) + router = self._get_router(context, router_binding.router_id) + self._delete_resource_router_id_binding( + context, id, loadbalancer_db.Vip) + super(NvpAdvancedPlugin, self).delete_vip(context, id) + self._update_interface(context, router, sync=True) + + def update_pool(self, context, id, pool): + pool['pool']['status'] = service_constants.PENDING_UPDATE + p = super(NvpAdvancedPlugin, self).update_pool(context, id, pool) + #Check whether the pool is already associated with the vip + if not p.get('vip_id'): + self._resource_set_status(context, loadbalancer_db.Pool, + p['id'], service_constants.ACTIVE, p) + return p + try: + self._vcns_update_pool(context, p) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update pool with id: %s!"), id) + self._resource_set_status(context, loadbalancer_db.Pool, + p['id'], service_constants.ERROR, p) + self._resource_set_status(context, loadbalancer_db.Pool, + p['id'], service_constants.ACTIVE, p) + return p + + def create_member(self, context, member): + m = super(NvpAdvancedPlugin, self).create_member(context, member) + pool_id = m.get('pool_id') + pool = self.get_pool(context, pool_id) + if not pool.get('vip_id'): + self._resource_set_status(context, loadbalancer_db.Member, + m['id'], service_constants.ACTIVE, m) + return m + self._resource_set_status(context, loadbalancer_db.Pool, + pool_id, + service_constants.PENDING_UPDATE) + try: + self._vcns_update_pool(context, pool) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update pool with the member")) + super(NvpAdvancedPlugin, self).delete_member(context, m['id']) + + self._resource_set_status(context, loadbalancer_db.Pool, + pool_id, service_constants.ACTIVE) + self._resource_set_status(context, loadbalancer_db.Member, + m['id'], service_constants.ACTIVE, m) + return m + + def update_member(self, context, id, member): + member['member']['status'] = service_constants.PENDING_UPDATE + old_member = self.get_member(context, id) + m = super(NvpAdvancedPlugin, self).update_member( + context, id, member) + + if m['pool_id'] != old_member['pool_id']: + old_pool_id = old_member['pool_id'] + old_pool = self.get_pool(context, old_pool_id) + if old_pool.get('vip_id'): + self._resource_set_status( + context, loadbalancer_db.Pool, + old_pool_id, service_constants.PENDING_UPDATE) + try: + self._vcns_update_pool(context, old_pool) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update old pool " + "with the member")) + super(NvpAdvancedPlugin, self).delete_member( + context, m['id']) + self._resource_set_status( + context, loadbalancer_db.Pool, + old_pool_id, service_constants.ACTIVE) + + pool_id = m['pool_id'] + pool = self.get_pool(context, pool_id) + if not pool.get('vip_id'): + self._resource_set_status(context, loadbalancer_db.Member, + m['id'], service_constants.ACTIVE, m) + return m + self._resource_set_status(context, loadbalancer_db.Pool, + pool_id, + service_constants.PENDING_UPDATE) + try: + self._vcns_update_pool(context, pool) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update pool with the member")) + super(NvpAdvancedPlugin, self).delete_member( + context, m['id']) + + self._resource_set_status(context, loadbalancer_db.Pool, + pool_id, service_constants.ACTIVE) + self._resource_set_status(context, loadbalancer_db.Member, + m['id'], service_constants.ACTIVE, m) + return m + + def delete_member(self, context, id): + m = self.get_member(context, id) + super(NvpAdvancedPlugin, self).delete_member(context, id) + pool_id = m['pool_id'] + pool = self.get_pool(context, pool_id) + if not pool.get('vip_id'): + return + self._resource_set_status(context, loadbalancer_db.Pool, + pool_id, service_constants.PENDING_UPDATE) + try: + self._vcns_update_pool(context, pool) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update pool with the member")) + self._resource_set_status(context, loadbalancer_db.Pool, + pool_id, service_constants.ACTIVE) + + def update_health_monitor(self, context, id, health_monitor): + old_hm = super(NvpAdvancedPlugin, self).get_health_monitor( + context, id) + hm = super(NvpAdvancedPlugin, self).update_health_monitor( + context, id, health_monitor) + for hm_pool in hm.get('pools'): + pool_id = hm_pool['pool_id'] + pool = self.get_pool(context, pool_id) + if pool.get('vip_id'): + edge_id = self._get_edge_id_by_vip_id( + context, pool['vip_id']) + try: + self.vcns_driver.update_health_monitor( + context, edge_id, old_hm, hm) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update monitor " + "with id: %s!"), id) + return hm + + def delete_health_monitor(self, context, id): + with context.session.begin(subtransactions=True): + qry = context.session.query( + loadbalancer_db.PoolMonitorAssociation + ).filter_by(monitor_id=id) + for assoc in qry: + pool_id = assoc['pool_id'] + super(NvpAdvancedPlugin, + self).delete_pool_health_monitor(context, + id, + pool_id) + pool = self.get_pool(context, pool_id) + if not pool.get('vip_id'): + continue + edge_id = self._get_edge_id_by_vip_id( + context, pool['vip_id']) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.PENDING_UPDATE) + try: + self._vcns_update_pool(context, pool) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update pool with monitor!")) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.ACTIVE) + try: + self.vcns_driver.delete_health_monitor( + context, id, edge_id) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to delete monitor " + "with id: %s!"), id) + super(NvpAdvancedPlugin, + self).delete_health_monitor(context, id) + self._delete_resource_router_id_binding( + context, id, loadbalancer_db.HealthMonitor) + + super(NvpAdvancedPlugin, self).delete_health_monitor(context, id) + self._delete_resource_router_id_binding( + context, id, loadbalancer_db.HealthMonitor) + + def create_pool_health_monitor(self, context, + health_monitor, pool_id): + monitor_id = health_monitor['health_monitor']['id'] + pool = self.get_pool(context, pool_id) + monitors = pool.get('health_monitors') + if len(monitors) > 0: + msg = _("Vcns right now can only support " + "one monitor per pool") + LOG.error(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + #Check whether the pool is already associated with the vip + if not pool.get('vip_id'): + res = super(NvpAdvancedPlugin, + self).create_pool_health_monitor(context, + health_monitor, + pool_id) + return res + #Get the edge_id + edge_id = self._get_edge_id_by_vip_id(context, pool['vip_id']) + res = super(NvpAdvancedPlugin, + self).create_pool_health_monitor(context, + health_monitor, + pool_id) + monitor = self.get_health_monitor(context, monitor_id) + #TODO(linb)Add Exception handle if error + self.vcns_driver.create_health_monitor(context, edge_id, monitor) + #Get updated pool + pool['health_monitors'].append(monitor['id']) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.PENDING_UPDATE) + try: + self._vcns_update_pool(context, pool) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to associate monitor with pool!")) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.ERROR) + super(NvpAdvancedPlugin, self).delete_pool_health_monitor( + context, monitor_id, pool_id) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.ACTIVE) + self._resource_set_status( + context, loadbalancer_db.PoolMonitorAssociation, + monitor_id, service_constants.ACTIVE, res, + pool_id=pool_id) + return res + + def delete_pool_health_monitor(self, context, id, pool_id): + super(NvpAdvancedPlugin, self).delete_pool_health_monitor( + context, id, pool_id) + pool = self.get_pool(context, pool_id) + #Check whether the pool is already associated with the vip + if pool.get('vip_id'): + #Delete the monitor on vshield edge + edge_id = self._get_edge_id_by_vip_id(context, pool['vip_id']) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.PENDING_UPDATE) + try: + self._vcns_update_pool(context, pool) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception( + _("Failed to update pool with pool_monitor!")) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.ERROR) + #TODO(linb): Add exception handle if error + self.vcns_driver.delete_health_monitor(context, id, edge_id) + self._resource_set_status( + context, loadbalancer_db.Pool, + pool_id, service_constants.ACTIVE) + class VcnsCallbacks(object): """Edge callback implementation Callback functions for diff --git a/neutron/plugins/nicira/common/exceptions.py b/neutron/plugins/nicira/common/exceptions.py index 7da4b3323..e26a49aca 100644 --- a/neutron/plugins/nicira/common/exceptions.py +++ b/neutron/plugins/nicira/common/exceptions.py @@ -67,7 +67,7 @@ class MaintenanceInProgress(NvpPluginException): class NvpServicePluginException(q_exc.NeutronException): """NVP Service Plugin exceptions.""" message = _("An unexpected error happened " - "in the NVP Service Plugin:%(err_msg)s") + "in the NVP Service Plugin: %(err_msg)s") class NvpServiceOverQuota(q_exc.Conflict): diff --git a/neutron/plugins/nicira/dbexts/vcns_db.py b/neutron/plugins/nicira/dbexts/vcns_db.py index f223eca4c..17904eeb8 100644 --- a/neutron/plugins/nicira/dbexts/vcns_db.py +++ b/neutron/plugins/nicira/dbexts/vcns_db.py @@ -17,8 +17,13 @@ from sqlalchemy.orm import exc +from neutron.openstack.common import log as logging from neutron.plugins.nicira.common import exceptions as nvp_exc from neutron.plugins.nicira.dbexts import vcns_models +from neutron.plugins.nicira.vshield.common import ( + exceptions as vcns_exc) + +LOG = logging.getLogger(__name__) def add_vcns_router_binding(session, router_id, vse_id, lswitch_id, status): @@ -55,6 +60,7 @@ def delete_vcns_router_binding(session, router_id): # # Edge Firewall binding methods +# def add_vcns_edge_firewallrule_binding(session, map_info): with session.begin(subtransactions=True): binding = vcns_models.VcnsEdgeFirewallRuleBinding( @@ -95,3 +101,103 @@ def cleanup_vcns_edge_firewallrule_binding(session, edge_id): session.query( vcns_models.VcnsEdgeFirewallRuleBinding).filter_by( edge_id=edge_id).delete() + + +def add_vcns_edge_vip_binding(session, map_info): + with session.begin(subtransactions=True): + binding = vcns_models.VcnsEdgeVipBinding( + vip_id=map_info['vip_id'], + edge_id=map_info['edge_id'], + vip_vseid=map_info['vip_vseid'], + app_profileid=map_info['app_profileid']) + session.add(binding) + + return binding + + +def get_vcns_edge_vip_binding(session, id): + with session.begin(subtransactions=True): + try: + qry = session.query(vcns_models.VcnsEdgeVipBinding) + return qry.filter_by(vip_id=id).one() + except exc.NoResultFound: + msg = _("VIP Resource binding with id:%s not found!") % id + LOG.exception(msg) + raise vcns_exc.VcnsNotFound( + resource='router_service_binding', msg=msg) + + +def delete_vcns_edge_vip_binding(session, id): + with session.begin(subtransactions=True): + qry = session.query(vcns_models.VcnsEdgeVipBinding) + if not qry.filter_by(vip_id=id).delete(): + msg = _("VIP Resource binding with id:%s not found!") % id + LOG.exception(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + + +def add_vcns_edge_pool_binding(session, map_info): + with session.begin(subtransactions=True): + binding = vcns_models.VcnsEdgePoolBinding( + pool_id=map_info['pool_id'], + edge_id=map_info['edge_id'], + pool_vseid=map_info['pool_vseid']) + session.add(binding) + + return binding + + +def get_vcns_edge_pool_binding(session, id, edge_id): + with session.begin(subtransactions=True): + return (session.query(vcns_models.VcnsEdgePoolBinding). + filter_by(pool_id=id, edge_id=edge_id).first()) + + +def get_vcns_edge_pool_binding_by_vseid(session, edge_id, pool_vseid): + with session.begin(subtransactions=True): + try: + qry = session.query(vcns_models.VcnsEdgePoolBinding) + binding = qry.filter_by(edge_id=edge_id, + pool_vseid=pool_vseid).one() + except exc.NoResultFound: + msg = (_("Pool Resource binding with edge_id:%(edge_id)s " + "pool_vseid:%(pool_vseid)s not found!") % + {'edge_id': edge_id, 'pool_vseid': pool_vseid}) + LOG.exception(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + return binding + + +def delete_vcns_edge_pool_binding(session, id, edge_id): + with session.begin(subtransactions=True): + qry = session.query(vcns_models.VcnsEdgePoolBinding) + if not qry.filter_by(pool_id=id, edge_id=edge_id).delete(): + msg = _("Pool Resource binding with id:%s not found!") % id + LOG.exception(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) + + +def add_vcns_edge_monitor_binding(session, map_info): + with session.begin(subtransactions=True): + binding = vcns_models.VcnsEdgeMonitorBinding( + monitor_id=map_info['monitor_id'], + edge_id=map_info['edge_id'], + monitor_vseid=map_info['monitor_vseid']) + session.add(binding) + + return binding + + +def get_vcns_edge_monitor_binding(session, id, edge_id): + with session.begin(subtransactions=True): + return (session.query(vcns_models.VcnsEdgeMonitorBinding). + filter_by(monitor_id=id, edge_id=edge_id).first()) + + +def delete_vcns_edge_monitor_binding(session, id, edge_id): + with session.begin(subtransactions=True): + qry = session.query(vcns_models.VcnsEdgeMonitorBinding) + if not qry.filter_by(monitor_id=id, edge_id=edge_id).delete(): + msg = _("Monitor Resource binding with id:%s not found!") % id + LOG.exception(msg) + raise nvp_exc.NvpServicePluginException(err_msg=msg) diff --git a/neutron/plugins/nicira/dbexts/vcns_models.py b/neutron/plugins/nicira/dbexts/vcns_models.py index e2be5f953..c63ff221a 100644 --- a/neutron/plugins/nicira/dbexts/vcns_models.py +++ b/neutron/plugins/nicira/dbexts/vcns_models.py @@ -51,3 +51,41 @@ class VcnsEdgeFirewallRuleBinding(model_base.BASEV2): primary_key=True) edge_id = sa.Column(sa.String(36), primary_key=True) rule_vseid = sa.Column(sa.String(36)) + + +class VcnsEdgePoolBinding(model_base.BASEV2): + """Represents the mapping between neutron pool and Edge pool.""" + + __tablename__ = 'vcns_edge_pool_bindings' + + pool_id = sa.Column(sa.String(36), + sa.ForeignKey("pools.id", ondelete="CASCADE"), + primary_key=True) + edge_id = sa.Column(sa.String(36), primary_key=True) + pool_vseid = sa.Column(sa.String(36)) + + +class VcnsEdgeVipBinding(model_base.BASEV2): + """Represents the mapping between neutron vip and Edge vip.""" + + __tablename__ = 'vcns_edge_vip_bindings' + + vip_id = sa.Column(sa.String(36), + sa.ForeignKey("vips.id", ondelete="CASCADE"), + primary_key=True) + edge_id = sa.Column(sa.String(36)) + vip_vseid = sa.Column(sa.String(36)) + app_profileid = sa.Column(sa.String(36)) + + +class VcnsEdgeMonitorBinding(model_base.BASEV2): + """Represents the mapping between neutron monitor and Edge monitor.""" + + __tablename__ = 'vcns_edge_monitor_bindings' + + monitor_id = sa.Column(sa.String(36), + sa.ForeignKey("healthmonitors.id", + ondelete="CASCADE"), + primary_key=True) + edge_id = sa.Column(sa.String(36), primary_key=True) + monitor_vseid = sa.Column(sa.String(36)) diff --git a/neutron/plugins/nicira/vshield/__init__.py b/neutron/plugins/nicira/vshield/__init__.py index fc51d2002..6818a0c8f 100644 --- a/neutron/plugins/nicira/vshield/__init__.py +++ b/neutron/plugins/nicira/vshield/__init__.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2013 VMware, Inc, +# Copyright 2013 VMware, Inc # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/neutron/plugins/nicira/vshield/common/__init__.py b/neutron/plugins/nicira/vshield/common/__init__.py index 5e8da711f..6818a0c8f 100644 --- a/neutron/plugins/nicira/vshield/common/__init__.py +++ b/neutron/plugins/nicira/vshield/common/__init__.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2013 OpenStack Foundation. +# Copyright 2013 VMware, Inc # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/neutron/plugins/nicira/vshield/edge_appliance_driver.py b/neutron/plugins/nicira/vshield/edge_appliance_driver.py index 246cba587..9d15dc16d 100644 --- a/neutron/plugins/nicira/vshield/edge_appliance_driver.py +++ b/neutron/plugins/nicira/vshield/edge_appliance_driver.py @@ -17,6 +17,7 @@ # @author: Kaiwei Fan, VMware, Inc. # @author: Bo Link, VMware, Inc. +from neutron.openstack.common import excutils from neutron.openstack.common import jsonutils from neutron.openstack.common import log as logging from neutron.plugins.nicira.vshield.common import ( @@ -118,6 +119,14 @@ class EdgeApplianceDriver(object): status_level = RouterStatus.ROUTER_STATUS_ERROR return status_level + def _enable_loadbalancer(self, edge): + if not edge.get('featureConfigs') or ( + not edge['featureConfigs'].get('features')): + edge['featureConfigs'] = {'features': []} + edge['featureConfigs']['features'].append( + {'featureType': 'loadbalancer_4.0', + 'enabled': True}) + def get_edge_status(self, edge_id): try: response = self.vcns.get_edge_status(edge_id)[1] @@ -295,7 +304,7 @@ class EdgeApplianceDriver(object): raise e def deploy_edge(self, router_id, name, internal_network, jobdata=None, - wait_for_exec=False): + wait_for_exec=False, loadbalancer_enable=True): task_name = 'deploying-%s' % name edge_name = name edge = self._assemble_edge( @@ -318,6 +327,8 @@ class EdgeApplianceDriver(object): vcns_const.INTEGRATION_SUBNET_NETMASK, type="internal") edge['vnics']['vnics'].append(vnic_inside) + if loadbalancer_enable: + self._enable_loadbalancer(edge) userdata = { 'request': edge, 'router_name': name, @@ -628,3 +639,24 @@ class EdgeApplianceDriver(object): def delete_lswitch(self, lswitch_id): self.vcns.delete_lswitch(lswitch_id) + + def get_loadbalancer_config(self, edge_id): + try: + header, response = self.vcns.get_loadbalancer_config( + edge_id) + except exceptions.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to get service config")) + return response + + def enable_service_loadbalancer(self, edge_id): + config = self.get_loadbalancer_config( + edge_id) + if not config['enabled']: + config['enabled'] = True + try: + self.vcns.enable_service_loadbalancer(edge_id, config) + except exceptions.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to enable loadbalancer " + "service config")) diff --git a/neutron/plugins/nicira/vshield/edge_loadbalancer_driver.py b/neutron/plugins/nicira/vshield/edge_loadbalancer_driver.py new file mode 100644 index 000000000..7e3ced5c8 --- /dev/null +++ b/neutron/plugins/nicira/vshield/edge_loadbalancer_driver.py @@ -0,0 +1,338 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 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. +# +# @author: Leon Cui, VMware + +from neutron.openstack.common import excutils +from neutron.openstack.common import log as logging +from neutron.plugins.nicira.dbexts import vcns_db +from neutron.plugins.nicira.vshield.common import ( + constants as vcns_const) +from neutron.plugins.nicira.vshield.common import ( + exceptions as vcns_exc) +from neutron.services.loadbalancer import constants as lb_constants + +LOG = logging.getLogger(__name__) + +BALANCE_MAP = { + lb_constants.LB_METHOD_ROUND_ROBIN: 'round-robin', + lb_constants.LB_METHOD_LEAST_CONNECTIONS: 'leastconn', + lb_constants.LB_METHOD_SOURCE_IP: 'source' +} +PROTOCOL_MAP = { + lb_constants.PROTOCOL_TCP: 'tcp', + lb_constants.PROTOCOL_HTTP: 'http', + lb_constants.PROTOCOL_HTTPS: 'tcp' +} + + +class EdgeLbDriver(): + """Implementation of driver APIs for + Edge Loadbalancer feature configuration + """ + + def _convert_lb_vip(self, context, edge_id, vip, app_profileid): + pool_id = vip.get('pool_id') + poolid_map = vcns_db.get_vcns_edge_pool_binding( + context.session, pool_id, edge_id) + pool_vseid = poolid_map['pool_vseid'] + return { + 'name': vip.get('name'), + 'ipAddress': vip.get('address'), + 'protocol': vip.get('protocol'), + 'port': vip.get('protocol_port'), + 'defaultPoolId': pool_vseid, + 'applicationProfileId': app_profileid + } + + def _restore_lb_vip(self, context, edge_id, vip_vse): + pool_binding = vcns_db.get_vcns_edge_pool_binding_by_vseid( + context.session, + edge_id, + vip_vse['defaultPoolId']) + + return { + 'name': vip_vse['name'], + 'address': vip_vse['ipAddress'], + 'protocol': vip_vse['protocol'], + 'protocol_port': vip_vse['port'], + 'pool_id': pool_binding['pool_id'] + } + + def _convert_lb_pool(self, context, edge_id, pool, members): + vsepool = { + 'name': pool.get('name'), + 'algorithm': BALANCE_MAP.get( + pool.get('lb_method'), + 'round-robin'), + 'member': [], + 'monitorId': [] + } + for member in members: + vsepool['member'].append({ + 'ipAddress': member['address'], + 'port': member['protocol_port'] + }) + ##TODO(linb) right now, vse only accept at most one monitor per pool + monitors = pool.get('health_monitors') + if not monitors: + return vsepool + monitorid_map = vcns_db.get_vcns_edge_monitor_binding( + context.session, + monitors[0], + edge_id) + vsepool['monitorId'].append(monitorid_map['monitor_vseid']) + return vsepool + + def _restore_lb_pool(self, context, edge_id, pool_vse): + #TODO(linb): Get more usefule info + return { + 'name': pool_vse['name'], + } + + def _convert_lb_monitor(self, context, monitor): + return { + 'type': PROTOCOL_MAP.get( + monitor.get('type'), 'http'), + 'interval': monitor.get('delay'), + 'timeout': monitor.get('timeout'), + 'maxRetries': monitor.get('max_retries'), + 'name': monitor.get('id') + } + + def _restore_lb_monitor(self, context, edge_id, monitor_vse): + return { + 'delay': monitor_vse['interval'], + 'timeout': monitor_vse['timeout'], + 'max_retries': monitor_vse['maxRetries'], + 'id': monitor_vse['name'] + } + + def _convert_app_profile(self, name, app_profile): + #TODO(linb): convert the session_persistence to + #corresponding app_profile + return { + "insertXForwardedFor": False, + "name": name, + "persistence": { + "method": "sourceip" + }, + "serverSslEnabled": False, + "sslPassthrough": False, + "template": "HTTP" + } + + def create_vip(self, context, edge_id, vip): + app_profile = self._convert_app_profile( + vip['name'], vip.get('session_persistence')) + try: + header, response = self.vcns.create_app_profile( + edge_id, app_profile) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to create app profile on edge: %s"), + edge_id) + objuri = header['location'] + app_profileid = objuri[objuri.rfind("/") + 1:] + + vip_new = self._convert_lb_vip(context, edge_id, vip, app_profileid) + try: + header, response = self.vcns.create_vip( + edge_id, vip_new) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to create vip on vshield edge: %s"), + edge_id) + objuri = header['location'] + vip_vseid = objuri[objuri.rfind("/") + 1:] + + # Add the vip mapping + map_info = { + "vip_id": vip['id'], + "vip_vseid": vip_vseid, + "edge_id": edge_id, + "app_profileid": app_profileid + } + vcns_db.add_vcns_edge_vip_binding(context.session, map_info) + + def get_vip(self, context, id): + vip_binding = vcns_db.get_vcns_edge_vip_binding(context.session, id) + edge_id = vip_binding[vcns_const.EDGE_ID] + vip_vseid = vip_binding['vip_vseid'] + try: + response = self.vcns.get_vip(edge_id, vip_vseid)[1] + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to get vip on edge")) + return self._restore_lb_vip(context, edge_id, response) + + def update_vip(self, context, vip): + vip_binding = vcns_db.get_vcns_edge_vip_binding( + context.session, vip['id']) + edge_id = vip_binding[vcns_const.EDGE_ID] + vip_vseid = vip_binding.get('vip_vseid') + app_profileid = vip_binding.get('app_profileid') + + vip_new = self._convert_lb_vip(context, edge_id, vip, app_profileid) + try: + self.vcns.update_vip(edge_id, vip_vseid, vip_new) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update vip on edge: %s"), edge_id) + + def delete_vip(self, context, id): + vip_binding = vcns_db.get_vcns_edge_vip_binding( + context.session, id) + edge_id = vip_binding[vcns_const.EDGE_ID] + vip_vseid = vip_binding['vip_vseid'] + app_profileid = vip_binding['app_profileid'] + + try: + self.vcns.delete_vip(edge_id, vip_vseid) + self.vcns.delete_app_profile(edge_id, app_profileid) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to delete vip on edge: %s"), edge_id) + vcns_db.delete_vcns_edge_vip_binding(context.session, id) + + def create_pool(self, context, edge_id, pool, members): + pool_new = self._convert_lb_pool(context, edge_id, pool, members) + try: + header = self.vcns.create_pool(edge_id, pool_new)[0] + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to create pool")) + + objuri = header['location'] + pool_vseid = objuri[objuri.rfind("/") + 1:] + + # update the pool mapping table + map_info = { + "pool_id": pool['id'], + "pool_vseid": pool_vseid, + "edge_id": edge_id + } + vcns_db.add_vcns_edge_pool_binding(context.session, map_info) + + def get_pool(self, context, id, edge_id): + pool_binding = vcns_db.get_vcns_edge_pool_binding( + context.session, id, edge_id) + if not pool_binding: + msg = (_("pool_binding not found with id: %(id)s " + "edge_id: %(edge_id)s") % { + 'id': id, + 'edge_id': edge_id}) + LOG.error(msg) + raise vcns_exc.VcnsNotFound( + resource='router_service_binding', msg=msg) + pool_vseid = pool_binding['pool_vseid'] + try: + response = self.vcns.get_pool(edge_id, pool_vseid)[1] + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to get pool on edge")) + return self._restore_lb_pool(context, edge_id, response) + + def update_pool(self, context, edge_id, pool, members): + pool_binding = vcns_db.get_vcns_edge_pool_binding( + context.session, pool['id'], edge_id) + pool_vseid = pool_binding['pool_vseid'] + pool_new = self._convert_lb_pool(context, edge_id, pool, members) + try: + self.vcns.update_pool(edge_id, pool_vseid, pool_new) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update pool")) + + def delete_pool(self, context, id, edge_id): + pool_binding = vcns_db.get_vcns_edge_pool_binding( + context.session, id, edge_id) + pool_vseid = pool_binding['pool_vseid'] + try: + self.vcns.delete_pool(edge_id, pool_vseid) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to delete pool")) + vcns_db.delete_vcns_edge_pool_binding( + context.session, id, edge_id) + + def create_health_monitor(self, context, edge_id, health_monitor): + monitor_new = self._convert_lb_monitor(context, health_monitor) + try: + header = self.vcns.create_health_monitor(edge_id, monitor_new)[0] + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to create monitor on edge: %s"), + edge_id) + + objuri = header['location'] + monitor_vseid = objuri[objuri.rfind("/") + 1:] + + # update the health_monitor mapping table + map_info = { + "monitor_id": health_monitor['id'], + "monitor_vseid": monitor_vseid, + "edge_id": edge_id + } + vcns_db.add_vcns_edge_monitor_binding(context.session, map_info) + + def get_health_monitor(self, context, id, edge_id): + monitor_binding = vcns_db.get_vcns_edge_monitor_binding( + context.session, id, edge_id) + if not monitor_binding: + msg = (_("monitor_binding not found with id: %(id)s " + "edge_id: %(edge_id)s") % { + 'id': id, + 'edge_id': edge_id}) + LOG.error(msg) + raise vcns_exc.VcnsNotFound( + resource='router_service_binding', msg=msg) + monitor_vseid = monitor_binding['monitor_vseid'] + try: + response = self.vcns.get_health_monitor(edge_id, monitor_vseid)[1] + except vcns_exc.VcnsApiException as e: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to get monitor on edge: %s"), + e.response) + return self._restore_lb_monitor(context, edge_id, response) + + def update_health_monitor(self, context, edge_id, + old_health_monitor, health_monitor): + monitor_binding = vcns_db.get_vcns_edge_monitor_binding( + context.session, + old_health_monitor['id'], edge_id) + monitor_vseid = monitor_binding['monitor_vseid'] + monitor_new = self._convert_lb_monitor( + context, health_monitor) + try: + self.vcns.update_health_monitor( + edge_id, monitor_vseid, monitor_new) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to update monitor on edge: %s"), + edge_id) + + def delete_health_monitor(self, context, id, edge_id): + monitor_binding = vcns_db.get_vcns_edge_monitor_binding( + context.session, id, edge_id) + monitor_vseid = monitor_binding['monitor_vseid'] + try: + self.vcns.delete_health_monitor(edge_id, monitor_vseid) + except vcns_exc.VcnsApiException: + with excutils.save_and_reraise_exception(): + LOG.exception(_("Failed to delete monitor")) + vcns_db.delete_vcns_edge_monitor_binding( + context.session, id, edge_id) diff --git a/neutron/plugins/nicira/vshield/vcns.py b/neutron/plugins/nicira/vshield/vcns.py index beace318d..803b97988 100644 --- a/neutron/plugins/nicira/vshield/vcns.py +++ b/neutron/plugins/nicira/vshield/vcns.py @@ -32,6 +32,13 @@ URI_PREFIX = "/api/4.0/edges" FIREWALL_SERVICE = "firewall/config" FIREWALL_RULE_RESOURCE = "rules" +#LbaaS Constants +LOADBALANCER_SERVICE = "loadbalancer/config" +VIP_RESOURCE = "virtualservers" +POOL_RESOURCE = "pools" +MONITOR_RESOURCE = "monitors" +APP_PROFILE_RESOURCE = "applicationprofiles" + class Vcns(object): @@ -111,6 +118,14 @@ class Vcns(object): uri = "/api/ws.v1/lswitch/%s" % lswitch_id return self.do_request(HTTP_DELETE, uri) + def get_loadbalancer_config(self, edge_id): + uri = self._build_uri_path(edge_id, LOADBALANCER_SERVICE) + return self.do_request(HTTP_GET, uri, decode=True) + + def enable_service_loadbalancer(self, edge_id, config): + uri = self._build_uri_path(edge_id, LOADBALANCER_SERVICE) + return self.do_request(HTTP_PUT, uri, config) + def update_firewall(self, edge_id, fw_req): uri = self._build_uri_path( edge_id, FIREWALL_SERVICE) @@ -159,6 +174,96 @@ class Vcns(object): vcns_rule_id) return self.do_request(HTTP_GET, uri, decode=True) + # + #Edge LBAAS call helper + # + def create_vip(self, edge_id, vip_new): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + VIP_RESOURCE) + return self.do_request(HTTP_POST, uri, vip_new) + + def get_vip(self, edge_id, vip_vseid): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + VIP_RESOURCE, vip_vseid) + return self.do_request(HTTP_GET, uri, decode=True) + + def update_vip(self, edge_id, vip_vseid, vip_new): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + VIP_RESOURCE, vip_vseid) + return self.do_request(HTTP_PUT, uri, vip_new) + + def delete_vip(self, edge_id, vip_vseid): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + VIP_RESOURCE, vip_vseid) + return self.do_request(HTTP_DELETE, uri) + + def create_pool(self, edge_id, pool_new): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + POOL_RESOURCE) + return self.do_request(HTTP_POST, uri, pool_new) + + def get_pool(self, edge_id, pool_vseid): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + POOL_RESOURCE, pool_vseid) + return self.do_request(HTTP_GET, uri, decode=True) + + def update_pool(self, edge_id, pool_vseid, pool_new): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + POOL_RESOURCE, pool_vseid) + return self.do_request(HTTP_PUT, uri, pool_new) + + def delete_pool(self, edge_id, pool_vseid): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + POOL_RESOURCE, pool_vseid) + return self.do_request(HTTP_DELETE, uri) + + def create_health_monitor(self, edge_id, monitor_new): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + MONITOR_RESOURCE) + return self.do_request(HTTP_POST, uri, monitor_new) + + def get_health_monitor(self, edge_id, monitor_vseid): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + MONITOR_RESOURCE, monitor_vseid) + return self.do_request(HTTP_GET, uri, decode=True) + + def update_health_monitor(self, edge_id, monitor_vseid, monitor_new): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + MONITOR_RESOURCE, + monitor_vseid) + return self.do_request(HTTP_PUT, uri, monitor_new) + + def delete_health_monitor(self, edge_id, monitor_vseid): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + MONITOR_RESOURCE, + monitor_vseid) + return self.do_request(HTTP_DELETE, uri) + + def create_app_profile(self, edge_id, app_profile): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + APP_PROFILE_RESOURCE) + return self.do_request(HTTP_POST, uri, app_profile) + + def delete_app_profile(self, edge_id, app_profileid): + uri = self._build_uri_path( + edge_id, LOADBALANCER_SERVICE, + APP_PROFILE_RESOURCE, + app_profileid) + return self.do_request(HTTP_DELETE, uri) + def _build_uri_path(self, edge_id, service, resource=None, diff --git a/neutron/plugins/nicira/vshield/vcns_driver.py b/neutron/plugins/nicira/vshield/vcns_driver.py index 8178744a9..c2c5c1499 100644 --- a/neutron/plugins/nicira/vshield/vcns_driver.py +++ b/neutron/plugins/nicira/vshield/vcns_driver.py @@ -18,15 +18,21 @@ from oslo.config import cfg -from neutron.plugins.nicira.common import config # noqa +from neutron.openstack.common import log as logging +from neutron.plugins.nicira.common import config as nicira_cfg # noqa from neutron.plugins.nicira.vshield import edge_appliance_driver from neutron.plugins.nicira.vshield import edge_firewall_driver +from neutron.plugins.nicira.vshield import edge_loadbalancer_driver from neutron.plugins.nicira.vshield.tasks import tasks from neutron.plugins.nicira.vshield import vcns +LOG = logging.getLogger(__name__) + class VcnsDriver(edge_appliance_driver.EdgeApplianceDriver, - edge_firewall_driver.EdgeFirewallDriver): + edge_firewall_driver.EdgeFirewallDriver, + edge_loadbalancer_driver.EdgeLbDriver): + def __init__(self, callbacks): super(VcnsDriver, self).__init__() diff --git a/neutron/tests/unit/db/loadbalancer/test_db_loadbalancer.py b/neutron/tests/unit/db/loadbalancer/test_db_loadbalancer.py index d646afd0f..c54c72939 100644 --- a/neutron/tests/unit/db/loadbalancer/test_db_loadbalancer.py +++ b/neutron/tests/unit/db/loadbalancer/test_db_loadbalancer.py @@ -64,6 +64,10 @@ class LoadBalancerTestMixin(object): for k in loadbalancer.RESOURCE_ATTRIBUTE_MAP.keys() ) + def _get_vip_optional_args(self): + return ('description', 'subnet_id', 'address', + 'session_persistence', 'connection_limit') + def _create_vip(self, fmt, name, pool_id, protocol, protocol_port, admin_state_up, expected_res_status=None, **kwargs): data = {'vip': {'name': name, @@ -72,8 +76,8 @@ class LoadBalancerTestMixin(object): 'protocol_port': protocol_port, 'admin_state_up': admin_state_up, 'tenant_id': self._tenant_id}} - for arg in ('description', 'subnet_id', 'address', - 'session_persistence', 'connection_limit'): + args = self._get_vip_optional_args() + for arg in args: if arg in kwargs and kwargs[arg] is not None: data['vip'][arg] = kwargs[arg] @@ -255,7 +259,8 @@ class LoadBalancerTestMixin(object): class LoadBalancerPluginDbTestCase(LoadBalancerTestMixin, test_db_plugin.NeutronDbPluginV2TestCase): - def setUp(self, core_plugin=None, lb_plugin=None, lbaas_provider=None): + def setUp(self, core_plugin=None, lb_plugin=None, lbaas_provider=None, + ext_mgr=None): service_plugins = {'lb_plugin_name': DB_LB_PLUGIN_KLASS} if not lbaas_provider: lbaas_provider = ( @@ -269,12 +274,18 @@ class LoadBalancerPluginDbTestCase(LoadBalancerTestMixin, sdb.ServiceTypeManager._instance = None super(LoadBalancerPluginDbTestCase, self).setUp( + ext_mgr=ext_mgr, service_plugins=service_plugins ) - self._subnet_id = _subnet_id - - self.plugin = loadbalancer_plugin.LoadBalancerPlugin() + if not ext_mgr: + self.plugin = loadbalancer_plugin.LoadBalancerPlugin() + ext_mgr = PluginAwareExtensionManager( + extensions_path, + {constants.LOADBALANCER: self.plugin} + ) + app = config.load_paste_app('extensions_test_app') + self.ext_api = ExtensionMiddleware(app, ext_mgr=ext_mgr) get_lbaas_agent_patcher = mock.patch( 'neutron.services.loadbalancer.agent_scheduler' @@ -285,12 +296,7 @@ class LoadBalancerPluginDbTestCase(LoadBalancerTestMixin, self.addCleanup(mock.patch.stopall) self.addCleanup(cfg.CONF.reset) - ext_mgr = PluginAwareExtensionManager( - extensions_path, - {constants.LOADBALANCER: self.plugin} - ) - app = config.load_paste_app('extensions_test_app') - self.ext_api = ExtensionMiddleware(app, ext_mgr=ext_mgr) + self._subnet_id = _subnet_id class TestLoadBalancer(LoadBalancerPluginDbTestCase): diff --git a/neutron/tests/unit/nicira/test_edge_router.py b/neutron/tests/unit/nicira/test_edge_router.py index f2deff614..ad772d77a 100644 --- a/neutron/tests/unit/nicira/test_edge_router.py +++ b/neutron/tests/unit/nicira/test_edge_router.py @@ -31,6 +31,7 @@ from neutron.tests.unit.nicira import SERVICE_PLUGIN_NAME from neutron.tests.unit.nicira import test_nicira_plugin from neutron.tests.unit.nicira import VCNS_NAME from neutron.tests.unit.nicira.vshield import fake_vcns +from neutron.tests.unit import test_l3_plugin _uuid = uuidutils.generate_uuid @@ -67,7 +68,8 @@ class NvpRouterTestCase(test_nicira_plugin.TestNiciraL3NatTestCase): service_plugins=service_plugins) -class ServiceRouterTest(test_nicira_plugin.NiciraL3NatTest): +class ServiceRouterTest(test_nicira_plugin.NiciraL3NatTest, + test_l3_plugin.L3NatTestCaseMixin): def vcns_patch(self): instance = self.mock_vcns.start() @@ -94,6 +96,10 @@ class ServiceRouterTest(test_nicira_plugin.NiciraL3NatTest): self.fc2.create_lswitch) instance.return_value.delete_lswitch.side_effect = ( self.fc2.delete_lswitch) + instance.return_value.get_loadbalancer_config.side_effect = ( + self.fc2.get_loadbalancer_config) + instance.return_value.enable_service_loadbalancer.side_effect = ( + self.fc2.enable_service_loadbalancer) def setUp(self, ext_mgr=None, service_plugins=None): cfg.CONF.set_override('api_extensions_path', NVPEXT_PATH) @@ -113,7 +119,7 @@ class ServiceRouterTest(test_nicira_plugin.NiciraL3NatTest): self.fc2.set_fake_nvpapi(self.fc) self.addCleanup(self.fc2.reset_all) - self.addCleanup(self.mock_vcns.stop) + self.addCleanup(mock.patch.stopall) def tearDown(self): plugin = NeutronManager.get_plugin() diff --git a/neutron/tests/unit/nicira/test_lbaas_plugin.py b/neutron/tests/unit/nicira/test_lbaas_plugin.py new file mode 100644 index 000000000..94ed0bb0e --- /dev/null +++ b/neutron/tests/unit/nicira/test_lbaas_plugin.py @@ -0,0 +1,433 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 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. +# +# @author: linb, VMware + +import contextlib + +from webob import exc as web_exc + +from neutron.api.v2 import attributes +from neutron import context +from neutron.db.loadbalancer import loadbalancer_db as ldb +from neutron.extensions import loadbalancer as lb +from neutron import manager +from neutron.openstack.common import uuidutils +from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer +from neutron.tests.unit.nicira import test_edge_router + +_uuid = uuidutils.generate_uuid + +LBAAS_PLUGIN_CLASS = ( + "neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin" +) + + +class LoadBalancerTestExtensionManager( + test_edge_router.ServiceRouterTestExtensionManager): + + def get_resources(self): + # If l3 resources have been loaded and updated by main API + # router, update the map in the l3 extension so it will load + # the same attributes as the API router + resources = super(LoadBalancerTestExtensionManager, + self).get_resources() + lb_attr_map = lb.RESOURCE_ATTRIBUTE_MAP.copy() + for res in lb.RESOURCE_ATTRIBUTE_MAP.keys(): + attr_info = attributes.RESOURCE_ATTRIBUTE_MAP.get(res) + if attr_info: + lb.RESOURCE_ATTRIBUTE_MAP[res] = attr_info + lb_resources = lb.Loadbalancer.get_resources() + # restore the original resources once the controllers are created + lb.RESOURCE_ATTRIBUTE_MAP = lb_attr_map + resources.extend(lb_resources) + return resources + + +class TestLoadbalancerPlugin( + test_db_loadbalancer.LoadBalancerPluginDbTestCase, + test_edge_router.ServiceRouterTest): + + def vcns_loadbalancer_patch(self): + instance = self.vcns_instance + instance.return_value.create_vip.side_effect = ( + self.fc2.create_vip) + instance.return_value.get_vip.side_effect = ( + self.fc2.get_vip) + instance.return_value.update_vip.side_effect = ( + self.fc2.update_vip) + instance.return_value.delete_vip.side_effect = ( + self.fc2.delete_vip) + instance.return_value.create_pool.side_effect = ( + self.fc2.create_pool) + instance.return_value.get_pool.side_effect = ( + self.fc2.get_pool) + instance.return_value.update_pool.side_effect = ( + self.fc2.update_pool) + instance.return_value.delete_pool.side_effect = ( + self.fc2.delete_pool) + instance.return_value.create_health_monitor.side_effect = ( + self.fc2.create_health_monitor) + instance.return_value.get_health_monitor.side_effect = ( + self.fc2.get_health_monitor) + instance.return_value.update_health_monitor.side_effect = ( + self.fc2.update_health_monitor) + instance.return_value.delete_health_monitor.side_effect = ( + self.fc2.delete_health_monitor) + instance.return_value.create_app_profile.side_effect = ( + self.fc2.create_app_profile) + instance.return_value.delete_app_profile.side_effect = ( + self.fc2.delete_app_profile) + + def setUp(self): + # Save the global RESOURCE_ATTRIBUTE_MAP + self.saved_attr_map = {} + for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems(): + self.saved_attr_map[resource] = attrs.copy() + + super(TestLoadbalancerPlugin, self).setUp( + ext_mgr=LoadBalancerTestExtensionManager(), + lb_plugin=LBAAS_PLUGIN_CLASS) + self.vcns_loadbalancer_patch() + self.plugin = manager.NeutronManager.get_plugin() + self.router_id = None + + def tearDown(self): + super(TestLoadbalancerPlugin, self).tearDown() + # Restore the global RESOURCE_ATTRIBUTE_MAP + attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map + self.ext_api = None + self.plugin = None + + def _fake_router_edge_mapping(self): + req = self._create_router(self.fmt, self._tenant_id) + res = self.deserialize(self.fmt, req) + self.router_id = res['router']['id'] + + def _get_vip_optional_args(self): + args = super(TestLoadbalancerPlugin, self)._get_vip_optional_args() + return args + ('router_id',) + + def test_update_healthmonitor(self): + self._fake_router_edge_mapping() + + keys = [('type', "TCP"), + ('tenant_id', self._tenant_id), + ('delay', 20), + ('timeout', 20), + ('max_retries', 2), + ('admin_state_up', False)] + + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor() + ) as (subnet, pool, health_mon): + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + with self.vip( + router_id=self.router_id, pool=pool, + subnet=subnet): + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + health_mon, pool['pool']['id'] + ) + data = {'health_monitor': {'delay': 20, + 'timeout': 20, + 'max_retries': 2, + 'admin_state_up': False}} + req = self.new_update_request( + "health_monitors", + data, + health_mon['health_monitor']['id']) + res = self.deserialize( + self.fmt, req.get_response(self.ext_api)) + for k, v in keys: + self.assertEqual(res['health_monitor'][k], v) + + def test_delete_healthmonitor(self): + ctx = context.get_admin_context() + self._fake_router_edge_mapping() + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor(no_delete=True) + ) as (subnet, pool, health_mon): + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + with self.vip( + router_id=self.router_id, pool=pool, + subnet=subnet): + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + health_mon, pool['pool']['id'] + ) + + req = self.new_delete_request('health_monitors', + health_mon['health_monitor']['id']) + res = req.get_response(self.ext_api) + self.assertEqual(res.status_int, 204) + qry = ctx.session.query(ldb.HealthMonitor) + qry = qry.filter_by(id=health_mon['health_monitor']['id']) + self.assertIsNone(qry.first()) + + def test_create_vip(self, **extras): + self._fake_router_edge_mapping() + expected = { + 'name': 'vip1', + 'description': '', + 'protocol_port': 80, + 'protocol': 'HTTP', + 'connection_limit': -1, + 'admin_state_up': True, + 'status': 'PENDING_CREATE', + 'tenant_id': self._tenant_id, + } + + expected.update(extras) + + name = expected['name'] + + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor() + ) as (subnet, pool, monitor): + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + expected['pool_id'] = pool['pool']['id'] + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool['pool']['id'] + ) + with self.vip( + router_id=self.router_id, name=name, + pool=pool, subnet=subnet, **extras) as vip: + for k in ('id', 'address', 'port_id', 'pool_id'): + self.assertTrue(vip['vip'].get(k, None)) + expected['status'] = 'ACTIVE' + self.assertEqual( + dict((k, v) + for k, v in vip['vip'].items() if k in expected), + expected + ) + + def test_update_vip(self): + self._fake_router_edge_mapping() + name = 'new_vip' + keys = [('name', name), + ('address', "10.0.0.2"), + ('protocol_port', 80), + ('connection_limit', 100), + ('admin_state_up', False), + ('status', 'ACTIVE')] + + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor() + ) as (subnet, pool, monitor): + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool['pool']['id'] + ) + with self.vip( + router_id=self.router_id, name=name, + pool=pool, subnet=subnet) as vip: + keys.append(('subnet_id', vip['vip']['subnet_id'])) + data = {'vip': {'name': name, + 'connection_limit': 100, + 'session_persistence': + {'type': "APP_COOKIE", + 'cookie_name': "jesssionId"}, + 'admin_state_up': False}} + req = self.new_update_request( + 'vips', data, vip['vip']['id']) + res = self.deserialize(self.fmt, + req.get_response(self.ext_api)) + for k, v in keys: + self.assertEqual(res['vip'][k], v) + + def test_delete_vip(self): + self._fake_router_edge_mapping() + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor() + ) as (subnet, pool, monitor): + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool['pool']['id'] + ) + with self.vip( + router_id=self.router_id, + pool=pool, subnet=subnet, no_delete=True) as vip: + req = self.new_delete_request('vips', + vip['vip']['id']) + res = req.get_response(self.ext_api) + self.assertEqual(res.status_int, 204) + + def test_update_pool(self): + self._fake_router_edge_mapping() + data = {'pool': {'name': "new_pool", + 'admin_state_up': False}} + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor() + ) as (subnet, pool, monitor): + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool['pool']['id'] + ) + with self.vip( + router_id=self.router_id, + pool=pool, subnet=subnet): + req = self.new_update_request( + 'pools', data, pool['pool']['id']) + res = self.deserialize(self.fmt, + req.get_response(self.ext_api)) + for k, v in data['pool'].items(): + self.assertEqual(res['pool'][k], v) + + def test_create_member(self): + self._fake_router_edge_mapping() + + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor() + ) as (subnet, pool, monitor): + pool_id = pool['pool']['id'] + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool['pool']['id'] + ) + with self.vip( + router_id=self.router_id, + pool=pool, subnet=subnet): + with contextlib.nested( + self.member(address='192.168.1.100', + protocol_port=80, + pool_id=pool_id), + self.member(router_id=self.router_id, + address='192.168.1.101', + protocol_port=80, + pool_id=pool_id)) as (member1, member2): + req = self.new_show_request('pools', + pool_id, + fmt=self.fmt) + pool_update = self.deserialize( + self.fmt, + req.get_response(self.ext_api) + ) + self.assertIn(member1['member']['id'], + pool_update['pool']['members']) + self.assertIn(member2['member']['id'], + pool_update['pool']['members']) + + def _show_pool(self, pool_id): + req = self.new_show_request('pools', pool_id, fmt=self.fmt) + res = req.get_response(self.ext_api) + self.assertEqual(web_exc.HTTPOk.code, res.status_int) + return self.deserialize(self.fmt, res) + + def test_update_member(self): + self._fake_router_edge_mapping() + with contextlib.nested( + self.subnet(), + self.pool(name="pool1"), + self.pool(name="pool2"), + self.health_monitor() + ) as (subnet, pool1, pool2, monitor): + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool1['pool']['id'] + ) + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool2['pool']['id'] + ) + with self.vip( + router_id=self.router_id, + pool=pool1, subnet=subnet): + keys = [('address', "192.168.1.100"), + ('tenant_id', self._tenant_id), + ('protocol_port', 80), + ('weight', 10), + ('pool_id', pool2['pool']['id']), + ('admin_state_up', False), + ('status', 'ACTIVE')] + with self.member( + pool_id=pool1['pool']['id']) as member: + + pool1_update = self._show_pool(pool1['pool']['id']) + self.assertEqual(len(pool1_update['pool']['members']), 1) + pool2_update = self._show_pool(pool2['pool']['id']) + self.assertEqual(len(pool1_update['pool']['members']), 1) + self.assertFalse(pool2_update['pool']['members']) + + data = {'member': {'pool_id': pool2['pool']['id'], + 'weight': 10, + 'admin_state_up': False}} + req = self.new_update_request('members', + data, + member['member']['id']) + raw_res = req.get_response(self.ext_api) + self.assertEquals(web_exc.HTTPOk.code, raw_res.status_int) + res = self.deserialize(self.fmt, raw_res) + for k, v in keys: + self.assertEqual(res['member'][k], v) + pool1_update = self._show_pool(pool1['pool']['id']) + pool2_update = self._show_pool(pool2['pool']['id']) + self.assertEqual(len(pool2_update['pool']['members']), 1) + self.assertFalse(pool1_update['pool']['members']) + + def test_delete_member(self): + self._fake_router_edge_mapping() + with contextlib.nested( + self.subnet(), + self.pool(), + self.health_monitor() + ) as (subnet, pool, monitor): + pool_id = pool['pool']['id'] + net_id = subnet['subnet']['network_id'] + self._set_net_external(net_id) + self.plugin.create_pool_health_monitor( + context.get_admin_context(), + monitor, pool['pool']['id'] + ) + with self.vip( + router_id=self.router_id, + pool=pool, subnet=subnet): + with self.member(pool_id=pool_id, + no_delete=True) as member: + req = self.new_delete_request('members', + member['member']['id']) + res = req.get_response(self.ext_api) + self.assertEqual(res.status_int, 204) + pool_update = self._show_pool(pool['pool']['id']) + self.assertFalse(pool_update['pool']['members']) diff --git a/neutron/tests/unit/nicira/test_nicira_plugin.py b/neutron/tests/unit/nicira/test_nicira_plugin.py index adc6bcee8..be6c11fc1 100644 --- a/neutron/tests/unit/nicira/test_nicira_plugin.py +++ b/neutron/tests/unit/nicira/test_nicira_plugin.py @@ -124,8 +124,7 @@ class NiciraPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): ext_mgr=ext_mgr) cfg.CONF.set_override('metadata_mode', None, 'NVP') self.addCleanup(self.fc.reset_all) - self.addCleanup(self.mock_nvpapi.stop) - self.addCleanup(patch_sync.stop) + self.addCleanup(mock.patch.stopall) class TestNiciraBasicGet(test_plugin.TestBasicGet, NiciraPluginV2TestCase): diff --git a/neutron/tests/unit/nicira/vshield/fake_vcns.py b/neutron/tests/unit/nicira/vshield/fake_vcns.py index 3435c3b46..e6999dd69 100644 --- a/neutron/tests/unit/nicira/vshield/fake_vcns.py +++ b/neutron/tests/unit/nicira/vshield/fake_vcns.py @@ -48,6 +48,11 @@ class FakeVcns(object): "firewallRules": [] } } + self._fake_virtualservers_dict = {} + self._fake_pools_dict = {} + self._fake_monitors_dict = {} + self._fake_app_profiles_dict = {} + self._fake_loadbalancer_config = {} def set_fake_nvpapi(self, fake_nvpapi): self._fake_nvpapi = fake_nvpapi @@ -363,6 +368,173 @@ class FakeVcns(object): break return self.return_helper(header, response) + # + #Fake Edge LBAAS call + # + def create_vip(self, edge_id, vip_new): + if not self._fake_virtualservers_dict.get(edge_id): + self._fake_virtualservers_dict[edge_id] = {} + vip_vseid = uuidutils.generate_uuid() + self._fake_virtualservers_dict[edge_id][vip_vseid] = vip_new + header = { + 'status': 204, + 'location': "https://host/api/4.0/edges/edge_id" + "/loadbalancer/config/%s" % vip_vseid} + response = "" + return self.return_helper(header, response) + + def get_vip(self, edge_id, vip_vseid): + header = {'status': 404} + response = "" + if not self._fake_virtualservers_dict.get(edge_id) or ( + not self._fake_virtualservers_dict[edge_id].get(vip_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + response = self._fake_virtualservers_dict[edge_id][vip_vseid] + return self.return_helper(header, response) + + def update_vip(self, edge_id, vip_vseid, vip_new): + header = {'status': 404} + response = "" + if not self._fake_virtualservers_dict.get(edge_id) or ( + not self._fake_virtualservers_dict[edge_id].get(vip_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + self._fake_virtualservers_dict[edge_id][vip_vseid].update( + vip_new) + return self.return_helper(header, response) + + def delete_vip(self, edge_id, vip_vseid): + header = {'status': 404} + response = "" + if not self._fake_virtualservers_dict.get(edge_id) or ( + not self._fake_virtualservers_dict[edge_id].get(vip_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + del self._fake_virtualservers_dict[edge_id][vip_vseid] + return self.return_helper(header, response) + + def create_pool(self, edge_id, pool_new): + if not self._fake_pools_dict.get(edge_id): + self._fake_pools_dict[edge_id] = {} + pool_vseid = uuidutils.generate_uuid() + self._fake_pools_dict[edge_id][pool_vseid] = pool_new + header = { + 'status': 204, + 'location': "https://host/api/4.0/edges/edge_id" + "/loadbalancer/config/%s" % pool_vseid} + response = "" + return self.return_helper(header, response) + + def get_pool(self, edge_id, pool_vseid): + header = {'status': 404} + response = "" + if not self._fake_pools_dict.get(edge_id) or ( + not self._fake_pools_dict[edge_id].get(pool_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + response = self._fake_pools_dict[edge_id][pool_vseid] + return self.return_helper(header, response) + + def update_pool(self, edge_id, pool_vseid, pool_new): + header = {'status': 404} + response = "" + if not self._fake_pools_dict.get(edge_id) or ( + not self._fake_pools_dict[edge_id].get(pool_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + self._fake_pools_dict[edge_id][pool_vseid].update( + pool_new) + return self.return_helper(header, response) + + def delete_pool(self, edge_id, pool_vseid): + header = {'status': 404} + response = "" + if not self._fake_pools_dict.get(edge_id) or ( + not self._fake_pools_dict[edge_id].get(pool_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + del self._fake_pools_dict[edge_id][pool_vseid] + return self.return_helper(header, response) + + def create_health_monitor(self, edge_id, monitor_new): + if not self._fake_monitors_dict.get(edge_id): + self._fake_monitors_dict[edge_id] = {} + monitor_vseid = uuidutils.generate_uuid() + self._fake_monitors_dict[edge_id][monitor_vseid] = monitor_new + header = { + 'status': 204, + 'location': "https://host/api/4.0/edges/edge_id" + "/loadbalancer/config/%s" % monitor_vseid} + response = "" + return self.return_helper(header, response) + + def get_health_monitor(self, edge_id, monitor_vseid): + header = {'status': 404} + response = "" + if not self._fake_monitors_dict.get(edge_id) or ( + not self._fake_monitors_dict[edge_id].get(monitor_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + response = self._fake_monitors_dict[edge_id][monitor_vseid] + return self.return_helper(header, response) + + def update_health_monitor(self, edge_id, monitor_vseid, monitor_new): + header = {'status': 404} + response = "" + if not self._fake_monitors_dict.get(edge_id) or ( + not self._fake_monitors_dict[edge_id].get(monitor_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + self._fake_monitors_dict[edge_id][monitor_vseid].update( + monitor_new) + return self.return_helper(header, response) + + def delete_health_monitor(self, edge_id, monitor_vseid): + header = {'status': 404} + response = "" + if not self._fake_monitors_dict.get(edge_id) or ( + not self._fake_monitors_dict[edge_id].get(monitor_vseid)): + return self.return_helper(header, response) + header = {'status': 204} + del self._fake_monitors_dict[edge_id][monitor_vseid] + return self.return_helper(header, response) + + def create_app_profile(self, edge_id, app_profile): + if not self._fake_app_profiles_dict.get(edge_id): + self._fake_app_profiles_dict[edge_id] = {} + app_profileid = uuidutils.generate_uuid() + self._fake_app_profiles_dict[edge_id][app_profileid] = app_profile + header = { + 'status': 204, + 'location': "https://host/api/4.0/edges/edge_id" + "/loadbalancer/config/%s" % app_profileid} + response = "" + return self.return_helper(header, response) + + def delete_app_profile(self, edge_id, app_profileid): + header = {'status': 404} + response = "" + if not self._fake_app_profiles_dict.get(edge_id) or ( + not self._fake_app_profiles_dict[edge_id].get(app_profileid)): + return self.return_helper(header, response) + header = {'status': 204} + del self._fake_app_profiles_dict[edge_id][app_profileid] + return self.return_helper(header, response) + + def get_loadbalancer_config(self, edge_id): + header = {'status': 204} + response = {'config': False} + if self._fake_loadbalancer_config[edge_id]: + response['config'] = self._fake_loadbalancer_config[edge_id] + return self.return_helper(header, response) + + def enable_service_loadbalancer(self, edge_id, config): + header = {'status': 204} + response = "" + self._fake_loadbalancer_config[edge_id] = True + return self.return_helper(header, response) + def return_helper(self, header, response): status = int(header['status']) if 200 <= status <= 300: @@ -379,3 +551,8 @@ class FakeVcns(object): self._edges.clear() self._lswitches.clear() self.fake_firewall_dict = {} + self._fake_virtualservers_dict = {} + self._fake_pools_dict = {} + self._fake_monitors_dict = {} + self._fake_app_profiles_dict = {} + self._fake_loadbalancer_config = {} diff --git a/neutron/tests/unit/nicira/vshield/test_loadbalancer_driver.py b/neutron/tests/unit/nicira/vshield/test_loadbalancer_driver.py new file mode 100644 index 000000000..40a3d5cca --- /dev/null +++ b/neutron/tests/unit/nicira/vshield/test_loadbalancer_driver.py @@ -0,0 +1,233 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 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. +# +# @author: linb, VMware + +import mock + +from neutron.common import config as n_config +from neutron import context +from neutron.openstack.common import uuidutils +from neutron.plugins.nicira.dbexts import vcns_db +from neutron.plugins.nicira.vshield import ( + vcns_driver) +from neutron.plugins.nicira.vshield.common import ( + exceptions as vcns_exc) +from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer +from neutron.tests.unit.nicira import get_fake_conf +from neutron.tests.unit.nicira import VCNS_NAME +from neutron.tests.unit.nicira.vshield import fake_vcns + +_uuid = uuidutils.generate_uuid + +VSE_ID = 'edge-1' +POOL_MAP_INFO = { + 'pool_id': None, + 'edge_id': VSE_ID, + 'pool_vseid': 'pool-1'} + +VCNS_CONFIG_FILE = get_fake_conf("vcns.ini.test") + + +class VcnsDriverTestCase(test_db_loadbalancer.LoadBalancerPluginDbTestCase): + + def vcns_loadbalancer_patch(self): + instance = self.mock_vcns.start() + instance.return_value.create_vip.side_effect = ( + self.fc2.create_vip) + instance.return_value.get_vip.side_effect = ( + self.fc2.get_vip) + instance.return_value.update_vip.side_effect = ( + self.fc2.update_vip) + instance.return_value.delete_vip.side_effect = ( + self.fc2.delete_vip) + instance.return_value.create_pool.side_effect = ( + self.fc2.create_pool) + instance.return_value.get_pool.side_effect = ( + self.fc2.get_pool) + instance.return_value.update_pool.side_effect = ( + self.fc2.update_pool) + instance.return_value.delete_pool.side_effect = ( + self.fc2.delete_pool) + instance.return_value.create_health_monitor.side_effect = ( + self.fc2.create_health_monitor) + instance.return_value.get_health_monitor.side_effect = ( + self.fc2.get_health_monitor) + instance.return_value.update_health_monitor.side_effect = ( + self.fc2.update_health_monitor) + instance.return_value.delete_health_monitor.side_effect = ( + self.fc2.delete_health_monitor) + instance.return_value.create_app_profile.side_effect = ( + self.fc2.create_app_profile) + instance.return_value.delete_app_profile.side_effect = ( + self.fc2.delete_app_profile) + self.pool_id = None + self.vip_id = None + + def setUp(self): + + n_config.parse(['--config-file', VCNS_CONFIG_FILE]) + # mock vcns + self.fc2 = fake_vcns.FakeVcns(unique_router_name=False) + self.mock_vcns = mock.patch(VCNS_NAME, autospec=True) + self.vcns_loadbalancer_patch() + + self.nvp_service_plugin_callback = mock.Mock() + self.driver = vcns_driver.VcnsDriver(self.nvp_service_plugin_callback) + + super(VcnsDriverTestCase, self).setUp() + self.addCleanup(self.fc2.reset_all) + self.addCleanup(self.mock_vcns.stop) + + def tearDown(self): + super(VcnsDriverTestCase, self).tearDown() + + +class TestEdgeLbDriver(VcnsDriverTestCase): + + def test_create_and_get_vip(self): + ctx = context.get_admin_context() + with self.pool(no_delete=True) as pool: + self.pool_id = pool['pool']['id'] + POOL_MAP_INFO['pool_id'] = pool['pool']['id'] + vcns_db.add_vcns_edge_pool_binding(ctx.session, POOL_MAP_INFO) + with self.vip(pool=pool) as res: + vip_create = res['vip'] + self.driver.create_vip(ctx, VSE_ID, vip_create) + vip_get = self.driver.get_vip(ctx, vip_create['id']) + for k, v in vip_get.iteritems(): + self.assertEqual(vip_create[k], v) + + def test_update_vip(self): + ctx = context.get_admin_context() + with self.pool(no_delete=True) as pool: + self.pool_id = pool['pool']['id'] + POOL_MAP_INFO['pool_id'] = pool['pool']['id'] + vcns_db.add_vcns_edge_pool_binding(ctx.session, POOL_MAP_INFO) + with self.vip(pool=pool) as res: + vip_create = res['vip'] + self.driver.create_vip(ctx, VSE_ID, vip_create) + vip_update = {'id': vip_create['id'], + 'pool_id': pool['pool']['id'], + 'name': 'update_name', + 'description': 'description', + 'address': 'update_address', + 'port_id': 'update_port_id', + 'protocol_port': 'protocol_port', + 'protocol': 'update_protocol'} + self.driver.update_vip(ctx, vip_update) + vip_get = self.driver.get_vip(ctx, vip_create['id']) + for k, v in vip_get.iteritems(): + if k in vip_update: + self.assertEqual(vip_update[k], v) + + def test_delete_vip(self): + ctx = context.get_admin_context() + with self.pool(no_delete=True) as pool: + self.pool_id = pool['pool']['id'] + POOL_MAP_INFO['pool_id'] = pool['pool']['id'] + vcns_db.add_vcns_edge_pool_binding(ctx.session, POOL_MAP_INFO) + with self.vip(pool=pool) as res: + vip_create = res['vip'] + self.driver.create_vip(ctx, VSE_ID, vip_create) + self.driver.delete_vip(ctx, vip_create['id']) + self.assertRaises(vcns_exc.VcnsNotFound, + self.driver.get_vip, + ctx, + vip_create['id']) + + #Test Pool Operation + def test_create_and_get_pool(self): + ctx = context.get_admin_context() + with self.pool(no_delete=True) as p: + self.pool_id = p['pool']['id'] + pool_create = p['pool'] + self.driver.create_pool(ctx, VSE_ID, pool_create, []) + pool_get = self.driver.get_pool(ctx, pool_create['id'], VSE_ID) + for k, v in pool_get.iteritems(): + self.assertEqual(pool_create[k], v) + + def test_update_pool(self): + ctx = context.get_admin_context() + with self.pool(no_delete=True) as p: + self.pool_id = p['pool']['id'] + pool_create = p['pool'] + self.driver.create_pool(ctx, VSE_ID, pool_create, []) + pool_update = {'id': pool_create['id'], + 'lb_method': 'lb_method', + 'name': 'update_name', + 'members': [], + 'health_monitors': []} + self.driver.update_pool(ctx, VSE_ID, pool_update, []) + pool_get = self.driver.get_pool(ctx, pool_create['id'], VSE_ID) + for k, v in pool_get.iteritems(): + if k in pool_update: + self.assertEqual(pool_update[k], v) + + def test_delete_pool(self): + ctx = context.get_admin_context() + with self.pool(no_delete=True) as p: + self.pool_id = p['pool']['id'] + pool_create = p['pool'] + self.driver.create_pool(ctx, VSE_ID, pool_create, []) + self.driver.delete_pool(ctx, pool_create['id'], VSE_ID) + self.assertRaises(vcns_exc.VcnsNotFound, + self.driver.get_pool, + ctx, + pool_create['id'], + VSE_ID) + + def test_create_and_get_monitor(self): + ctx = context.get_admin_context() + with self.health_monitor(no_delete=True) as m: + monitor_create = m['health_monitor'] + self.driver.create_health_monitor(ctx, VSE_ID, monitor_create) + monitor_get = self.driver.get_health_monitor( + ctx, monitor_create['id'], VSE_ID) + for k, v in monitor_get.iteritems(): + self.assertEqual(monitor_create[k], v) + + def test_update_health_monitor(self): + ctx = context.get_admin_context() + with self.health_monitor(no_delete=True) as m: + monitor_create = m['health_monitor'] + self.driver.create_health_monitor( + ctx, VSE_ID, monitor_create) + monitor_update = {'id': monitor_create['id'], + 'delay': 'new_delay', + 'timeout': "new_timeout", + 'type': 'type', + 'max_retries': "max_retries"} + self.driver.update_health_monitor( + ctx, VSE_ID, monitor_create, monitor_update) + monitor_get = self.driver.get_health_monitor( + ctx, monitor_create['id'], VSE_ID) + for k, v in monitor_get.iteritems(): + if k in monitor_update: + self.assertEqual(monitor_update[k], v) + + def test_delete_health_monitor(self): + ctx = context.get_admin_context() + with self.health_monitor(no_delete=True) as m: + monitor_create = m['health_monitor'] + self.driver.create_health_monitor(ctx, VSE_ID, monitor_create) + self.driver.delete_health_monitor( + ctx, monitor_create['id'], VSE_ID) + self.assertRaises(vcns_exc.VcnsNotFound, + self.driver.get_health_monitor, + ctx, + monitor_create['id'], + VSE_ID)