158 lines
6.2 KiB
Python
158 lines
6.2 KiB
Python
#
|
|
# 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 re
|
|
|
|
from oslo_concurrency import lockutils
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
|
|
from neutron._i18n import _LW
|
|
from neutron.agent.linux import ip_lib
|
|
from neutron.common import utils
|
|
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import utils as lutil
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class Plumber(object):
|
|
"""Object responsible for VLAN interface CRUD.
|
|
|
|
This handles the creation/deletion/listing of VLAN interfaces for
|
|
a trunk within a namespace.
|
|
"""
|
|
|
|
def __init__(self, namespace=None):
|
|
self.namespace = namespace
|
|
|
|
def trunk_on_host(self, trunk):
|
|
"""Returns true if trunk device is present else False."""
|
|
trunk_dev = self._trunk_device_name(trunk)
|
|
return ip_lib.device_exists(trunk_dev, namespace=self.namespace)
|
|
|
|
def ensure_trunk_subports(self, trunk):
|
|
"""Idempotent wiring for a trunk's subports.
|
|
|
|
Given a trunk object, delete any vlan subinterfaces belonging to a
|
|
trunk that aren't on the object. Create any which are on the object
|
|
which do not exist.
|
|
"""
|
|
trunk_dev = self._trunk_device_name(trunk)
|
|
with self._trunk_lock(trunk_dev):
|
|
# lock scoped to trunk device so two diffs don't interleave
|
|
expected = self._get_subport_devs_and_vlans(trunk.sub_ports)
|
|
existing = self._get_vlan_children(trunk_dev)
|
|
to_delete = existing - expected
|
|
to_create = expected - existing
|
|
for devname, vlan_id in to_delete:
|
|
LOG.debug("Deleting subport %(name)s with vlan tag %(tag)s",
|
|
dict(name=devname, tag=vlan_id))
|
|
self._safe_delete_device(devname)
|
|
for devname, vlan_id in to_create:
|
|
LOG.debug("Creating subport %(name)s with vlan tag %(tag)s",
|
|
dict(name=devname, tag=vlan_id))
|
|
self._create_vlan_subint(trunk_dev, devname, vlan_id)
|
|
|
|
def delete_trunk_subports(self, trunk):
|
|
return self.delete_subports_by_port_id(trunk.port_id)
|
|
|
|
def delete_subports_by_port_id(self, port_id):
|
|
device = self._get_tap_device_name(port_id)
|
|
if not ip_lib.device_exists(device, namespace=self.namespace):
|
|
LOG.debug("Device %s not present on this host", device)
|
|
return
|
|
with self._trunk_lock(device):
|
|
for subname, vlan_id in self._get_vlan_children(device):
|
|
LOG.debug("Deleting subport %(name)s with vlan tag %(tag)s",
|
|
dict(name=subname, tag=vlan_id))
|
|
self._safe_delete_device(subname)
|
|
|
|
def _trunk_lock(self, trunk_dev):
|
|
lock_name = 'trunk-%s' % trunk_dev
|
|
return lockutils.lock(lock_name, utils.SYNCHRONIZED_PREFIX)
|
|
|
|
def _create_vlan_subint(self, trunk_name, devname, vlan_id):
|
|
ip_wrap = ip_lib.IPWrapper(namespace=self.namespace)
|
|
try:
|
|
dev = ip_wrap.add_vlan(devname, trunk_name, vlan_id)
|
|
dev.disable_ipv6()
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception() as ectx:
|
|
ectx.reraise = ip_lib.IPDevice(
|
|
devname, namespace=self.namespace).exists()
|
|
|
|
def _safe_delete_device(self, devname):
|
|
dev = ip_lib.IPDevice(devname, namespace=self.namespace)
|
|
try:
|
|
dev.link.set_down()
|
|
dev.link.delete()
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception() as ectx:
|
|
ectx.reraise = dev.exists()
|
|
|
|
def _trunk_device_name(self, trunk):
|
|
return self._get_tap_device_name(trunk.port_id)
|
|
|
|
def _get_subport_devs_and_vlans(self, subports):
|
|
return {(self._get_tap_device_name(s.port_id),
|
|
s.segmentation_id)
|
|
for s in subports}
|
|
|
|
def _get_tap_device_name(self, devname):
|
|
return lutil.get_tap_device_name(devname)
|
|
|
|
def _get_vlan_children(self, dev):
|
|
"""Return set of (devname, vlan_id) tuples for children of device."""
|
|
# TODO(kevinbenton): move into ip-lib after privsep stuff settles
|
|
ip_wrapper = ip_lib.IPWrapper(namespace=self.namespace)
|
|
output = ip_wrapper.netns.execute(["ip", "-d", "link", "list"],
|
|
check_exit_code=True)
|
|
return {(i.devname, i.vlan_tag)
|
|
for i in _iter_output_by_interface(output)
|
|
if i.parent_devname == dev}
|
|
|
|
|
|
def _iter_output_by_interface(output):
|
|
interface = []
|
|
for line in output.splitlines():
|
|
if not line.startswith(' '):
|
|
# no space indicates new interface info
|
|
interface_str = ' '.join(interface)
|
|
if interface_str.strip():
|
|
yield _InterfaceInfo(interface_str)
|
|
interface = []
|
|
interface.append(line)
|
|
if interface:
|
|
yield _InterfaceInfo(' '.join(interface))
|
|
|
|
|
|
class _InterfaceInfo(object):
|
|
def __init__(self, line):
|
|
try:
|
|
name_section = line.split(': ')[1]
|
|
except IndexError:
|
|
name_section = None
|
|
LOG.warning(_LW("Bad interface line: %s"), line)
|
|
if not name_section or '@' not in name_section:
|
|
self.devname = name_section
|
|
self.parent_devname = self.vlan_tag = None
|
|
else:
|
|
self.devname, parent = name_section.split('@')
|
|
m = re.match(r'.*802\.1Q id (\d+).*', line)
|
|
self.vlan_tag = int(m.group(1)) if m else None
|
|
# we only care about parent interfaces if it's a vlan sub-interface
|
|
self.parent_devname = parent if self.vlan_tag is not None else None
|
|
|
|
def __repr__(self):
|
|
return ('_InterfaceInfo(devname=%s, parent=%s, vlan=%s)' %
|
|
(self.devname, self.parent_devname, self.vlan_tag))
|