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 0000000000..cc4807fa12 --- /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 7161341ed5..576e047303 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 7da4b3323a..e26a49acab 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 f223eca4cc..17904eeb86 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 e2be5f9537..c63ff221ac 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 fc51d20020..6818a0c8fa 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 5e8da711fb..6818a0c8fa 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 246cba5873..9d15dc16da 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 0000000000..7e3ced5c8f --- /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 beace318da..803b979880 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 8178744a91..c2c5c14992 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 d646afd0f2..c54c72939b 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 f2deff6145..ad772d77ab 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 0000000000..94ed0bb0e1 --- /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 adc6bcee8c..be6c11fc1a 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 3435c3b460..e6999dd69d 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 0000000000..40a3d5ccac --- /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)