This change set adds a new vif driver. This new vif driver is to be used in conjunction with the ovs_neutron_agent. Hybrid vif plugging is not supported at this time. This change set assumes that an OVS is running local to the nova-compute service. Change-Id: I54d3d4644de55ba4adf43edf034731c4879eaba6
287 lines
11 KiB
Python
287 lines
11 KiB
Python
# Copyright 2016 IBM Corp.
|
|
#
|
|
# 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 netifaces
|
|
import six
|
|
|
|
from nova import exception
|
|
from nova.network import linux_net
|
|
from nova import utils
|
|
from oslo_config import cfg
|
|
from oslo_utils import importutils
|
|
from pypowervm.tasks import cna as pvm_cna
|
|
from pypowervm.wrappers import managed_system as pvm_ms
|
|
from pypowervm.wrappers import network as pvm_net
|
|
|
|
from nova_powervm.virt.powervm.i18n import _
|
|
from nova_powervm.virt.powervm.i18n import _LE
|
|
from nova_powervm.virt.powervm.i18n import _LI
|
|
from nova_powervm.virt.powervm.i18n import _LW
|
|
from nova_powervm.virt.powervm import mgmt
|
|
from nova_powervm.virt.powervm import vm
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
SECURE_RMC_VSWITCH = 'MGMTSWITCH'
|
|
SECURE_RMC_VLAN = 4094
|
|
|
|
VIF_MAPPING = {'pvm_sea': 'nova_powervm.virt.powervm.vif.PvmSeaVifDriver',
|
|
'ovs': 'nova_powervm.virt.powervm.vif.PvmOvsVifDriver'}
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class VirtualInterfaceUnplugException(exception.NovaException):
|
|
"""Indicates that a VIF unplug failed."""
|
|
# TODO(thorst) symmetrical to the exception in base Nova. Evaluate
|
|
# moving to Nova core.
|
|
msg_fmt = _("Virtual interface unplug failed")
|
|
|
|
|
|
def _build_vif_driver(adapter, host_uuid, instance, vif):
|
|
"""Returns the appropriate VIF Driver for the given VIF.
|
|
|
|
:param adapter: The pypowervm adapter API interface.
|
|
:param host_uuid: The host system UUID.
|
|
:param instance: The nova instance.
|
|
:param vif: The virtual interface to from Nova.
|
|
:return: The appropriate PvmVifDriver for the VIF.
|
|
"""
|
|
if vif.get('type') is None:
|
|
raise exception.VirtualInterfacePlugException(
|
|
_("vif_type parameter must be present for this vif_driver "
|
|
"implementation"))
|
|
|
|
# Check the type to the implementations
|
|
if VIF_MAPPING.get(vif['type']):
|
|
return importutils.import_object(
|
|
VIF_MAPPING.get(vif['type']), adapter, host_uuid, instance)
|
|
|
|
# No matching implementation, raise error.
|
|
raise exception.VirtualInterfacePlugException(
|
|
_("Unable to find appropriate PowerVM VIF Driver for VIF type "
|
|
"%(vif_type)s on instance %(instance)s") %
|
|
{'vif_type': vif['type'], 'instance': instance.name})
|
|
|
|
|
|
def plug(adapter, host_uuid, instance, vif):
|
|
"""Plugs a virtual interface (network) into a VM.
|
|
|
|
:param adapter: The pypowervm adapter.
|
|
:param host_uuid: The host UUID for the PowerVM API.
|
|
:param instance: The nova instance object.
|
|
:param vif: The virtual interface to plug into the instance.
|
|
"""
|
|
vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
|
|
vif_drv.plug(vif)
|
|
|
|
|
|
def unplug(adapter, host_uuid, instance, vif, cna_w_list=None):
|
|
"""Unplugs a virtual interface (network) from a VM.
|
|
|
|
:param adapter: The pypowervm adapter.
|
|
:param host_uuid: The host UUID for the PowerVM API.
|
|
:param instance: The nova instance object.
|
|
:param vif: The virtual interface to plug into the instance.
|
|
:param cna_w_list: (Optional, Default: None) The list of Client Network
|
|
Adapters from pypowervm. Providing this input
|
|
allows for an improvement in operation speed.
|
|
"""
|
|
vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
|
|
vif_drv.unplug(vif, cna_w_list=cna_w_list)
|
|
|
|
|
|
def get_secure_rmc_vswitch(adapter, host_uuid):
|
|
"""Returns the vSwitch that is used for secure RMC.
|
|
|
|
:param adapter: The pypowervm adapter API interface.
|
|
:param host_uuid: The host system UUID.
|
|
:return: The wrapper for the secure RMC vSwitch. If it does not exist
|
|
on the system, None is returned.
|
|
"""
|
|
vswitches = pvm_net.VSwitch.search(
|
|
adapter, parent_type=pvm_ms.System.schema_type,
|
|
parent_uuid=host_uuid, name=SECURE_RMC_VSWITCH)
|
|
if len(vswitches) == 1:
|
|
return vswitches[0]
|
|
return None
|
|
|
|
|
|
def plug_secure_rmc_vif(adapter, instance, host_uuid):
|
|
"""Creates the Secure RMC Network Adapter on the VM.
|
|
|
|
:param adapter: The pypowervm adapter API interface.
|
|
:param instance: The nova instance to create the VIF against.
|
|
:param host_uuid: The host system UUID.
|
|
:return: The created network adapter wrapper.
|
|
"""
|
|
lpar_uuid = vm.get_pvm_uuid(instance)
|
|
return pvm_cna.crt_cna(adapter, host_uuid, lpar_uuid, SECURE_RMC_VLAN,
|
|
vswitch=SECURE_RMC_VSWITCH, crt_vswitch=True)
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class PvmVifDriver(object):
|
|
"""Represents an abstract class for a PowerVM Vif Driver.
|
|
|
|
A VIF Driver understands a given virtual interface type (network). It
|
|
understands how to plug and unplug a given VIF for a virtual machine.
|
|
"""
|
|
|
|
def __init__(self, adapter, host_uuid, instance):
|
|
"""Initializes a VIF Driver.
|
|
|
|
:param adapter: The pypowervm adapter API interface.
|
|
:param host_uuid: The host system UUID.
|
|
:param instance: The nova instance that the vif action will be run
|
|
against.
|
|
"""
|
|
self.adapter = adapter
|
|
self.host_uuid = host_uuid
|
|
self.instance = instance
|
|
|
|
@abc.abstractmethod
|
|
def plug(self, vif):
|
|
"""Plugs a virtual interface (network) into a VM.
|
|
|
|
:param vif: The virtual interface to plug into the instance.
|
|
"""
|
|
pass
|
|
|
|
def unplug(self, vif, cna_w_list=None):
|
|
"""Unplugs a virtual interface (network) from a VM.
|
|
|
|
:param vif: The virtual interface to plug into the instance.
|
|
:param cna_w_list: (Optional, Default: None) The list of Client Network
|
|
Adapters from pypowervm. Providing this input
|
|
allows for an improvement in operation speed.
|
|
"""
|
|
# This is a default implementation that most implementations will
|
|
# require.
|
|
|
|
# Need to find the adapters if they were not provided
|
|
if not cna_w_list:
|
|
cna_w_list = vm.get_cnas(self.adapter, self.instance,
|
|
self.host_uuid)
|
|
|
|
cna_w = self._find_cna_for_vif(cna_w_list, vif)
|
|
if not cna_w:
|
|
LOG.warning(_LW('Unable to unplug VIF with mac %(mac)s for '
|
|
'instance %(inst)s. The VIF was not found on '
|
|
'the instance.'),
|
|
{'mac': vif['address'], 'inst': self.instance.name})
|
|
return
|
|
|
|
LOG.info(_LI('Deleting VIF with mac %(mac)s for instance %(inst)s.'),
|
|
{'mac': vif['address'], 'inst': self.instance.name})
|
|
try:
|
|
cna_w.delete()
|
|
except Exception as e:
|
|
LOG.error(_LE('Unable to unplug VIF with mac %(mac)s for instance '
|
|
'%(inst)s.'), {'mac': vif['address'],
|
|
'inst': self.instance.name})
|
|
LOG.exception(e)
|
|
raise VirtualInterfaceUnplugException()
|
|
|
|
def _find_cna_for_vif(self, cna_w_list, vif):
|
|
for cna_w in cna_w_list:
|
|
# If the MAC address matched, attempt the delete.
|
|
if vm.norm_mac(cna_w.mac) == vif['address']:
|
|
return cna_w
|
|
return None
|
|
|
|
|
|
class PvmSeaVifDriver(PvmVifDriver):
|
|
"""The PowerVM Shared Ethernet Adapter VIF Driver."""
|
|
|
|
def plug(self, vif):
|
|
lpar_uuid = vm.get_pvm_uuid(self.instance)
|
|
# CNA's require a VLAN. If the network doesn't provide, default to 1
|
|
vlan = vif['network']['meta'].get('vlan', 1)
|
|
return pvm_cna.crt_cna(self.adapter, self.host_uuid, lpar_uuid, vlan,
|
|
mac_addr=vif['address'])
|
|
|
|
|
|
class PvmOvsVifDriver(PvmVifDriver):
|
|
"""The Open vSwitch VIF driver for PowerVM."""
|
|
|
|
def plug(self, vif):
|
|
# Create the trunk and client adapter.
|
|
lpar_uuid = vm.get_pvm_uuid(self.instance)
|
|
mgmt_uuid = mgmt.get_mgmt_partition(self.adapter).uuid
|
|
cna_w, trunk_wraps = pvm_cna.crt_p2p_cna(
|
|
self.adapter, self.host_uuid, lpar_uuid, [mgmt_uuid],
|
|
CONF.powervm.pvm_vswitch_for_ovs, crt_vswitch=True,
|
|
mac_addr=vif['address'])
|
|
|
|
# There will only be one trunk wrap, as we have created with just the
|
|
# mgmt lpar. Next step is to set the device up and connect to the OVS
|
|
dev = self.get_trunk_dev_name(trunk_wraps[0])
|
|
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
|
|
linux_net.create_ovs_vif_port(vif['network']['bridge'], dev,
|
|
self.get_ovs_interfaceid(vif),
|
|
vif['address'], self.instance.uuid)
|
|
|
|
def get_ovs_interfaceid(self, vif):
|
|
return vif.get('ovs_interfaceid') or vif['id']
|
|
|
|
def get_trunk_dev_name(self, trunk_w):
|
|
# The mac address from the API is of format: 01234567890A
|
|
# We need it in format: 01:23:45:67:89:0a
|
|
# That means we need to add colons and lower case it
|
|
mac_addr = ":".join(trunk_w.mac[i:i + 2]
|
|
for i in range(0, len(trunk_w.mac), 2)).lower()
|
|
|
|
# Use netifaces to find the appropriate matching interface name
|
|
# TODO(thorst) I don't like this logic. Seems gross.
|
|
ifaces = netifaces.interfaces()
|
|
for iface in ifaces:
|
|
link_addrs = netifaces.ifaddresses(iface)[netifaces.AF_LINK]
|
|
for link_addr in link_addrs:
|
|
if link_addr.get('addr') == mac_addr:
|
|
return iface
|
|
|
|
raise exception.VirtualInterfacePlugException(
|
|
_("Unable to find appropriate Trunk Device for mac "
|
|
"%(mac_addr)s.") % {'mac_addr': mac_addr})
|
|
|
|
def unplug(self, vif, cna_w_list=None):
|
|
# Need to find the adapters if they were not provided
|
|
if not cna_w_list:
|
|
cna_w_list = vm.get_cnas(self.adapter, self.instance,
|
|
self.host_uuid)
|
|
|
|
# Find the CNA for this vif.
|
|
cna_w = self._find_cna_for_vif(cna_w_list, vif)
|
|
if not cna_w:
|
|
LOG.warning(_LW('Unable to unplug VIF with mac %(mac)s for '
|
|
'instance %(inst)s. The VIF was not found on '
|
|
'the instance.'),
|
|
{'mac': vif['address'], 'inst': self.instance.name})
|
|
return
|
|
|
|
# Find and delete the trunk adapters
|
|
trunks = pvm_cna.find_trunks(self.adapter, cna_w)
|
|
for trunk in trunks:
|
|
dev = self.get_trunk_dev_name(trunk)
|
|
linux_net.delete_ovs_vif_port(vif['network']['bridge'], dev)
|
|
trunk.delete()
|
|
|
|
# Now delete the client CNA
|
|
super(PvmOvsVifDriver, self).unplug(vif, cna_w_list=cna_w_list)
|