components/openstack/neutron/files/evs/plugin.py
author Girish Moodalbail <Girish.Moodalbail@oracle.COM>
Fri, 18 Apr 2014 18:22:59 -0700
branchs11-update
changeset 3088 3f570a6d19b9
parent 3077 3e8d5f02f4a0
child 1944 56ac2df1785b
permissions -rw-r--r--
18610375 Terminating a VM instance doesn't release floating ip associated with it 17975048 'neutron subnet-create' has problems with gateway IPs

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

# 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: Girish Moodalbail, Oracle, Inc.

import netaddr
import rad.client as radcli
import rad.connect as radcon
import rad.bindings.com.oracle.solaris.rad.evscntl_1 as evsbind

from oslo.config import cfg

from quantum.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from quantum.api.rpc.agentnotifiers import l3_rpc_agent_api
from quantum.api.v2 import attributes
from quantum.common import constants as l3_constants
from quantum.common import exceptions
from quantum.common import rpc as q_rpc
from quantum.common import topics
from quantum.db import dhcp_rpc_base
from quantum.db import l3_rpc_base
from quantum.extensions import l3
from quantum.extensions import providernet
from quantum.openstack.common import log as logging
from quantum.openstack.common import rpc
from quantum.plugins.evs.db import api as evs_db
from quantum.plugins.evs.db import l3nat as evs_l3nat
from quantum import quantum_plugin_base_v2


LOG = logging.getLogger(__name__)

evs_controller_opts = [
    cfg.StrOpt('evs_controller', default='ssh://evsuser@localhost',
               help=_("An URI that specifies an EVS controller"))
]

cfg.CONF.register_opts(evs_controller_opts, "EVS")

# Maps OpenStack network resource attributes to EVS properties
NETWORK_EVS_ATTRIBUTE_MAP = {
    'tenant_id': 'tenant',
    'network_id': 'evs',
    'id': 'evs',
    'name': 'evs',
    l3.EXTERNAL: 'OpenStack:' + l3.EXTERNAL,
}

# Maps OpenStack subnet resource attributes to EVS' IPnet properties
SUBNET_IPNET_ATTRIBUTE_MAP = {
    'tenant_id': 'tenant',
    'network_id': 'evs',
    'id': 'ipnet',
    'name': 'ipnet',
    'enable_dhcp': 'OpenStack:enable_dhcp',
    'dns_nameservers': 'OpenStack:dns_nameservers',
    'host_routes': 'OpenStack:host_routes',
}

# Maps OpenStack port resource attributes to EVS' VPort properties
PORT_VPORT_ATTRIBUTE_MAP = {
    'tenant_id': 'tenant',
    'network_id': 'evs',
    'id': 'vport',
    'name': 'vport',
    'device_id': 'OpenStack:device_id',
    'device_owner': 'OpenStack:device_owner',
}


class EVSControllerError(exceptions.QuantumException):
    message = _("EVS controller: %(errmsg)s")

    def __init__(self, evs_errmsg):
        super(EVSControllerError, self).__init__(errmsg=evs_errmsg)


class EVSOpNotSupported(exceptions.QuantumException):
    message = _("Operation not supported by EVS plugin: %(opname)s")

    def __init__(self, evs_errmsg):
        super(EVSOpNotSupported, self).__init__(opname=evs_errmsg)


class EVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
                      l3_rpc_base.L3RpcCallbackMixin):
    RPC_API_VERSION = '1.0'

    def create_rpc_dispatcher(self):
        '''Get the rpc dispatcher for this manager.

        If a manager would like to set an rpc API version, or support more than
        one class as the target of rpc messages, override this method.
        '''
        return q_rpc.PluginRpcDispatcher([self])


class EVSQuantumPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2,
                         evs_l3nat.EVS_L3_NAT_db_mixin):
    """Implements v2 Neutron Plug-in API specification.

    All the quantum API calls to create/delete/retrieve Network/Subnet/Port
    are forwarded to EVS controller through Solaris RAD. The RAD connection
    to EVS Controller is over SSH. In order that this plugin can communicate
    with EVS Controller non-interactively and securely, one should setup SSH
    authentication with pre-shared keys between the host running neutron-server
    and the host running EVS controller.

    The following table maps OpenStack Neutron resources and attributes to
    Solaris Elastic Virtual Switch resources and attributes

    |---------------------+------------------+------------------------------|
    | OpenStack Neutron   | Solaris EVS      | Comments                     |
    |---------------------+------------------+------------------------------|
    | Network             | EVS              | Represents an isolated L2    |
    | -- name             | -- name          | segment; implemented either  |
    | -- id               | -- uuid          | through VLANs or VXLANs      |
    | -- tenant_id        | -- tenant        |                              |
    | -- shared           | Always False     |                              |
    | -- admin_state_up   | Always True      |                              |
    | -- status           | Always ACTIVE    |                              |
    | -- provider:        |                  |                              |
    |    network_type     |  -- l2-type      | (either VLAN or VXLAN)       |
    | -- provider:        |                  |                              |
    |    segmentation_id  |  -- vlanid/vni   |                              |
    |                     |                  |                              |
    |                     |                  |                              |
    | Subnet              | IPnet            | An IP network represents     |
    | -- name             | -- name          | a block of either IPv4       |
    | -- id               | -- uuid          | or IPv6 addresses (subnet)   |
    | -- network_id       | -- evs           | along with a default router  |
    | -- tenant_id        | -- tenant        | for the block                |
    | -- cidr             | -- subnet        |                              |
    | -- gateway_ip       | -- defrouter     |                              |
    | -- allocation_pools | -- start/stop    |                              |
    | -- dns_nameservers  | -- OpenStack:\   |                              |
    |                     | dns_nameservers  |                              |
    | -- host_routes      | -- OpenStack:\   |                              |
    |                     | host_routes      |                              |
    | -- enable_dhcp      | -- OpenStack:\   |                              |
    |                     | enable_dhcp      |                              |
    | -- shared           | Always False     |                              |
    |                     |                  |                              |
    | Port                | VPort            | A VPort represents the point |
    | -- name             | -- name          | of attachment between the    |
    | -- id               | -- uuid          | VNIC and an EVS. It          |
    | -- network_id       | -- evs           | encapsulates various network |
    | -- tenant_id        | -- tenant        | configuration parameters (   |
    | -- status           | -- status        | MAC addresses, IP addresses, |
    | -- mac_address      | -- macaddr       | and SLAs)                    |
    | -- fixed_ips        | -- ipaddr        |                              |
    | -- device_id        | -- OpenStack:\   |                              |
    |                     |    device_id     |                              |
    | -- device_owner     | -- OpenStack:\   |                              |
    |                     |    device_owner  |                              |
    | -- security_groups  | -- Not Supported |                              |
    | -- admin_state_up   | Always UP        |                              |
    |---------------------+------------------+------------------------------|
    """

    _supported_extension_aliases = ["provider", "router", "quotas"]

    def __init__(self):
        # Since EVS Framework does not support router and floatingip
        # resources, the plugin itself will maintain a DB for these
        # two resources
        evs_db.configure_db()

        # Since there is no connect_uri() yet, we need to do this ourselves
        # parse ssh://user@hostname
        suh = cfg.CONF.EVS.evs_controller.split('://')
        if len(suh) != 2 or suh[0] != 'ssh' or not suh[1].strip():
            raise SystemExit(_("Specified evs_controller is invalid"))
        uh = suh[1].split('@')
        if len(uh) != 2 or not uh[0].strip() or not uh[1].strip():
            raise SystemExit(_("'user' and 'hostname' need to be specified "
                               "for evs_controller"))

        # TODO(gmoodalb): - try few times before you give up
        self._rc = radcon.connect_ssh(uh[1], user=uh[0])
        self._evsc = self._rc.get_object(evsbind.EVSController())
        self._setup_rpc()

    def _setup_rpc(self):
        # RPC support
        self.topic = topics.PLUGIN
        self.conn = rpc.create_connection(new=True)
        self.callbacks = EVSRpcCallbacks()
        self.dispatcher = self.callbacks.create_rpc_dispatcher()
        self.conn.create_consumer(self.topic, self.dispatcher, fanout=False)
        # Consume from all consumers in a thread
        self.conn.consume_in_thread()
        self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()

    @property
    def supported_extension_aliases(self):
        return self._supported_extension_aliases

    def _fields(self, resource, fields):
        if fields:
            return dict(((key, item) for key, item in resource.iteritems()
                         if key in fields))
        return resource

    def _convert_evs_to_network(self, evs):
        """Converts an EVS structure into Neutron Network structure."""

        networkdict = dict()
        networkdict['name'] = evs.name
        networkdict['id'] = evs.uuid
        networkdict['subnets'] = ([ipnet.uuid for ipnet in evs.ipnets]
                                  if evs.ipnets else [])
        networkdict['tenant_id'] = evs.tenantname
        networkdict[l3.EXTERNAL] = False
        for prop in evs.props:
            if prop.name == 'l2-type':
                networkdict[providernet.NETWORK_TYPE] = prop.value
            if prop.name == 'vlanid' or prop.name == 'vni':
                networkdict[providernet.SEGMENTATION_ID] = int(prop.value)
            if prop.name == NETWORK_EVS_ATTRIBUTE_MAP[l3.EXTERNAL]:
                networkdict[l3.EXTERNAL] = \
                    (True if prop.value == 'True' else False)
        # fixed values as EVS framework doesn't support this
        networkdict['admin_state_up'] = True
        networkdict['status'] = 'ACTIVE'
        networkdict['shared'] = False

        return networkdict

    def _convert_ipnet_to_subnet(self, ipnet):
        """Converts an EVS IPnet structure into Neutron Subnet structure."""

        subnetdict = dict()
        subnetdict['name'] = ipnet.name
        subnetdict['network_id'] = ipnet.evsuuid
        subnetdict['id'] = ipnet.uuid
        subnetdict['tenant_id'] = ipnet.tenantname
        subnetdict['ip_version'] = \
            (4 if ipnet.ipvers == evsbind.IPVersion.IPV4 else 6)
        # assign default values to some subnet attributes
        subnetdict['dns_nameservers'] = []
        subnetdict['host_routes'] = []
        subnetdict['enable_dhcp'] = False
        for prop in ipnet.props:
            if prop.name == 'defrouter':
                subnetdict['gateway_ip'] = prop.value
            if prop.name == 'subnet':
                subnetdict['cidr'] = prop.value
            if prop.name == SUBNET_IPNET_ATTRIBUTE_MAP['enable_dhcp']:
                subnetdict['enable_dhcp'] = \
                    (True if prop.value == 'True' else False)
            if prop.name == SUBNET_IPNET_ATTRIBUTE_MAP['dns_nameservers']:
                subnetdict['dns_nameservers'] = prop.value.split(',')
            if prop.name == SUBNET_IPNET_ATTRIBUTE_MAP['host_routes']:
                hrlist = []
                vlist = prop.value.split(',')
                for i in range(0, len(vlist), 2):
                    hrlist.append({vlist[i]: vlist[i + 1]})
                subnetdict['host_routes'] = hrlist
        # EVS Controller returns a pool that includes gateway_ip as-well,
        # however neutron expects pool without gateway_ip. So, we determine
        # the pool ourselves here.
        assert 'gateway_ip' in subnetdict
        start_ip = netaddr.IPAddress(ipnet.start)
        end_ip = netaddr.IPAddress(ipnet.end)
        gw_ip = netaddr.IPAddress(subnetdict['gateway_ip'])
        pools = []
        if gw_ip == start_ip:
            pools.append({'start' : str(netaddr.IPAddress(start_ip + 1)),
                          'end': str(netaddr.IPAddress(end_ip))})
        elif gw_ip == end_ip:
            pools.append({'start' : str(netaddr.IPAddress(start_ip)),
                          'end': str(netaddr.IPAddress(end_ip - 1))})
        else:
            pools.append({'start': str(netaddr.IPAddress(start_ip)),
                          'end' : str(netaddr.IPAddress(gw_ip - 1))})
            pools.append({'start': str(netaddr.IPAddress(gw_ip + 1)),
                          'end' : str(netaddr.IPAddress(end_ip))})

        subnetdict['allocation_pools'] = pools
        subnetdict['shared'] = False

        return subnetdict

    def _convert_vport_to_port(self, context, vport):
        """Converts an EVS VPort structure into Neutron port structure."""

        portdict = dict()
        portdict['admin_state_up'] = True
        portdict['id'] = vport.uuid
        portdict['name'] = vport.name
        portdict['network_id'] = vport.evsuuid
        #TODO(gmoodalb): set to host/zonename/vnicname?
        portdict['device_id'] = ''
        portdict['device_owner'] = ''
        for prop in vport.props:
            if (prop.name == 'macaddr'):
                portdict['mac_address'] = prop.value
            if (prop.name == 'ipaddr'):
                evs = self.get_network(context, vport.evsuuid)
                portdict['fixed_ips'] = \
                    [{
                        'ip_address': prop.value.split('/')[0],
                        'subnet_id': evs['subnets'][0],
                    }]
            if (prop.name == 'OpenStack:device_id'):
                portdict['device_id'] = prop.value
            if (prop.name == 'OpenStack:device_owner'):
                portdict['device_owner'] = prop.value
        portdict['security_groups'] = []
        portdict['status'] = 'ACTIVE'
        portdict['tenant_id'] = vport.tenantname

        return portdict

    def _apply_rsrc_props_filter(self, rsrclist, filters):
        # if all of the filter values are None, then return
        if all([value is None for value in filters.values()]):
            return

        rsrc_to_remove = []
        for rsrc in rsrclist:
            propdict = dict((prop.name, prop.value) for prop in rsrc.props)
            for key, value in filters.iteritems():
                if value is None:
                    continue
                if key not in propdict:
                    rsrc_to_remove.append(rsrc)
                    break
                elif isinstance(value, list):
                    strlist = [str(v) for v in value]
                    if propdict[key] not in strlist:
                        rsrc_to_remove.append(rsrc)
                        break
                # TODO(gmoodalb): - check if it's an instance of basestring?
                elif propdict[key] != str(value):
                    rsrc_to_remove.append(rsrc)
                    break

        for rsrc in rsrc_to_remove:
            rsrclist.remove(rsrc)

    def create_subnet(self, context, subnet):
        """Creates a subnet(IPnet) for a given network(EVS).

         An IP network represents a block of either IPv4 or IPv6 addresses
         (i.e., subnet) along with a default router for the block. Only one
         IPnet can be associated with an EVS. All the zones/VNICs that
         connect to the EVS, through a VPort, will get an IP address from the
         IPnet associated with the EVS.
        """
        ipnetname = subnet['subnet']['name']
        if not ipnetname:
            ipnetname = None

        proplist = ['subnet=%s' % (subnet['subnet']['cidr'])]

        # obtain the optional default router
        defrouter = subnet['subnet']['gateway_ip']
        if defrouter is None:
            # user specified --no-gateway, we don't support it
            raise EVSOpNotSupported(_("cannot use --no-gateway"))
        if defrouter is not attributes.ATTR_NOT_SPECIFIED:
            proplist.append('defrouter=%s' % (defrouter))

        # obtain the optional DNS nameservers
        nameservers = subnet['subnet']['dns_nameservers']
        if attributes.is_attr_set(nameservers):
            proplist.append('%s=%s' %
                            (SUBNET_IPNET_ATTRIBUTE_MAP['dns_nameservers'],
                            ','.join(nameservers)))

        # obtain the host routes
        hostroutes = subnet['subnet']['host_routes']
        if attributes.is_attr_set(hostroutes):
            hrlist = ['%s,%s' % (destination, nexthop)
                      for destination, nexthop in hostroutes]
            proplist.append('%s=%s' %
                            (SUBNET_IPNET_ATTRIBUTE_MAP['host_routes'],
                             ",".join(hrlist)))

        enable_dhcp = subnet['subnet']['enable_dhcp']
        proplist.append('%s=%s' %
                        (SUBNET_IPNET_ATTRIBUTE_MAP['enable_dhcp'],
                         enable_dhcp))

        propstr = None
        if proplist:
            propstr = ",".join(proplist)

        # TODO(gmoodalb): extract the tenant id if an admin is creating for
        # someone else
        try:
            evsuuid = subnet['subnet']['network_id']
            tenantname = subnet['subnet']['tenant_id']
            pat = radcli.ADRGlobPattern(
                {'name': evsuuid, 'tenant': tenantname})
            evs = self._rc.get_object(evsbind.EVS(), pat)
            ipnet = evs.addIPnet(propstr, ipnetname)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        retval = self._convert_ipnet_to_subnet(ipnet)

        # notify dhcp agent of subnet creation
        self.dhcp_agent_notifier.notify(context, {'subnet': retval},
                                        'subnet.create.end')
        return retval

    def update_subnet(self, context, id, subnet):
        evs_rpccall_sync = subnet.pop('evs_rpccall_sync', False)
        if not (set(subnet['subnet'].keys()) == set(('enable_dhcp',))):
                raise EVSOpNotSupported(_("only enable_dhcp can be updated"))

        propstr = "%s=%s" % (SUBNET_IPNET_ATTRIBUTE_MAP['enable_dhcp'],
                             subnet['subnet']['enable_dhcp'])
        try:
            pat = radcli.ADRGlobPattern({'uuid': id})
            ipnetlist = self._rc.list_objects(evsbind.IPnet(), pat)
            assert len(ipnetlist) == 1
            ipnet = self._rc.get_object(ipnetlist[0])
            ipnet.setProperty(propstr)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        retval = self.get_subnet(context, id)

        # notify dhcp agent of subnet update
        methodname = 'subnet.update.end'
        payload = {'subnet': retval}
        if not evs_rpccall_sync:
            self.dhcp_agent_notifier.notify(context, payload, methodname)
        else:
            msg = self.dhcp_agent_notifier.make_msg(
                methodname.replace(".", "_"), payload=payload)
            self.dhcp_agent_notifier.call(context, msg,
                                          topic=topics.DHCP_AGENT)
        return retval

    def get_subnet(self, context, id, fields=None):
        try:
            ipnetlist = self._evsc.getIPnetInfo('ipnet=%s' % (id))
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        if ipnetlist:
            subnetdict = self._convert_ipnet_to_subnet(ipnetlist[0])
            return self._fields(subnetdict, fields)
        return {}

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

        filterstr = None
        # EVS desn't support filtering of resource based on
        # properties, so we will have to filter ourselves
        ipnet_props = {'OpenStack:enable_dhcp': None}
        if filters is not None:
            filterlist = []
            for key, value in filters.items():
                if key == 'shared':
                    if any(value):
                        return []
                    continue
                if key == 'verbose':
                    continue
                if key == 'enable_dhcp':
                    ipnet_props[SUBNET_IPNET_ATTRIBUTE_MAP[key]] = value
                    continue
                key = SUBNET_IPNET_ATTRIBUTE_MAP.get(key, key)
                if isinstance(value, list):
                    value = ",".join([str(val) for val in value])
                filterlist.append("%s=%s" % (key, value))

            if filterlist:
                filterstr = ",".join(filterlist)

        LOG.debug(_("calling ListIPnet from get_subnets() filterstr: '%s'")
                  % (filterstr))
        try:
            ipnetlist = self._evsc.getIPnetInfo(filterstr)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        self._apply_rsrc_props_filter(ipnetlist, ipnet_props)

        retme = []
        for ipnet in ipnetlist:
            subnetdict = self._convert_ipnet_to_subnet(ipnet)
            retme.append(self._fields(subnetdict, fields))

        return retme

    def get_subnets_count(self, context, filters=None):
        return len(self.get_ipnets(context, filters))

    def _release_subnet_dhcp_port(self, context, subnet, delete_network):
        """Release any dhcp port associated with the subnet"""
        filters = dict(evs=subnet['network_id'])
        portlist = self.get_ports(context, filters)

        if delete_network:
            # One can delete a network if there is only one port that has a
            # VNIC attached to it and that port happens to be a DHCP port.
            ports_with_deviceid = [port for port in portlist
                                   if port['device_id'] != '']
            update_subnet = len(ports_with_deviceid) == 1
        else:
            # One can delete a subnet if there is only one port and that
            # port happens to be a DHCP port.
            update_subnet = len(portlist) == 1
        if update_subnet:
            # the lone port is a dhcp port created by dhcp agent
            # it must be released before we can delete the subnet
            subnet_update = {'subnet': {'enable_dhcp': False},
                                        'evs_rpccall_sync': True}
            self.update_subnet(context, subnet['id'], subnet_update)

    def delete_subnet(self, context, id):
        try:
            subnet = self.get_subnet(context, id)
            if not subnet:
                return
            pat = radcli.ADRGlobPattern({'name': subnet['network_id'],
                                         'tenant': subnet['tenant_id']})
            evs = self._rc.get_object(evsbind.EVS(), pat)
            # If the subnet is dhcp_enabled, then the dhcp agent would have
            # created a port connected to this subnet. We need to remove
            # that port before we can proceed with subnet delete operation.
            # Since, there is no subnet.delete.start event, we use an another
            # approach of updating the subnet's enable_dhcp attribute to
            # False that in turn sends a subnet.udpate notification. This
            # results in DHCP agent releasing the port.
            if subnet['enable_dhcp']:
                self._release_subnet_dhcp_port(context, subnet, False)
            evs.removeIPnet(id)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        # notify dhcp agent
        payload = {
            'subnet': {
                'network_id': subnet['network_id'],
                'id': id,
            }
        }
        self.dhcp_agent_notifier.notify(context, payload, 'subnet.delete.end')

    def create_network(self, context, network):
        """Creates a network(EVS) for a given tenant.

        An Elastic Virtual Switch (EVS) is a virtual switch that spans
        one or more servers (physical machines). It represents an isolated L2
        segment, and the isolation is implemented either through VLANs or
        VXLANs. An EVS provides network connectivity between the Virtual
        Machines connected to it. There are two main resources associated with
        an EVS: IPnet and VPort.
        """
        evsname = network['network']['name']
        if not evsname:
            evsname = None

        tenantname = network['network']['tenant_id']
        proplist = []
        network_type = network['network'][providernet.NETWORK_TYPE]
        if attributes.is_attr_set(network_type):
            proplist.append('l2-type=%s' % network_type)

        segment_id = network['network'][providernet.SEGMENTATION_ID]
        if attributes.is_attr_set(segment_id):
            if (not attributes.is_attr_set(network_type) or
                    len(network_type) == 0):
                raise EVSControllerError(_("provider:network_type must be "
                                           "specified when provider:"
                                           "segmentation_id is provided"))

            if network_type == 'vxlan':
                proplist.append('vni=%d' % segment_id)
            elif network_type == 'vlan':
                proplist.append('vlanid=%d' % segment_id)
            else:
                raise EVSControllerError(_("specified "
                                           "provider:network_type '%s' not "
                                           "supported") % network_type)

        router_external = network['network'][l3.EXTERNAL]
        if attributes.is_attr_set(router_external):
            proplist.append("%s=%s" % (NETWORK_EVS_ATTRIBUTE_MAP[l3.EXTERNAL],
                                       router_external))

        propstr = None
        if proplist:
            propstr = ",".join(proplist)

        try:
            evs = self._evsc.createEVS(propstr, tenantname, evsname)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        return self._convert_evs_to_network(evs)

    def update_network(self, context, id, network):
        raise EVSOpNotSupported(_("net-update"))

    def get_network(self, context, id, fields=None):
        try:
            evslist = self._evsc.getEVSInfo('evs=%s' % id)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.getpayload().errmsg)

        if evslist:
            networkdict = self._convert_evs_to_network(evslist[0])
            return self._fields(networkdict, fields)
        return {}

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

        filterstr = None
        # EVS desn't support filtering of resource based on
        # properties, so we will have to filter ourselves
        evs_props = {'OpenStack:router:external': None}
        if filters is not None:
            filterlist = []
            for key, value in filters.items():
                if key == 'shared':
                    if any(value):
                        # EVS doesn't support shared networks
                        return []
                    continue
                if key in ('admin_state_up', 'verbose'):
                    continue
                if key == 'router:external':
                    evs_props[NETWORK_EVS_ATTRIBUTE_MAP[key]] = value
                    continue
                key = NETWORK_EVS_ATTRIBUTE_MAP.get(key, key)
                if isinstance(value, list):
                    value = ",".join([str(val) for val in value])
                filterlist.append("%s=%s" % (key, value))

            if filterlist:
                filterstr = ",".join(filterlist)

        LOG.debug(_("calling ListEVswitch from get_networks(): '%s'")
                  % (filterstr))
        try:
            evslist = self._evsc.getEVSInfo(filterstr)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        self._apply_rsrc_props_filter(evslist, evs_props)

        retme = []
        for evs in evslist:
            networkdict = self._convert_evs_to_network(evs)
            retme.append(self._fields(networkdict, fields))

        return retme

    def get_networks_count(self, context, filters=None):
        return len(self.get_networks(context, filters))

    def delete_network(self, context, id):
        try:
            filters = dict(network_id=id)
            subnets = self.get_subnets(context, filters=filters)
            dhcp_subnets = [s for s in subnets if s['enable_dhcp']]
            for subnet in dhcp_subnets:
                self._release_subnet_dhcp_port(context, subnet, True)
            self._evsc.deleteEVS(id, context.tenant_id)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        # notify dhcp agent of network deletion
        self.dhcp_agent_notifier.notify(context, {'network': {'id': id}},
                                        'network.delete.end')

    def create_port(self, context, port):
        """Creates a port(VPort) for a given network(EVS).

         A VPort represents the point of attachment between the VNIC and an
         EVS. It encapsulates various network configuration parameters such as
             -- SLAs (maxbw, cos, and priority)
             -- IP address and
             -- MAC address, et al
         This configuration is inherited by the VNIC when it connects to the
         VPort.
        """
        vportname = port['port']['name']
        if not vportname:
            vportname = None

        proplist = []
        macaddr = port['port']['mac_address']
        if attributes.is_attr_set(macaddr):
            proplist.append('macaddr=%s' % (macaddr))

        fixed_ips = port['port']['fixed_ips']
        if attributes.is_attr_set(fixed_ips):
            # we only support one subnet
            ipaddr = fixed_ips[0].get('ip_address')
            if ipaddr is not None:
                proplist.append('ipaddr=%s' % ipaddr)

        # retrieve device_id and device_owner
        device_id = port['port']['device_id']
        if attributes.is_attr_set(device_id) and device_id:
            proplist.append("%s=%s" %
                            (PORT_VPORT_ATTRIBUTE_MAP['device_id'], device_id))

        device_owner = port['port']['device_owner']
        if attributes.is_attr_set(device_owner) and device_owner:
            proplist.append("%s=%s" %
                            (PORT_VPORT_ATTRIBUTE_MAP['device_owner'],
                            device_owner))

        propstr = None
        if proplist:
            propstr = ",".join(proplist)

        try:
            evsuuid = port['port']['network_id']
            tenantname = port['port']['tenant_id']
            # TODO(gmoodalb): -- pull it from the network_id!!
            if not tenantname:
                tenantname = context.tenant_id
            pat = radcli.ADRGlobPattern({'name': evsuuid,
                                         'tenant': tenantname})
            evs = self._rc.get_object(evsbind.EVS(), pat)
            vport = evs.addVPort(propstr, vportname)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        retval = self._convert_vport_to_port(context, vport)

        # notify dhcp agent of port creation
        self.dhcp_agent_notifier.notify(context, {'port': retval},
                                        'port.create.end')
        return retval

    def update_port(self, context, id, port):
        # EVS does not allow updating certain attributes
        if not (set(port['port'].keys()) <=
                set(('device_id', 'device_owner'))):
            raise EVSOpNotSupported(_("only device_id and "
                                      "device_owner supported"))

        proplist = []
        device_id = port['port'].get('device_id')
        if device_id is not None:
            # EVS expects property values to be non-zero length
            if len(device_id) == 0:
                device_id = " "
            proplist.append("%s=%s" %
                            (PORT_VPORT_ATTRIBUTE_MAP['device_id'], device_id))

        device_owner = port['port'].get('device_owner')
        if device_owner is not None:
            if len(device_owner) == 0:
                device_owner = " "
            proplist.append("%s=%s" %
                            (PORT_VPORT_ATTRIBUTE_MAP['device_owner'],
                            device_owner))

        if not proplist:
            return dict()

        try:
            pat = radcli.ADRGlobPattern({'uuid': id})
            vportlist = self._rc.list_objects(evsbind.VPort(), pat)
            assert len(vportlist) == 1
            vport = self._rc.get_object(vportlist[0])
            for prop in proplist:
                vport.setProperty(prop)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        retval = self.get_port(context, id)

        # notify dhcp agent of port update
        self.dhcp_agent_notifier.notify(context, {'port': retval},
                                        'port.update.end')
        return retval

    def get_port(self, context, id, fields=None):
        try:
            vportlist = self._evsc.getVPortInfo('vport=%s' % (id))
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        if vportlist:
            portdict = self._convert_vport_to_port(context, vportlist[0])
            return self._fields(portdict, fields)
        return {}

    def get_ports(self, context, filters=None, fields=None,
                  sorts=None, limit=None, marker=None, page_reverse=False):
        LOG.debug(_("inside the get_ports() method: filters: '%s'") %
                  str(filters))

        filterstr = None
        # EVS desn't support filtering of resource based on
        # properties, so we will have to filter ourselves
        vport_props = {'OpenStack:device_id': None,
                       'OpenStack:device_owner': None}
        if filters is not None:
            filterlist = []
            for key, value in filters.items():
                if key == 'shared':
                    if any(value):
                        return []
                    continue
                if key == 'admin_state_up':
                    continue
                if key in ('device_id', 'device_owner'):
                    vport_props[PORT_VPORT_ATTRIBUTE_MAP[key]] = value
                    continue
                key = PORT_VPORT_ATTRIBUTE_MAP.get(key, key)
                if isinstance(value, list):
                    value = ",".join([str(val) for val in value])
                filterlist.append("%s=%s" % (key, value))

            if filterlist:
                filterstr = ",".join(filterlist)

        LOG.debug(_("calling getVPortInfo from get_ports(): '%s'") %
                  (filterstr))
        try:
            vportlist = self._evsc.getVPortInfo(filterstr)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        self._apply_rsrc_props_filter(vportlist, vport_props)

        retme = []
        for vport in vportlist:
            portdict = self._convert_vport_to_port(context, vport)
            retme.append(self._fields(portdict, fields))

        return retme

    def get_ports_count(self, context, filters=None):
        return len(self.get_ports(context, filters))

    def _release_l3agent_internal_port(self, context, port):
        """ 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"""

        if port['device_owner'] not in [l3_constants.DEVICE_OWNER_ROUTER_INTF,
                                        l3_constants.DEVICE_OWNER_ROUTER_GW,
                                        l3_constants.DEVICE_OWNER_FLOATINGIP]:
            return
        router_id = port['device_id']
        port_update = {
            'port': {
                'device_id': '',
                'device_owner': ''
            }
        }
        self.update_port(context, port['id'], port_update)
        routers = self.get_sync_data(context.elevated(), [router_id])
        msg = l3_rpc_agent_api.L3AgentNotify.make_msg("routers_updated",
                                                      routers=routers)
        l3_rpc_agent_api.L3AgentNotify.call(context, msg,
                                            topic=topics.L3_AGENT)

    def delete_port(self, context, id, l3_port_check=True):
        if l3_port_check:
            self.prevent_l3_port_deletion(context, id)
        try:
            self.disassociate_floatingips(context, id)
            port = self.get_port(context, id)
            if not port:
                return
            if not l3_port_check:
                self._release_l3agent_internal_port(context, port)
            pat = radcli.ADRGlobPattern({'name': port['network_id'],
                                         'tenant': port['tenant_id']})
            evs = self._rc.get_object(evsbind.EVS(), pat)
            evs.removeVPort(id)
        except radcli.ObjectError as oe:
            raise EVSControllerError(oe.get_payload().errmsg)

        # notify dhcp agent of port deletion
        payload = {
            'port': {
                'network_id': port['network_id'],
                'id': id,
            }
        }
        self.dhcp_agent_notifier.notify(context, payload, 'port.delete.end')

    # needed for DHCP agent support
    def update_fixed_ip_lease_expiration(self, context, network_id,
                                         ip_address, lease_remaining):
        pass