components/openstack/neutron/files/evs/db/l3nat.py
author Drew Fisher <drew.fisher@oracle.com>
Mon, 17 Mar 2014 09:51:44 -0600
changeset 1760 353323c7bdc1
child 1944 56ac2df1785b
permissions -rw-r--r--
PSARC/2013/350 OpenStack for Solaris (Umbrella) PSARC/2014/007 OpenStack client API components for Grizzly PSARC/2014/054 OpenStack Cinder (OpenStack Block Storage Service) PSARC/2014/055 OpenStack Glance (OpenStack Image Service) PSARC/2014/058 OpenStack Horizon (OpenStack Dashboard) PSARC/2014/048 OpenStack Keystone (OpenStack Identity Service) PSARC/2014/059 OpenStack Neutron (OpenStack Networking Service) PSARC/2014/049 OpenStack Nova (OpenStack Compute Service) 18290089 integrate cinderclient 18290097 integrate glanceclient 18290102 integrate keystoneclient 18290109 integrate neutronclient 18290113 integrate novaclient 18290119 integrate swiftclient 18290125 integrate quantumclient 18307582 Request to integrate Cinder into userland 18307595 Request to integrate Glance into userland 18307626 Request to integrate Horizon into userland 18307641 Request to integrate Keystone into userland 18307650 Request to integrate Neutron into userland 18307659 Request to integrate Nova into userland 18321909 a few Python packages deliver both po and mo files

# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 Nicira Networks, Inc.  All rights reserved.
#
# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
#
# @author: Dan Wendlandt, Nicira, Inc
# @author: Girish Moodalbail, Oracle, Inc.
#

"""
Based off generic l3_agent (quantum/agent/l3_agent) code
"""

import sqlalchemy as sa

from quantum.api.v2 import attributes
from quantum.common import constants as l3_constants
from quantum.common import exceptions as q_exc
from quantum.db import l3_db
from quantum.extensions import l3
from quantum.openstack.common import log as logging
from quantum.openstack.common import uuidutils
from quantum.plugins.evs.db import api as evs_db


LOG = logging.getLogger(__name__)

DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF
DEVICE_OWNER_ROUTER_GW = l3_constants.DEVICE_OWNER_ROUTER_GW
DEVICE_OWNER_FLOATINGIP = l3_constants.DEVICE_OWNER_FLOATINGIP


class Router(evs_db.EVS_DB_BASE):
    """Represents a v2 quantum router."""

    id = sa.Column(sa.String(36), primary_key=True,
                   default=uuidutils.generate_uuid)
    name = sa.Column(sa.String(255))
    status = sa.Column(sa.String(16))
    admin_state_up = sa.Column(sa.Boolean)
    tenant_id = sa.Column(sa.String(255))
    gw_port_id = sa.Column(sa.String(36))
    gw_port_network_id = sa.Column(sa.String(36))


class FloatingIP(evs_db.EVS_DB_BASE):
    """Represents a floating IP, which may or many not be
       allocated to a tenant, and may or may not be associated with
       an internal port/ip address/router."""

    id = sa.Column(sa.String(36), primary_key=True,
                   default=uuidutils.generate_uuid)
    floating_ip_address = sa.Column(sa.String(64), nullable=False)
    floating_network_id = sa.Column(sa.String(36), nullable=False)
    floating_port_id = sa.Column(sa.String(36), nullable=False)
    fixed_port_id = sa.Column(sa.String(36))
    fixed_ip_address = sa.Column(sa.String(64))
    router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'))
    tenant_id = sa.Column(sa.String(255))


