zun/nova/virt/docker/vifs.py
Hongbin Lu e41c54b175 Refactor VIF plugging and attaching code
This is the first step to offload VIF plugging code to os-vif
library. In particular, this commit moved the veth pair creation
from plug_bridge/plug_ovs_hybrid to attach. This makes more sense
because the veth pair is created for attach the VIF to container.

Partial-Implements: blueprint switch-to-os-vif
Change-Id: Ieaeeeac04a0f92207c6235933ca1040da6da3ffe
2016-12-13 16:51:59 -06:00

466 lines
19 KiB
Python

# Copyright (C) 2013 VMware, Inc
# Copyright 2011 OpenStack Foundation
# 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.
from oslo_concurrency import processutils
from oslo_log import log as logging
from nova import exception
from nova.i18n import _
from nova.network import linux_net
from nova.network import manager
from nova.network import model as network_model
from nova import utils
from nova.virt.docker import network
from oslo_config import cfg
import random
# We need config opts from manager, but pep8 complains, this silences it.
assert manager
CONF = cfg.CONF
CONF.import_opt('vlan_interface', 'nova.manager')
CONF.import_opt('flat_interface', 'nova.manager')
LOG = logging.getLogger(__name__)
class DockerGenericVIFDriver(object):
def plug(self, instance, vif):
vif_type = vif['type']
LOG.debug('plug vif_type=%(vif_type)s instance=%(instance)s '
'vif=%(vif)s',
{'vif_type': vif_type, 'instance': instance,
'vif': vif})
if vif_type is None:
raise exception.NovaException(
_("vif_type parameter must be present "
"for this vif_driver implementation"))
if vif_type == network_model.VIF_TYPE_BRIDGE:
self.plug_bridge(instance, vif)
elif vif_type == network_model.VIF_TYPE_OVS:
if self.ovs_hybrid_required(vif):
self.plug_ovs_hybrid(instance, vif)
else:
self.plug_ovs(instance, vif)
elif vif_type == network_model.VIF_TYPE_MIDONET:
self.plug_midonet(instance, vif)
elif vif_type == network_model.VIF_TYPE_IOVISOR:
self.plug_iovisor(instance, vif)
else:
raise exception.NovaException(
_("Unexpected vif_type=%s") % vif_type)
def plug_iovisor(self, instance, vif):
"""Plug docker vif into IOvisor
Creates a port on IOvisor and onboards the interface
"""
if_local_name = 'tap%s' % vif['id'][:11]
if_remote_name = 'ns%s' % vif['id'][:11]
iface_id = vif['id']
net_id = vif['network']['id']
tenant_id = instance['project_id']
# Device already exists so return.
if linux_net.device_exists(if_local_name):
return
undo_mgr = utils.UndoManager()
try:
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
'veth', 'peer', 'name', if_remote_name,
run_as_root=True)
utils.execute('ifc_ctl', 'gateway', 'add_port', if_local_name,
run_as_root=True)
utils.execute('ifc_ctl', 'gateway', 'ifup', if_local_name,
'access_vm',
vif['network']['label'] + "_" + iface_id,
vif['address'], 'pgtag2=%s' % net_id,
'pgtag1=%s' % tenant_id, run_as_root=True)
utils.execute('ip', 'link', 'set', if_local_name, 'up',
run_as_root=True)
except Exception:
LOG.exception("Failed to configure network on IOvisor")
msg = _('Failed to setup the network, rolling back')
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
def plug_ovs(self, instance, vif):
if_local_name = 'tap%s' % vif['id'][:11]
if_remote_name = 'ns%s' % vif['id'][:11]
bridge = vif['network']['bridge']
# Device already exists so return.
if linux_net.device_exists(if_local_name):
return
undo_mgr = utils.UndoManager()
try:
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
'veth', 'peer', 'name', if_remote_name,
run_as_root=True)
linux_net.create_ovs_vif_port(bridge, if_local_name,
network.get_ovs_interfaceid(vif),
vif['address'],
instance['uuid'])
utils.execute('ip', 'link', 'set', if_local_name, 'up',
run_as_root=True)
except Exception:
LOG.exception("Failed to configure network")
msg = _('Failed to setup the network, rolling back')
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
def plug_midonet(self, instance, vif):
"""Plug into MidoNet's network port
This accomplishes binding of the vif to a MidoNet virtual port
"""
if_local_name = 'tap%s' % vif['id'][:11]
if_remote_name = 'ns%s' % vif['id'][:11]
port_id = network.get_ovs_interfaceid(vif)
# Device already exists so return.
if linux_net.device_exists(if_local_name):
return
undo_mgr = utils.UndoManager()
try:
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
'veth', 'peer', 'name', if_remote_name,
run_as_root=True)
undo_mgr.undo_with(lambda: utils.execute(
'ip', 'link', 'delete', if_local_name, run_as_root=True))
utils.execute('ip', 'link', 'set', if_local_name, 'up',
run_as_root=True)
utils.execute('mm-ctl', '--bind-port', port_id, if_local_name,
run_as_root=True)
except Exception:
LOG.exception("Failed to configure network")
msg = _('Failed to setup the network, rolling back')
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
def plug_ovs_hybrid(self, instance, vif):
"""Plug using hybrid strategy
Create a per-VIF linux bridge, then link that bridge to the OVS
integration bridge via a veth device, setting up the other end
of the veth device just like a normal OVS port. Then boot the
VIF on the linux bridge. and connect the tap port to linux bridge
"""
iface_id = self.get_ovs_interfaceid(vif)
br_name = self.get_br_name(vif['id'])
v1_name, v2_name = self.get_veth_pair_names(vif['id'])
undo_mgr = utils.UndoManager()
try:
if not linux_net.device_exists(br_name):
utils.execute('brctl', 'addbr', br_name, run_as_root=True)
# Incase of failure undo the Steps
# Deleting/Undoing the interface will delete all
# associated resources
undo_mgr.undo_with(lambda: utils.execute(
'brctl', 'delbr', br_name, run_as_root=True))
# LOG.exception('Throw Test exception with bridgename %s'
# % br_name)
utils.execute('brctl', 'setfd', br_name, 0, run_as_root=True)
utils.execute('brctl', 'stp', br_name, 'off', run_as_root=True)
utils.execute('tee',
('/sys/class/net/%s/bridge/multicast_snooping' %
br_name),
process_input='0',
run_as_root=True,
check_exit_code=[0, 1])
if not linux_net.device_exists(v2_name):
linux_net._create_veth_pair(v1_name, v2_name)
undo_mgr.undo_with(lambda: utils.execute(
'ip', 'link', 'delete', v1_name, run_as_root=True))
utils.execute('ip', 'link', 'set', br_name, 'up',
run_as_root=True)
undo_mgr.undo_with(lambda: utils.execute('ip', 'link', 'set',
br_name, 'down',
run_as_root=True))
# Deleting/Undoing the interface will delete all
# associated resources (remove from the bridge, its
# pair, etc...)
utils.execute('brctl', 'addif', br_name, v1_name,
run_as_root=True)
linux_net.create_ovs_vif_port(self.get_bridge_name(vif),
v2_name,
iface_id, vif['address'],
instance['uuid'])
undo_mgr.undo_with(
lambda: utils.execute('ovs-vsctl', 'del-port',
self.get_bridge_name(vif),
v2_name, run_as_root=True))
except Exception:
msg = "Failed to configure Network." \
" Rolling back the network interfaces %s %s %s " % (
br_name, v1_name, v2_name)
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
# We are creating our own mac's now because the linux bridge interface
# takes on the lowest mac that is assigned to it. By using FE range
# mac's we prevent the interruption and possible loss of networking
# from changing mac addresses.
def _fe_random_mac(self):
mac = [0xfe, 0xed,
random.randint(0x00, 0xff),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac))
def plug_bridge(self, instance, vif):
bridge = vif['network']['bridge']
gateway = network.find_gateway(instance, vif['network'])
net = vif['network']
if net.get_meta('should_create_vlan', False):
vlan = net.get_meta('vlan'),
iface = (CONF.vlan_interface or
vif['network'].get_meta('bridge_interface'))
linux_net.LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
vlan,
bridge,
iface,
net_attrs=vif,
mtu=vif.get('mtu'))
iface = 'vlan%s' % vlan
else:
iface = (CONF.flat_interface or
vif['network'].get_meta('bridge_interface'))
LOG.debug('Ensuring bridge for %s - %s' % (iface, bridge))
linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(
bridge,
iface,
net_attrs=vif,
gateway=gateway)
def unplug(self, instance, vif):
vif_type = vif['type']
LOG.debug('vif_type=%(vif_type)s instance=%(instance)s '
'vif=%(vif)s',
{'vif_type': vif_type, 'instance': instance,
'vif': vif})
if vif_type is None:
raise exception.NovaException(
_("vif_type parameter must be present "
"for this vif_driver implementation"))
if vif_type == network_model.VIF_TYPE_BRIDGE:
self.unplug_bridge(instance, vif)
elif vif_type == network_model.VIF_TYPE_OVS:
if self.ovs_hybrid_required(vif):
self.unplug_ovs_hybrid(instance, vif)
else:
self.unplug_ovs(instance, vif)
elif vif_type == network_model.VIF_TYPE_MIDONET:
self.unplug_midonet(instance, vif)
elif vif_type == network_model.VIF_TYPE_IOVISOR:
self.unplug_iovisor(instance, vif)
else:
raise exception.NovaException(
_("Unexpected vif_type=%s") % vif_type)
def unplug_iovisor(self, instance, vif):
"""Unplug vif from IOvisor
Offboard an interface and deletes port from IOvisor
"""
if_local_name = 'tap%s' % vif['id'][:11]
iface_id = vif['id']
try:
utils.execute('ifc_ctl', 'gateway', 'ifdown',
if_local_name, 'access_vm',
vif['network']['label'] + "_" + iface_id,
vif['address'], run_as_root=True)
utils.execute('ifc_ctl', 'gateway', 'del_port', if_local_name,
run_as_root=True)
linux_net.delete_net_dev(if_local_name)
except processutils.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
def unplug_ovs(self, instance, vif):
"""Unplug the VIF by deleting the port from the bridge."""
try:
linux_net.delete_ovs_vif_port(vif['network']['bridge'],
vif['devname'])
except processutils.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
def unplug_midonet(self, instance, vif):
"""Unplug into MidoNet's network port
This accomplishes unbinding of the vif from its MidoNet virtual port
"""
try:
utils.execute('mm-ctl', '--unbind-port',
network.get_ovs_interfaceid(vif), run_as_root=True)
except processutils.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
def unplug_ovs_hybrid(self, instance, vif):
"""UnPlug using hybrid strategy
Unhook port from OVS, unhook port from bridge, delete
bridge, and delete both veth devices.
"""
try:
br_name = self.get_br_name(vif['id'])
v1_name, v2_name = self.get_veth_pair_names(vif['id'])
if linux_net.device_exists(br_name):
utils.execute('brctl', 'delif', br_name, v1_name,
run_as_root=True)
utils.execute('ip', 'link', 'set', br_name, 'down',
run_as_root=True)
utils.execute('brctl', 'delbr', br_name,
run_as_root=True)
linux_net.delete_ovs_vif_port(self.get_bridge_name(vif),
v2_name)
except processutils.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
def unplug_bridge(self, instance, vif):
# NOTE(arosen): nothing has to be done in the linuxbridge case
# as when the veth is deleted it automatically is removed from
# the bridge.
pass
def _create_veth_pair(self, if_local_name, if_remote_name, bridge):
undo_mgr = utils.UndoManager()
try:
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
'veth', 'peer', 'name', if_remote_name,
run_as_root=True)
undo_mgr.undo_with(lambda: utils.execute(
'ip', 'link', 'delete', if_local_name, run_as_root=True))
# NOTE(samalba): Deleting the interface will delete all
# associated resources (remove from the bridge, its pair, etc...)
utils.execute('ip', 'link', 'set', if_local_name, 'address',
self._fe_random_mac(), run_as_root=True)
utils.execute('brctl', 'addif', bridge, if_local_name,
run_as_root=True)
utils.execute('ip', 'link', 'set', if_local_name, 'up',
run_as_root=True)
except Exception:
LOG.exception("Failed to configure network")
msg = _('Failed to setup the network, rolling back')
undo_mgr.rollback_and_reraise(msg=msg)
def attach(self, instance, vif, container_id):
vif_type = vif['type']
if_local_name = 'tap%s' % vif['id'][:11]
if_remote_name = 'ns%s' % vif['id'][:11]
gateways = network.find_gateways(instance, vif['network'])
ips = network.find_fixed_ips(instance, vif['network'])
LOG.debug('attach vif_type=%(vif_type)s instance=%(instance)s '
'vif=%(vif)s',
{'vif_type': vif_type, 'instance': instance,
'vif': vif})
if not linux_net.device_exists(if_local_name):
br_name = vif['network']['bridge']
if (vif_type == network_model.VIF_TYPE_OVS
and self.ovs_hybrid_required(vif)):
br_name = self.get_br_name(vif['id'])
self._create_veth_pair(if_local_name, if_remote_name, br_name)
try:
utils.execute('ip', 'link', 'set', if_remote_name, 'netns',
container_id, run_as_root=True)
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'link',
'set', if_remote_name, 'address', vif['address'],
run_as_root=True)
for ip in ips:
utils.execute('ip', 'netns', 'exec', container_id, 'ip',
'addr', 'add', ip, 'dev', if_remote_name,
run_as_root=True)
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'link',
'set', if_remote_name, 'up', run_as_root=True)
# Setup MTU on if_remote_name is required if it is a non
# default value
mtu = None
if vif.get('mtu') is not None:
mtu = vif.get('mtu')
if mtu is not None:
utils.execute('ip', 'netns', 'exec', container_id, 'ip',
'link', 'set', if_remote_name, 'mtu', mtu,
run_as_root=True)
for gateway in gateways:
utils.execute('ip', 'netns', 'exec', container_id,
'ip', 'route', 'replace', 'default', 'via',
gateway, 'dev', if_remote_name, run_as_root=True)
# Disable TSO, for now no config option
utils.execute('ip', 'netns', 'exec', container_id, 'ethtool',
'--offload', if_remote_name, 'tso', 'off',
run_as_root=True)
except Exception:
LOG.exception("Failed to attach vif")
def get_bridge_name(self, vif):
return vif['network']['bridge']
def get_ovs_interfaceid(self, vif):
return vif.get('ovs_interfaceid') or vif['id']
def get_br_name(self, iface_id):
return ("qbr" + iface_id)[:network_model.NIC_NAME_LEN]
def get_veth_pair_names(self, iface_id):
return (("qvb%s" % iface_id)[:network_model.NIC_NAME_LEN],
("qvo%s" % iface_id)[:network_model.NIC_NAME_LEN])
def ovs_hybrid_required(self, vif):
ovs_hybrid_required = self.get_firewall_required(vif) or \
self.get_hybrid_plug_enabled(vif)
return ovs_hybrid_required
def get_firewall_required(self, vif):
if vif.get('details'):
enabled = vif['details'].get('port_filter', False)
if enabled:
return False
if CONF.firewall_driver != "nova.virt.firewall.NoopFirewallDriver":
return True
return False
def get_hybrid_plug_enabled(self, vif):
if vif.get('details'):
return vif['details'].get('ovs_hybrid_plug', False)
return False