Refactor container VIF handling for linuxbridge
Neutron recently changed behaviour to complete all bridge setup and configuration for the linuxbridge driver via the neutron linuxbridge agent, requiring Nova simply to setup the tap device that subsequently gets plugged into a linuxbridge. Rework plug/unplug handling based on libvirt driver to fallback to a legacy style plug/unplug driver for tap devices of this kind. In the case of LXD we actually still use a veth pair so that: a) security group rules are applied to the tap named device on the host. b) the container still gets part of a nic to use, named ethX internally to the container. c) the neutron linuxbridge agent can see the required tapXXX device prior to the container being created, allowing VIF plugging to be completed. This looks something like this once wired and running: Host | Container [bridge] <-> [tapXXX|tinXXX] <-> [ethX] The veth pair is mapping into a LXD container using the physical LXD nic type. As the drive now creates the veth pair for unbridged network types, unplug must occur after the device has been removed from the container during interface_detach. Rework LXD device profile naming for consistency: a) VIFs attaching to bridges will be named inline with the bridge (no-change) b) VIFs not being attached to a bridge will be named with the VIF devname (changed from 'unbridged' which did not support any multiplicity). Change-Id: I2fdf41e5640f5ca5e3bcd7df1aa159a65b706138 Closes-Bug: 1694719
This commit is contained in:
parent
698f3da561
commit
16866d2cf8
|
@ -653,8 +653,8 @@ class LXDDriverTest(test.NoDBTestCase):
|
||||||
|
|
||||||
lxd_driver.attach_interface(ctx, instance, image_meta, vif)
|
lxd_driver.attach_interface(ctx, instance, image_meta, vif)
|
||||||
|
|
||||||
self.assertTrue('eth1' in profile.devices)
|
self.assertTrue('qbr0123456789a' in profile.devices)
|
||||||
self.assertEqual(expected, profile.devices['eth1'])
|
self.assertEqual(expected, profile.devices['qbr0123456789a'])
|
||||||
profile.save.assert_called_once_with(wait=True)
|
profile.save.assert_called_once_with(wait=True)
|
||||||
|
|
||||||
def test_detach_interface(self):
|
def test_detach_interface(self):
|
||||||
|
|
|
@ -33,6 +33,11 @@ VIF = network_model.VIF(
|
||||||
id='0123456789abcdef', address='ca:fe:de:ad:be:ef',
|
id='0123456789abcdef', address='ca:fe:de:ad:be:ef',
|
||||||
network=NETWORK, type=network_model.VIF_TYPE_OVS,
|
network=NETWORK, type=network_model.VIF_TYPE_OVS,
|
||||||
devname='tap-012-345-678', ovs_interfaceid='9abc-def-000')
|
devname='tap-012-345-678', ovs_interfaceid='9abc-def-000')
|
||||||
|
TAP_VIF = network_model.VIF(
|
||||||
|
id='0123456789abcdef', address='ca:fe:de:ad:be:ee',
|
||||||
|
network=NETWORK, type=network_model.VIF_TYPE_TAP,
|
||||||
|
devname='tap-014-345-678',
|
||||||
|
details={'mac_address': 'aa:bb:cc:dd:ee:ff'})
|
||||||
INSTANCE = fake_instance.fake_instance_obj(
|
INSTANCE = fake_instance.fake_instance_obj(
|
||||||
context.get_admin_context(), name='test')
|
context.get_admin_context(), name='test')
|
||||||
|
|
||||||
|
@ -140,7 +145,7 @@ class LXDGenericVifDriverTest(test.NoDBTestCase):
|
||||||
self.vif_driver = vif.LXDGenericVifDriver()
|
self.vif_driver = vif.LXDGenericVifDriver()
|
||||||
|
|
||||||
@mock.patch('nova.virt.lxd.vif.os_vif')
|
@mock.patch('nova.virt.lxd.vif.os_vif')
|
||||||
def test_plug(self, os_vif):
|
def test_plug_ovs(self, os_vif):
|
||||||
self.vif_driver.plug(INSTANCE, VIF)
|
self.vif_driver.plug(INSTANCE, VIF)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -149,10 +154,26 @@ class LXDGenericVifDriverTest(test.NoDBTestCase):
|
||||||
'instance-00000001', os_vif.plug.call_args[0][1].name)
|
'instance-00000001', os_vif.plug.call_args[0][1].name)
|
||||||
|
|
||||||
@mock.patch('nova.virt.lxd.vif.os_vif')
|
@mock.patch('nova.virt.lxd.vif.os_vif')
|
||||||
def test_unplug(self, os_vif):
|
def test_unplug_ovs(self, os_vif):
|
||||||
self.vif_driver.unplug(INSTANCE, VIF)
|
self.vif_driver.unplug(INSTANCE, VIF)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'tap-012-345-678', os_vif.unplug.call_args[0][0].vif_name)
|
'tap-012-345-678', os_vif.unplug.call_args[0][0].vif_name)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'instance-00000001', os_vif.unplug.call_args[0][1].name)
|
'instance-00000001', os_vif.unplug.call_args[0][1].name)
|
||||||
|
|
||||||
|
@mock.patch('nova.virt.lxd.vif.os_vif')
|
||||||
|
def test_plug_tap(self, os_vif):
|
||||||
|
with mock.patch.object(vif, '_create_veth_pair') as create_veth_pair:
|
||||||
|
self.vif_driver.plug(INSTANCE, TAP_VIF)
|
||||||
|
os_vif.plug.assert_not_called()
|
||||||
|
create_veth_pair.assert_called_with('tap-014-345-678',
|
||||||
|
'tin-014-345-678',
|
||||||
|
1000)
|
||||||
|
|
||||||
|
@mock.patch('nova.virt.lxd.vif.linux_net')
|
||||||
|
@mock.patch('nova.virt.lxd.vif.os_vif')
|
||||||
|
def test_unplug_tap(self, os_vif, linux_net):
|
||||||
|
self.vif_driver.unplug(INSTANCE, TAP_VIF)
|
||||||
|
os_vif.plug.assert_not_called()
|
||||||
|
linux_net.delete_net_dev.assert_called_with('tap-014-345-678')
|
||||||
|
|
|
@ -662,14 +662,12 @@ class LXDDriver(driver.ComputeDriver):
|
||||||
|
|
||||||
profile = self.client.profiles.get(instance.name)
|
profile = self.client.profiles.get(instance.name)
|
||||||
|
|
||||||
interfaces = []
|
|
||||||
for key, val in profile.devices.items():
|
|
||||||
if key.startswith('eth'):
|
|
||||||
interfaces.append(key)
|
|
||||||
net_device = 'eth{}'.format(len(interfaces))
|
|
||||||
|
|
||||||
network_config = lxd_vif.get_config(vif)
|
network_config = lxd_vif.get_config(vif)
|
||||||
|
|
||||||
|
# XXX(jamespage): Refactor into vif module so code is shared
|
||||||
|
# across hotplug and instance creation.
|
||||||
if 'bridge' in network_config:
|
if 'bridge' in network_config:
|
||||||
|
net_device = network_config['bridge']
|
||||||
config_update = {
|
config_update = {
|
||||||
net_device: {
|
net_device: {
|
||||||
'nictype': 'bridged',
|
'nictype': 'bridged',
|
||||||
|
@ -679,10 +677,12 @@ class LXDDriver(driver.ComputeDriver):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
net_device = lxd_vif.get_vif_devname(vif)
|
||||||
config_update = {
|
config_update = {
|
||||||
net_device: {
|
net_device: {
|
||||||
'nictype': 'p2p',
|
'nictype': 'physical',
|
||||||
'hwaddr': vif['address'],
|
'hwaddr': vif['address'],
|
||||||
|
'parent': lxd_vif.get_vif_internal_devname(vif),
|
||||||
'type': 'nic',
|
'type': 'nic',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -691,10 +691,11 @@ class LXDDriver(driver.ComputeDriver):
|
||||||
profile.save(wait=True)
|
profile.save(wait=True)
|
||||||
|
|
||||||
def detach_interface(self, context, instance, vif):
|
def detach_interface(self, context, instance, vif):
|
||||||
self.vif_driver.unplug(instance, vif)
|
|
||||||
|
|
||||||
profile = self.client.profiles.get(instance.name)
|
profile = self.client.profiles.get(instance.name)
|
||||||
to_remove = None
|
to_remove = None
|
||||||
|
# XXX(jamespage): refactor to use actual key
|
||||||
|
# which switch to consistent
|
||||||
|
# device naming in the profile.
|
||||||
for key, val in profile.devices.items():
|
for key, val in profile.devices.items():
|
||||||
if val.get('hwaddr') == vif['address']:
|
if val.get('hwaddr') == vif['address']:
|
||||||
to_remove = key
|
to_remove = key
|
||||||
|
@ -702,6 +703,8 @@ class LXDDriver(driver.ComputeDriver):
|
||||||
del profile.devices[to_remove]
|
del profile.devices[to_remove]
|
||||||
profile.save(wait=True)
|
profile.save(wait=True)
|
||||||
|
|
||||||
|
self.vif_driver.unplug(instance, vif)
|
||||||
|
|
||||||
def migrate_disk_and_power_off(
|
def migrate_disk_and_power_off(
|
||||||
self, context, instance, dest, _flavor, network_info,
|
self, context, instance, dest, _flavor, network_info,
|
||||||
block_device_info=None, timeout=0, retry_interval=0):
|
block_device_info=None, timeout=0, retry_interval=0):
|
||||||
|
|
|
@ -155,24 +155,24 @@ def _network(instance, _, network_info, __):
|
||||||
devices = {}
|
devices = {}
|
||||||
for vifaddr in network_info:
|
for vifaddr in network_info:
|
||||||
cfg = vif.get_config(vifaddr)
|
cfg = vif.get_config(vifaddr)
|
||||||
|
devname = vif.get_vif_devname(vifaddr)
|
||||||
if 'bridge' in cfg:
|
if 'bridge' in cfg:
|
||||||
key = str(cfg['bridge'])
|
key = bridge = str(cfg['bridge'])
|
||||||
devices[key] = {
|
devices[key] = {
|
||||||
'nictype': 'bridged',
|
'nictype': 'bridged',
|
||||||
'hwaddr': str(cfg['mac_address']),
|
'hwaddr': str(cfg['mac_address']),
|
||||||
'parent': str(cfg['bridge']),
|
'parent': bridge,
|
||||||
|
'host_name': devname,
|
||||||
'type': 'nic'
|
'type': 'nic'
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
key = 'unbridged'
|
key = devname
|
||||||
devices[key] = {
|
devices[key] = {
|
||||||
'nictype': 'p2p',
|
'nictype': 'physical',
|
||||||
'hwaddr': str(cfg['mac_address']),
|
'hwaddr': str(cfg['mac_address']),
|
||||||
|
'parent': vif.get_vif_internal_devname(vifaddr),
|
||||||
'type': 'nic'
|
'type': 'nic'
|
||||||
}
|
}
|
||||||
host_device = vif.get_vif_devname(vifaddr)
|
|
||||||
if host_device:
|
|
||||||
devices[key]['host_name'] = host_device
|
|
||||||
|
|
||||||
specs = instance.flavor.extra_specs
|
specs = instance.flavor.extra_specs
|
||||||
# Since LXD does not implement average NIC IO and number of burst
|
# Since LXD does not implement average NIC IO and number of burst
|
||||||
|
|
|
@ -11,13 +11,21 @@
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from nova import conf
|
from nova import conf
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
from nova import utils
|
||||||
|
from nova.network import linux_net
|
||||||
from nova.network import model as network_model
|
from nova.network import model as network_model
|
||||||
from nova.network import os_vif_util
|
from nova.network import os_vif_util
|
||||||
|
|
||||||
import os_vif
|
import os_vif
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_vif_devname(vif):
|
def get_vif_devname(vif):
|
||||||
"""Get device name for a given vif."""
|
"""Get device name for a given vif."""
|
||||||
|
@ -26,6 +34,25 @@ def get_vif_devname(vif):
|
||||||
return ("nic" + vif['id'])[:network_model.NIC_NAME_LEN]
|
return ("nic" + vif['id'])[:network_model.NIC_NAME_LEN]
|
||||||
|
|
||||||
|
|
||||||
|
def get_vif_internal_devname(vif):
|
||||||
|
"""Get the internal device name for a given vif."""
|
||||||
|
return get_vif_devname(vif).replace('tap', 'tin')
|
||||||
|
|
||||||
|
|
||||||
|
def _create_veth_pair(dev1_name, dev2_name, mtu=None):
|
||||||
|
"""Create a pair of veth devices with the specified names,
|
||||||
|
deleting any previous devices with those names.
|
||||||
|
"""
|
||||||
|
for dev in [dev1_name, dev2_name]:
|
||||||
|
linux_net.delete_net_dev(dev)
|
||||||
|
|
||||||
|
utils.execute('ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer',
|
||||||
|
'name', dev2_name, run_as_root=True)
|
||||||
|
for dev in [dev1_name, dev2_name]:
|
||||||
|
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
|
||||||
|
linux_net._set_device_mtu(dev, mtu)
|
||||||
|
|
||||||
|
|
||||||
def _get_bridge_config(vif):
|
def _get_bridge_config(vif):
|
||||||
return {
|
return {
|
||||||
'bridge': vif['network']['bridge'],
|
'bridge': vif['network']['bridge'],
|
||||||
|
@ -73,13 +100,57 @@ class LXDGenericVifDriver(object):
|
||||||
os_vif.initialize()
|
os_vif.initialize()
|
||||||
|
|
||||||
def plug(self, instance, vif):
|
def plug(self, instance, vif):
|
||||||
|
vif_type = vif['type']
|
||||||
instance_info = os_vif_util.nova_to_osvif_instance(instance)
|
instance_info = os_vif_util.nova_to_osvif_instance(instance)
|
||||||
vif_obj = os_vif_util.nova_to_osvif_vif(vif)
|
|
||||||
|
|
||||||
|
# Try os-vif codepath first
|
||||||
|
vif_obj = os_vif_util.nova_to_osvif_vif(vif)
|
||||||
|
if vif_obj is not None:
|
||||||
os_vif.plug(vif_obj, instance_info)
|
os_vif.plug(vif_obj, instance_info)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Legacy non-os-vif codepath
|
||||||
|
func = getattr(self, 'plug_%s' % vif_type, None)
|
||||||
|
if not func:
|
||||||
|
raise exception.InternalError(
|
||||||
|
"Unexpected vif_type=%s" % vif_type
|
||||||
|
)
|
||||||
|
func(instance, vif)
|
||||||
|
|
||||||
def unplug(self, instance, vif):
|
def unplug(self, instance, vif):
|
||||||
|
vif_type = vif['type']
|
||||||
instance_info = os_vif_util.nova_to_osvif_instance(instance)
|
instance_info = os_vif_util.nova_to_osvif_instance(instance)
|
||||||
vif_obj = os_vif_util.nova_to_osvif_vif(vif)
|
|
||||||
|
|
||||||
|
# Try os-vif codepath first
|
||||||
|
vif_obj = os_vif_util.nova_to_osvif_vif(vif)
|
||||||
|
if vif_obj is not None:
|
||||||
os_vif.unplug(vif_obj, instance_info)
|
os_vif.unplug(vif_obj, instance_info)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Legacy non-os-vif codepath
|
||||||
|
func = getattr(self, 'unplug_%s' % vif_type, None)
|
||||||
|
if not func:
|
||||||
|
raise exception.InternalError(
|
||||||
|
"Unexpected vif_type=%s" % vif_type
|
||||||
|
)
|
||||||
|
func(instance, vif)
|
||||||
|
|
||||||
|
def plug_tap(self, instance, vif):
|
||||||
|
"""Plug a VIF_TYPE_TAP virtual interface."""
|
||||||
|
dev1_name = get_vif_devname(vif)
|
||||||
|
dev2_name = dev1_name.replace('tap', 'tin')
|
||||||
|
network = vif.get('network')
|
||||||
|
mtu = network.get_meta('mtu') if network else None
|
||||||
|
# NOTE(jamespage): For nova-lxd this is really a veth pair
|
||||||
|
# so that a) security rules get applied on the host
|
||||||
|
# and b) that the container can still be wired.
|
||||||
|
_create_veth_pair(dev1_name, dev2_name, mtu)
|
||||||
|
|
||||||
|
def unplug_tap(self, instance, vif):
|
||||||
|
"""Unplug a VIF_TYPE_TAP virtual interface."""
|
||||||
|
dev = get_vif_devname(vif)
|
||||||
|
try:
|
||||||
|
linux_net.delete_net_dev(dev)
|
||||||
|
except processutils.ProcessExecutionError:
|
||||||
|
LOG.exception("Failed while unplugging vif",
|
||||||
|
instance=instance)
|
||||||
|
|
Loading…
Reference in New Issue