class EVS_L3_NAT_db_mixin(l3_db.L3_NAT_db_mixin):
    """Mixin class to add L3/NAT router methods"""

    Router = Router
    FloatingIP = FloatingIP

    def _make_router_dict(self, router, fields=None):
        res = {'id': router['id'],
               'name': router['name'],
               'tenant_id': router['tenant_id'],
               'admin_state_up': router['admin_state_up'],
               'status': router['status'],
               'external_gateway_info': None}
        if router['gw_port_id']:
            nw_id = router['gw_port_network_id']
            res['external_gateway_info'] = {'network_id': nw_id}
        return self._fields(res, fields)

    def create_router(self, context, router):
        return super(EVS_L3_NAT_db_mixin, self).\
            create_router(evs_db.get_evs_context(context), router)

    def update_router(self, context, id, router):
        return super(EVS_L3_NAT_db_mixin, self).\
            update_router(evs_db.get_evs_context(context), id, router)

    def _update_router_gw_info(self, context, router_id, info):
        """This method overrides the base class method and it's contents
        are exactly same as the base class method except that the Router
        DB columns are different for EVS and OVS"""

        router = self._get_router(context, router_id)
        gw_port_id = router['gw_port_id']
        gw_port_network_id = router['gw_port_network_id']

        network_id = info.get('network_id', None) if info else None
        if network_id:
            self._get_network(context, network_id)
            if not self._network_is_external(context, network_id):
                msg = _("Network %s is not a valid external "
                        "network") % network_id
                raise q_exc.BadRequest(resource='router', msg=msg)

        # figure out if we need to delete existing port
        if gw_port_id and gw_port_network_id != network_id:
            fip_count = self.get_floatingips_count(context.elevated(),
                                                   {'router_id': [router_id]})
            if fip_count:
                raise l3.RouterExternalGatewayInUseByFloatingIp(
                    router_id=router_id, net_id=gw_port_network_id)
            with context.session.begin(subtransactions=True):
                router['gw_port_id'] = None
                router['gw_port_network_id'] = None
                context.session.add(router)
            self.delete_port(context.elevated(), gw_port_id,
                             l3_port_check=False)

        if network_id is not None and (gw_port_id is None or
                                       gw_port_network_id != network_id):
            subnets = self._get_subnets_by_network(context,
                                                   network_id)
            for subnet in subnets:
                self._check_for_dup_router_subnet(context, router_id,
                                                  network_id, subnet['id'],
                                                  subnet['cidr'])

            # Port has no 'tenant-id', as it is hidden from user
            gw_port = self.create_port(context.elevated(), {
                'port':
                {'tenant_id': '',  # intentionally not set
                 'network_id': network_id,
                 'mac_address': attributes.ATTR_NOT_SPECIFIED,
                 'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
                 'device_id': router_id,
                 'device_owner': DEVICE_OWNER_ROUTER_GW,
                 'admin_state_up': True,
                 'name': ''}})

            if not len(gw_port['fixed_ips']):
                self.delete_port(context.elevated(), gw_port['id'],
                                 l3_port_check=False)
                msg = (_('No IPs available for external network %s') %
                       network_id)
                raise q_exc.BadRequest(resource='router', msg=msg)

            with context.session.begin(subtransactions=True):
                router['gw_port_id'] = gw_port['id']
                router['gw_port_network_id'] = gw_port['network_id']
                context.session.add(router)

    def delete_router(self, context, id):
        super(EVS_L3_NAT_db_mixin, self).\
            delete_router(evs_db.get_evs_context(context), id)

    def get_router(self, context, id, fields=None):
        return super(EVS_L3_NAT_db_mixin, self).\
            get_router(evs_db.get_evs_context(context), id, fields)

    def get_routers(self, context, filters=None, fields=None,
                    sorts=None, limit=None, marker=None, page_reverse=False):

        query = evs_db.get_session().query(self.Router)
        if filters is not None:
            for key, value in filters.iteritems():
                column = getattr(self.Router, key, None)
                if column:
                    query = query.filter(column.in_(value))

        routers = query.all()
        retlist = []
        for router in routers:
            retlist.append(self._make_router_dict(router, fields))
        return retlist

    def get_routers_count(self, context, filters=None):
        return len(self.get_routers(context, filters))

    def add_router_interface(self, context, router_id, interface_info):
        return super(EVS_L3_NAT_db_mixin, self).\
            add_router_interface(evs_db.get_evs_context(context),
                                 router_id, interface_info)

    def remove_router_interface(self, context, router_id, interface_info):
        super(EVS_L3_NAT_db_mixin, self).\
            remove_router_interface(evs_db.get_evs_context(context),
                                    router_id, interface_info)

    def create_floatingip(self, context, floatingip):
        return super(EVS_L3_NAT_db_mixin, self).\
            create_floatingip(evs_db.get_evs_context(context), floatingip)

    def update_floatingip(self, context, id, floatingip):
        return super(EVS_L3_NAT_db_mixin, self).\
            update_floatingip(evs_db.get_evs_context(context), id, floatingip)

    def delete_floatingip(self, context, id):
        super(EVS_L3_NAT_db_mixin, self).\
            delete_floatingip(evs_db.get_evs_context(context), id)

    def get_floatingip(self, context, id, fields=None):
        return super(EVS_L3_NAT_db_mixin, self).\
            get_floatingip(evs_db.get_evs_context(context), id, fields)

    def get_floatingips(self, context, filters=None, fields=None,
                        sorts=None, limit=None, marker=None,
                        page_reverse=False):

        query = evs_db.get_session().query(self.FloatingIP)
        if filters:
            for key, value in filters.iteritems():
                column = getattr(self.FloatingIP, key, None)
                if column:
                    query = query.filter(column.in_(value))

        floatingips = query.all()
        retlist = []
        for floatingip in floatingips:
            retlist.append(self._make_floatingip_dict(floatingip, fields))
        return retlist

    def get_floatingips_count(self, context, filters=None):
        return len(self.get_floatingips(context, filters))

    def prevent_l3_port_deletion(self, context, port_id):
        """ Checks to make sure a port is allowed to be deleted, raising
        an exception if this is not the case.  This should be called by
        any plugin when the API requests the deletion of a port, since
        some ports for L3 are not intended to be deleted directly via a
        DELETE to /ports, but rather via other API calls that perform the
        proper deletion checks.
        """
        port = self.get_port(context, port_id)
        if port['device_owner'] in [DEVICE_OWNER_ROUTER_INTF,
                                    DEVICE_OWNER_ROUTER_GW,
                                    DEVICE_OWNER_FLOATINGIP]:
            raise l3.L3PortInUse(port_id=port_id,
                                 device_owner=port['device_owner'])

    def disassociate_floatingips(self, context, port_id):
        super(EVS_L3_NAT_db_mixin, self).\
            disassociate_floatingips(evs_db.get_evs_context(context), port_id)

    def _network_is_external(self, context, net_id):
        try:
            evs = self.get_network(context, net_id)
            return evs[l3.EXTERNAL]
        except:
            return False

    def get_sync_data(self, context, router_ids=None, active=None):
        return super(EVS_L3_NAT_db_mixin, self).\
            get_sync_data(evs_db.get_evs_context(context), router_ids, active)

    def get_external_network_id(self, context):
        return super(EVS_L3_NAT_db_mixin, self).\
            get_external_network_id(evs_db.get_evs_context(context))

    def _get_tenant_id_for_create(self, context, resource):
        if context.is_admin and 'tenant_id' in resource:
            tenant_id = resource['tenant_id']
        elif ('tenant_id' in resource and
              resource['tenant_id'] != context.tenant_id):
            reason = _('Cannot create resource for another tenant')
            raise q_exc.AdminRequired(reason=reason)
        else:
            tenant_id = context.tenant_id
        return tenant_id

    def _get_by_id(self, context, model, id):
        return context.session.query(model).\
            filter(model.id == id).one()

    def _get_network(self, context, network_id):
        return self.get_network(context, network_id)

    def _get_subnet(self, context, subnet_id):
        return self.get_subnet(context, subnet_id)

    def _get_port(self, context, port_id):
        return self.get_port(context, port_id)

    def _delete_port(self, context, port_id):
        return self.delete_port(context, port_id)

    def _get_subnets_by_network(self, context, network_id):
        return self.get_subnets(context, filters={'network_id': network_id})

    def allow_l3_port_deletion(self, context, port_id):
        """ If an L3 agent is using this port, then we need to send
        a notification to L3 agent to release the port before we can
        delete the port"""
        pass