
Fixes bug 1048108 The changeset also removed the namespace garbage collection from the port unplug. This attempts to delete the namespace. This is problematic as there may be additional attributes to the namespace that need to be dealt with. Change-Id: I418a1ec4e9b65e5bea67ae84414019c3d6b54214
277 lines
10 KiB
Python
277 lines
10 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 OpenStack LLC
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import abc
|
|
import logging
|
|
|
|
import netaddr
|
|
|
|
from quantum.agent.linux import ip_lib
|
|
from quantum.agent.linux import ovs_lib
|
|
from quantum.agent.linux import utils
|
|
from quantum.common import exceptions
|
|
from quantum.extensions.flavor import (FLAVOR_NETWORK)
|
|
from quantum.openstack.common import cfg
|
|
from quantum.openstack.common import importutils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
OPTS = [
|
|
cfg.StrOpt('ovs_integration_bridge',
|
|
default='br-int',
|
|
help='Name of Open vSwitch bridge to use'),
|
|
cfg.StrOpt('network_device_mtu',
|
|
help='MTU setting for device.'),
|
|
cfg.StrOpt('ryu_api_host',
|
|
default='127.0.0.1:8080',
|
|
help='Openflow Ryu REST API host:port'),
|
|
cfg.StrOpt('meta_flavor_driver_mappings',
|
|
help='Mapping between flavor and LinuxInterfaceDriver')
|
|
]
|
|
|
|
|
|
class LinuxInterfaceDriver(object):
|
|
__metaclass__ = abc.ABCMeta
|
|
|
|
# from linux IF_NAMESIZE
|
|
DEV_NAME_LEN = 14
|
|
DEV_NAME_PREFIX = 'tap'
|
|
|
|
def __init__(self, conf):
|
|
self.conf = conf
|
|
|
|
def init_l3(self, device_name, ip_cidrs, namespace=None):
|
|
"""Set the L3 settings for the interface using data from the port.
|
|
ip_cidrs: list of 'X.X.X.X/YY' strings
|
|
"""
|
|
device = ip_lib.IPDevice(device_name, self.conf.root_helper,
|
|
namespace=namespace)
|
|
|
|
previous = {}
|
|
for address in device.addr.list(scope='global', filters=['permanent']):
|
|
previous[address['cidr']] = address['ip_version']
|
|
|
|
# add new addresses
|
|
for ip_cidr in ip_cidrs:
|
|
|
|
net = netaddr.IPNetwork(ip_cidr)
|
|
if ip_cidr in previous:
|
|
del previous[ip_cidr]
|
|
continue
|
|
|
|
device.addr.add(net.version, ip_cidr, str(net.broadcast))
|
|
|
|
# clean up any old addresses
|
|
for ip_cidr, ip_version in previous.items():
|
|
device.addr.delete(ip_version, ip_cidr)
|
|
|
|
def check_bridge_exists(self, bridge):
|
|
if not ip_lib.device_exists(bridge):
|
|
raise exceptions.BridgeDoesNotExist(bridge=bridge)
|
|
|
|
def get_device_name(self, port):
|
|
return (self.DEV_NAME_PREFIX + port.id)[:self.DEV_NAME_LEN]
|
|
|
|
@abc.abstractmethod
|
|
def plug(self, network_id, port_id, device_name, mac_address,
|
|
bridge=None, namespace=None, prefix=None):
|
|
"""Plug in the interface."""
|
|
|
|
@abc.abstractmethod
|
|
def unplug(self, device_name, bridge=None, namespace=None):
|
|
"""Unplug the interface."""
|
|
|
|
|
|
class NullDriver(LinuxInterfaceDriver):
|
|
def plug(self, network_id, port_id, device_name, mac_address,
|
|
bridge=None, namespace=None, prefix=None):
|
|
pass
|
|
|
|
def unplug(self, device_name, bridge=None, namespace=None):
|
|
pass
|
|
|
|
|
|
class OVSInterfaceDriver(LinuxInterfaceDriver):
|
|
"""Driver for creating an internal interface on an OVS bridge."""
|
|
|
|
def plug(self, network_id, port_id, device_name, mac_address,
|
|
bridge=None, namespace=None, prefix=None):
|
|
"""Plug in the interface."""
|
|
if not bridge:
|
|
bridge = self.conf.ovs_integration_bridge
|
|
|
|
self.check_bridge_exists(bridge)
|
|
|
|
if not ip_lib.device_exists(device_name,
|
|
self.conf.root_helper,
|
|
namespace=namespace):
|
|
|
|
utils.execute(['ovs-vsctl',
|
|
'--', '--may-exist', 'add-port', bridge,
|
|
device_name,
|
|
'--', 'set', 'Interface', device_name,
|
|
'type=internal',
|
|
'--', 'set', 'Interface', device_name,
|
|
'external-ids:iface-id=%s' % port_id,
|
|
'--', 'set', 'Interface', device_name,
|
|
'external-ids:iface-status=active',
|
|
'--', 'set', 'Interface', device_name,
|
|
'external-ids:attached-mac=%s' %
|
|
mac_address],
|
|
self.conf.root_helper)
|
|
|
|
ip = ip_lib.IPWrapper(self.conf.root_helper)
|
|
device = ip.device(device_name)
|
|
device.link.set_address(mac_address)
|
|
if self.conf.network_device_mtu:
|
|
device.link.set_mtu(self.conf.network_device_mtu)
|
|
|
|
if namespace:
|
|
namespace_obj = ip.ensure_namespace(namespace)
|
|
namespace_obj.add_device_to_namespace(device)
|
|
device.link.set_up()
|
|
|
|
def unplug(self, device_name, bridge=None, namespace=None):
|
|
"""Unplug the interface."""
|
|
if not bridge:
|
|
bridge = self.conf.ovs_integration_bridge
|
|
|
|
self.check_bridge_exists(bridge)
|
|
bridge = ovs_lib.OVSBridge(bridge, self.conf.root_helper)
|
|
bridge.delete_port(device_name)
|
|
|
|
|
|
class BridgeInterfaceDriver(LinuxInterfaceDriver):
|
|
"""Driver for creating bridge interfaces."""
|
|
|
|
DEV_NAME_PREFIX = 'ns-'
|
|
|
|
def plug(self, network_id, port_id, device_name, mac_address,
|
|
bridge=None, namespace=None, prefix=None):
|
|
"""Plugin the interface."""
|
|
if not ip_lib.device_exists(device_name,
|
|
self.conf.root_helper,
|
|
namespace=namespace):
|
|
ip = ip_lib.IPWrapper(self.conf.root_helper)
|
|
|
|
# Enable agent to define the prefix
|
|
if prefix:
|
|
tap_name = device_name.replace(prefix, 'tap')
|
|
else:
|
|
tap_name = device_name.replace(self.DEV_NAME_PREFIX, 'tap')
|
|
root_veth, dhcp_veth = ip.add_veth(tap_name, device_name)
|
|
root_veth.link.set_address(mac_address)
|
|
|
|
if namespace:
|
|
namespace_obj = ip.ensure_namespace(namespace)
|
|
namespace_obj.add_device_to_namespace(dhcp_veth)
|
|
|
|
root_veth.link.set_up()
|
|
dhcp_veth.link.set_up()
|
|
|
|
else:
|
|
LOG.warn(_("Device %s already exists") % device_name)
|
|
|
|
def unplug(self, device_name, bridge=None, namespace=None):
|
|
"""Unplug the interface."""
|
|
device = ip_lib.IPDevice(device_name, self.conf.root_helper, namespace)
|
|
try:
|
|
device.link.delete()
|
|
LOG.debug(_("Unplugged interface '%s'") % device_name)
|
|
except RuntimeError:
|
|
LOG.error(_("Failed unplugging interface '%s'") %
|
|
device_name)
|
|
|
|
|
|
class RyuInterfaceDriver(OVSInterfaceDriver):
|
|
"""Driver for creating a Ryu OVS interface."""
|
|
|
|
def __init__(self, conf):
|
|
super(RyuInterfaceDriver, self).__init__(conf)
|
|
|
|
from ryu.app.client import OFPClient
|
|
LOG.debug('ryu rest host %s', self.conf.ryu_api_host)
|
|
self.ryu_client = OFPClient(self.conf.ryu_api_host)
|
|
|
|
def plug(self, network_id, port_id, device_name, mac_address,
|
|
bridge=None, namespace=None, prefix=None):
|
|
"""Plug in the interface."""
|
|
super(RyuInterfaceDriver, self).plug(network_id, port_id, device_name,
|
|
mac_address, bridge=bridge,
|
|
namespace=namespace,
|
|
prefix=prefix)
|
|
if not bridge:
|
|
bridge = self.conf.ovs_integration_bridge
|
|
|
|
self.check_bridge_exists(bridge)
|
|
ovs_br = ovs_lib.OVSBridge(bridge, self.conf.root_helper)
|
|
datapath_id = ovs_br.get_datapath_id()
|
|
port_no = ovs_br.get_port_ofport(device_name)
|
|
self.ryu_client.create_port(network_id, datapath_id, port_no)
|
|
|
|
|
|
class MetaInterfaceDriver(LinuxInterfaceDriver):
|
|
def __init__(self, conf):
|
|
super(MetaInterfaceDriver, self).__init__(conf)
|
|
from quantumclient.v2_0 import client
|
|
self.quantum = client.Client(
|
|
username=self.conf.admin_user,
|
|
password=self.conf.admin_password,
|
|
tenant_name=self.conf.admin_tenant_name,
|
|
auth_url=self.conf.auth_url,
|
|
auth_strategy=self.conf.auth_strategy,
|
|
auth_region=self.conf.auth_region
|
|
)
|
|
self.flavor_driver_map = {}
|
|
for flavor, driver_name in [
|
|
driver_set.split(':')
|
|
for driver_set in
|
|
self.conf.meta_flavor_driver_mappings.split(',')]:
|
|
self.flavor_driver_map[flavor] = self._load_driver(driver_name)
|
|
|
|
def _get_driver_by_network_id(self, network_id):
|
|
network = self.quantum.show_network(network_id)
|
|
flavor = network['network'][FLAVOR_NETWORK]
|
|
return self.flavor_driver_map[flavor]
|
|
|
|
def _get_driver_by_device_name(self, device_name, namespace=None):
|
|
device = ip_lib.IPDevice(device_name, self.conf.root_helper, namespace)
|
|
mac_address = device.link.address
|
|
ports = self.quantum.list_ports(mac_address=mac_address)
|
|
if not 'ports' in ports or len(ports['ports']) < 1:
|
|
raise Exception('No port for this device %s' % device_name)
|
|
return self._get_driver_by_network_id(ports['ports'][0]['network_id'])
|
|
|
|
def get_device_name(self, port):
|
|
driver = self._get_driver_by_network_id(port.network_id)
|
|
return driver.get_device_name(port)
|
|
|
|
def plug(self, network_id, port_id, device_name, mac_address,
|
|
bridge=None, namespace=None, prefix=None):
|
|
driver = self._get_driver_by_network_id(network_id)
|
|
return driver.plug(network_id, port_id, device_name, mac_address,
|
|
bridge=bridge, namespace=namespace, prefix=prefix)
|
|
|
|
def unplug(self, device_name, bridge=None, namespace=None):
|
|
driver = self._get_driver_by_device_name(device_name, namespace=None)
|
|
return driver.unplug(device_name, bridge, namespace)
|
|
|
|
def _load_driver(self, driver_provider):
|
|
LOG.debug("Driver location:%s", driver_provider)
|
|
plugin_klass = importutils.import_class(driver_provider)
|
|
return plugin_klass(self.conf)
|