Files
nova-powervm/nova_powervm/virt/powervm/vif.py
Drew Thorstensen 8298927776 Add in OVS VIF Driver Framework
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
2016-02-26 08:33:58 -05:00

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)