2524 lines
106 KiB
Python
2524 lines
106 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
#
|
|
# Copyright 2013 UnitedStack Inc.
|
|
# 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.
|
|
#
|
|
# Copyright (c) 2013-2016 Wind River Systems, Inc.
|
|
#
|
|
|
|
|
|
import jsonpatch
|
|
import six
|
|
import uuid
|
|
|
|
import pecan
|
|
from pecan import rest
|
|
import copy
|
|
import wsme
|
|
import string
|
|
from wsme import types as wtypes
|
|
import wsmeext.pecan as wsme_pecan
|
|
|
|
from sysinv.api.controllers.v1 import address
|
|
from sysinv.api.controllers.v1 import address_pool
|
|
from sysinv.api.controllers.v1 import base
|
|
from sysinv.api.controllers.v1 import collection
|
|
from sysinv.api.controllers.v1 import port as port_api
|
|
from sysinv.api.controllers.v1 import link
|
|
from sysinv.api.controllers.v1 import route
|
|
from sysinv.api.controllers.v1 import types
|
|
from sysinv.api.controllers.v1 import utils
|
|
from sysinv.api.controllers.v1 import interface_network
|
|
from sysinv.common import constants
|
|
from sysinv.common import exception
|
|
from sysinv.common import utils as cutils
|
|
from sysinv import objects
|
|
from sysinv.objects import utils as object_utils
|
|
from sysinv.openstack.common import log
|
|
from sysinv.openstack.common import uuidutils
|
|
from sysinv.openstack.common.rpc import common as rpc_common
|
|
from sysinv.openstack.common.gettextutils import _
|
|
from fm_api import constants as fm_constants
|
|
from fm_api import fm_api
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
FM = fm_api.FaultAPIs()
|
|
|
|
# These are the only valid network type values
|
|
VALID_NETWORK_TYPES = [constants.NETWORK_TYPE_NONE,
|
|
constants.NETWORK_TYPE_PXEBOOT,
|
|
constants.NETWORK_TYPE_OAM,
|
|
constants.NETWORK_TYPE_MGMT,
|
|
constants.NETWORK_TYPE_INFRA,
|
|
constants.NETWORK_TYPE_DATA,
|
|
constants.NETWORK_TYPE_PCI_PASSTHROUGH,
|
|
constants.NETWORK_TYPE_PCI_SRIOV]
|
|
|
|
VALID_INTERFACE_CLASS = [constants.INTERFACE_CLASS_PLATFORM,
|
|
constants.INTERFACE_CLASS_DATA,
|
|
constants.INTERFACE_CLASS_PCI_PASSTHROUGH,
|
|
constants.INTERFACE_CLASS_PCI_SRIOV]
|
|
|
|
# Interface network types that require coordination with neutron
|
|
NEUTRON_NETWORK_TYPES = [constants.NETWORK_TYPE_DATA,
|
|
constants.NETWORK_TYPE_PCI_PASSTHROUGH,
|
|
constants.NETWORK_TYPE_PCI_SRIOV]
|
|
|
|
NEUTRON_INTERFACE_CLASS = [constants.INTERFACE_CLASS_DATA,
|
|
constants.INTERFACE_CLASS_PCI_PASSTHROUGH,
|
|
constants.INTERFACE_CLASS_PCI_SRIOV]
|
|
|
|
# Interface network types that are PCI based
|
|
PCI_NETWORK_TYPES = [constants.NETWORK_TYPE_PCI_PASSTHROUGH, constants.NETWORK_TYPE_PCI_SRIOV]
|
|
|
|
# These combinations of network types are not supported on an interface
|
|
INCOMPATIBLE_NETWORK_TYPES = [[constants.NETWORK_TYPE_PXEBOOT, constants.NETWORK_TYPE_DATA],
|
|
[constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_DATA],
|
|
[constants.NETWORK_TYPE_INFRA, constants.NETWORK_TYPE_DATA],
|
|
[constants.NETWORK_TYPE_OAM, constants.NETWORK_TYPE_DATA]]
|
|
|
|
VALID_AEMODE_LIST = ['active_standby', 'balanced', '802.3ad']
|
|
|
|
DATA_NETWORK_TYPES = [constants.NETWORK_TYPE_DATA]
|
|
|
|
# Kernel allows max 15 chars. For Ethernet/AE, leave 5 for VLAN id.
|
|
# For VLAN interfaces, support the full 15 char limit
|
|
MAX_IFNAME_LEN = 10
|
|
MAX_VLAN_ID_LEN = 5
|
|
|
|
# Maximum number of characters in provider network list
|
|
MAX_PROVIDERNETWORK_LEN = 255
|
|
|
|
DEFAULT_MTU = 1500
|
|
|
|
|
|
class InterfacePatchType(types.JsonPatchType):
|
|
@staticmethod
|
|
def mandatory_attrs():
|
|
return ['/address', '/ihost_uuid']
|
|
|
|
|
|
class Interface(base.APIBase):
|
|
"""API representation of an interface.
|
|
|
|
This class enforces type checking and value constraints, and converts
|
|
between the internal object model and the API representation of
|
|
an interface.
|
|
"""
|
|
|
|
uuid = types.uuid
|
|
"Unique UUID for this interface"
|
|
|
|
ifname = wtypes.text
|
|
"Represent the unique name of the interface"
|
|
|
|
iftype = wtypes.text
|
|
"Represent the unique type of the interface"
|
|
|
|
# mac = wsme.wsattr(types.macaddress, mandatory=True)
|
|
imac = wsme.wsattr(types.macaddress, mandatory=False)
|
|
"MAC Address for this interface"
|
|
|
|
imtu = int
|
|
"MTU bytes size for this interface"
|
|
|
|
ifclass = wtypes.text
|
|
"Represent the class of the interface"
|
|
|
|
networktype = wtypes.text
|
|
"Represent the network type of the interface"
|
|
|
|
aemode = wtypes.text
|
|
"Represent the aemode of the interface"
|
|
|
|
schedpolicy = wtypes.text
|
|
"Represent the schedpolicy of the interface"
|
|
|
|
txhashpolicy = wtypes.text
|
|
"Represent the txhashpolicy of the interface"
|
|
|
|
providernetworks = wtypes.text
|
|
"Represent the providernetworks of the interface"
|
|
|
|
providernetworksdict = {wtypes.text: utils.ValidTypes(wtypes.text,
|
|
six.integer_types)}
|
|
"Represent the providernetworksdict of the interface"
|
|
|
|
ifcapabilities = {wtypes.text: utils.ValidTypes(wtypes.text,
|
|
six.integer_types)}
|
|
"This interface's meta data"
|
|
|
|
forihostid = int
|
|
"The ihostid that this interface belongs to"
|
|
|
|
ihost_uuid = types.uuid
|
|
"The UUID of the host this interface belongs to"
|
|
|
|
ports = [link.Link]
|
|
"Links to the collection of Ports on this interface"
|
|
|
|
links = [link.Link]
|
|
"A list containing a self link and associated interface links"
|
|
|
|
vlan_id = int
|
|
"VLAN id for this interface"
|
|
|
|
uses = [wtypes.text]
|
|
"A list containing the interface(s) that this interface uses"
|
|
|
|
usesmodify = wtypes.text
|
|
"A list containing the interface(s) that this interface uses"
|
|
|
|
used_by = [wtypes.text]
|
|
"A list containing the interface(s) that use this interface"
|
|
|
|
ipv4_mode = wtypes.text
|
|
"Represents the current IPv4 address mode"
|
|
|
|
ipv4_pool = wtypes.text
|
|
"Represents the current IPv4 address pool selection"
|
|
|
|
ipv6_mode = wtypes.text
|
|
"Represents the current IPv6 address mode"
|
|
|
|
ipv6_pool = wtypes.text
|
|
"Represents the current IPv6 address pool selection"
|
|
|
|
sriov_numvfs = int
|
|
"The number of configured SR-IOV VFs"
|
|
|
|
networks = [wtypes.text]
|
|
"Represent the networks of the interface"
|
|
|
|
def __init__(self, **kwargs):
|
|
self.fields = objects.interface.fields.keys()
|
|
for k in self.fields:
|
|
setattr(self, k, kwargs.get(k))
|
|
|
|
# API-only attributes
|
|
self.fields.append('ports')
|
|
setattr(self, 'ports', kwargs.get('ports', None))
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_interface, expand=True):
|
|
# fields = ['uuid', 'address'] if not expand else None
|
|
# interface = iinterface.from_rpc_object(rpc_interface, fields)
|
|
|
|
interface = Interface(**rpc_interface.as_dict())
|
|
if not expand:
|
|
interface.unset_fields_except(['uuid', 'ifname', 'iftype',
|
|
'imac', 'imtu', 'ifclass', 'networktype', 'networks',
|
|
'aemode', 'schedpolicy', 'txhashpolicy',
|
|
'providernetworks', 'ihost_uuid', 'forihostid',
|
|
'vlan_id', 'uses', 'usesmodify', 'used_by',
|
|
'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool',
|
|
'sriov_numvfs'])
|
|
|
|
# never expose the ihost_id attribute
|
|
interface.ihost_id = wtypes.Unset
|
|
# interface.networktype = wtypes.Unset
|
|
|
|
interface.links = [link.Link.make_link('self', pecan.request.host_url,
|
|
'iinterfaces', interface.uuid),
|
|
link.Link.make_link('bookmark',
|
|
pecan.request.host_url,
|
|
'iinterfaces', interface.uuid,
|
|
bookmark=True)
|
|
]
|
|
if expand:
|
|
interface.ports = [
|
|
link.Link.make_link('self',
|
|
pecan.request.host_url,
|
|
'iinterfaces',
|
|
interface.uuid + "/ports"),
|
|
link.Link.make_link(
|
|
'bookmark',
|
|
pecan.request.host_url,
|
|
'iinterfaces',
|
|
interface.uuid + "/ports",
|
|
bookmark=True)
|
|
]
|
|
|
|
ifclass = rpc_interface.as_dict()['ifclass']
|
|
networks = rpc_interface.as_dict()['networks']
|
|
networktypelist = []
|
|
if ifclass == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in networks:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
networktypelist.append(network.type)
|
|
elif ifclass:
|
|
networktypelist.append(ifclass)
|
|
else:
|
|
networktypelist.append(constants.INTERFACE_CLASS_NONE)
|
|
if not any(networktype in address.ALLOWED_NETWORK_TYPES
|
|
for networktype in networktypelist):
|
|
|
|
# Hide this functionality when the network type does not support
|
|
# setting or updating the network type
|
|
interface.ipv4_mode = wtypes.Unset
|
|
interface.ipv6_mode = wtypes.Unset
|
|
interface.ipv4_pool = wtypes.Unset
|
|
interface.ipv6_pool = wtypes.Unset
|
|
|
|
# It is not necessary to show these fields if the interface is not
|
|
# configured to allocate addresses from a pool
|
|
if interface.ipv4_mode != constants.IPV4_POOL:
|
|
interface.ipv4_pool = wtypes.Unset
|
|
if interface.ipv6_mode != constants.IPV6_POOL:
|
|
interface.ipv6_pool = wtypes.Unset
|
|
|
|
return interface
|
|
|
|
|
|
class InterfaceCollection(collection.Collection):
|
|
"""API representation of a collection of interfaces."""
|
|
|
|
iinterfaces = [Interface]
|
|
"A list containing interface objects"
|
|
|
|
def __init__(self, **kwargs):
|
|
self._type = 'iinterfaces'
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_interfaces, limit, url=None,
|
|
expand=False, **kwargs):
|
|
collection = InterfaceCollection()
|
|
collection.iinterfaces = [Interface.convert_with_links(p, expand)
|
|
for p in rpc_interfaces]
|
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
|
return collection
|
|
|
|
|
|
LOCK_NAME = 'InterfaceController'
|
|
|
|
|
|
class InterfaceController(rest.RestController):
|
|
"""REST controller for iinterfaces."""
|
|
|
|
ports = port_api.PortController(from_iinterface=True)
|
|
"Expose ports as a sub-element of interface"
|
|
|
|
addresses = address.AddressController(parent="iinterfaces")
|
|
"Expose addresses as a sub-element of interface"
|
|
|
|
routes = route.RouteController(parent="iinterfaces")
|
|
"Expose routes as a sub-element of interface"
|
|
|
|
interface_networks = interface_network.InterfaceNetworkController(
|
|
parent="iinterfaces")
|
|
"Expose interface_networks as a sub-element of interface"
|
|
|
|
_custom_actions = {
|
|
'detail': ['GET'],
|
|
}
|
|
|
|
def __init__(self, from_ihosts=False):
|
|
self._from_ihosts = from_ihosts
|
|
|
|
def _get_interfaces_collection(self, ihost_uuid, marker, limit, sort_key,
|
|
sort_dir, expand=False, resource_url=None):
|
|
if self._from_ihosts and not ihost_uuid:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Host id not specified."))
|
|
|
|
limit = utils.validate_limit(limit)
|
|
sort_dir = utils.validate_sort_dir(sort_dir)
|
|
|
|
marker_obj = None
|
|
if marker:
|
|
marker_obj = objects.interface.get_by_uuid(
|
|
pecan.request.context,
|
|
marker)
|
|
|
|
if ihost_uuid:
|
|
interfaces = pecan.request.dbapi.iinterface_get_by_ihost(
|
|
ihost_uuid, limit,
|
|
marker_obj,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
else:
|
|
interfaces = pecan.request.dbapi.iinterface_get_list(
|
|
limit, marker_obj,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
return InterfaceCollection.convert_with_links(interfaces, limit,
|
|
url=resource_url,
|
|
expand=expand,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(InterfaceCollection, wtypes.text, types.uuid, int,
|
|
wtypes.text, wtypes.text)
|
|
def get_all(self, ihost=None, marker=None, limit=None,
|
|
sort_key='id', sort_dir='asc'):
|
|
"""Retrieve a list of interfaces."""
|
|
|
|
if uuidutils.is_uuid_like(ihost) or cutils.is_int_like(ihost):
|
|
ihost_id = ihost
|
|
else:
|
|
try:
|
|
host = pecan.request.dbapi.ihost_get(ihost)
|
|
ihost_id = host.uuid
|
|
except exception.SysinvException:
|
|
raise wsme.exc.ClientSideError(_("Invalid ihost %s" % ihost))
|
|
|
|
return self._get_interfaces_collection(ihost_id, marker, limit,
|
|
sort_key, sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(InterfaceCollection, types.uuid, types.uuid, int,
|
|
wtypes.text, wtypes.text)
|
|
def detail(self, ihost_uuid=None, marker=None, limit=None,
|
|
sort_key='id', sort_dir='asc'):
|
|
"""Retrieve a list of interfaces with detail."""
|
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
|
parent = pecan.request.path.split('/')[:-1][-1]
|
|
if parent != "iinterfaces":
|
|
raise exception.HTTPNotFound
|
|
|
|
expand = True
|
|
resource_url = '/'.join(['interfaces', 'detail'])
|
|
return self._get_interfaces_collection(ihost_uuid,
|
|
marker, limit,
|
|
sort_key, sort_dir,
|
|
expand, resource_url)
|
|
|
|
@wsme_pecan.wsexpose(Interface, types.uuid)
|
|
def get_one(self, interface_uuid):
|
|
"""Retrieve information about the given interface."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
rpc_interface = objects.interface.get_by_uuid(
|
|
pecan.request.context, interface_uuid)
|
|
return Interface.convert_with_links(rpc_interface)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(Interface, body=Interface)
|
|
def post(self, interface):
|
|
"""Create a new interface."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
try:
|
|
interface = interface.as_dict()
|
|
new_interface = _create(interface)
|
|
except exception.SysinvException as e:
|
|
LOG.exception(e)
|
|
raise wsme.exc.ClientSideError(str(e))
|
|
except exception.HTTPNotFound:
|
|
raise wsme.exc.ClientSideError(_("Interface create failed: interface %s"
|
|
% (interface['ifname'])))
|
|
return Interface.convert_with_links(new_interface)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme.validate(types.uuid, [InterfacePatchType])
|
|
@wsme_pecan.wsexpose(Interface, types.uuid,
|
|
body=[InterfacePatchType])
|
|
def patch(self, interface_uuid, patch):
|
|
"""Update an existing interface."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
LOG.debug("patch_data: %s" % patch)
|
|
|
|
uses = None
|
|
ports = None
|
|
networks = []
|
|
networks_to_add = []
|
|
interface_networks_to_remove = []
|
|
patches_to_remove = []
|
|
for p in patch:
|
|
if '/ifclass' == p['path']:
|
|
if p['value'] == 'none':
|
|
p['value'] = None
|
|
elif '/usesmodify' == p['path']:
|
|
uses = p['value'].split(',')
|
|
patches_to_remove.append(p)
|
|
elif '/ports' == p['path']:
|
|
ports = p['value']
|
|
patches_to_remove.append(p)
|
|
elif '/networks' == p['path']:
|
|
networks = p['value'].split(',')
|
|
patches_to_remove.append(p)
|
|
elif '/networks_to_add' == p['path']:
|
|
networks_to_add = p['value'].split(',')
|
|
patches_to_remove.append(p)
|
|
elif '/interface_networks_to_remove' == p['path']:
|
|
interface_networks_to_remove = p['value'].split(',')
|
|
patches_to_remove.append(p)
|
|
|
|
if uses:
|
|
patch.append(dict(path='/uses', value=uses, op='replace'))
|
|
|
|
patch = [p for p in patch if p not in patches_to_remove]
|
|
|
|
LOG.debug("patch_ports: %s" % ports)
|
|
LOG.debug("patch_networks: %s" % networks)
|
|
|
|
rpc_interface = objects.interface.get_by_uuid(pecan.request.context,
|
|
interface_uuid)
|
|
|
|
# create a temp interface for semantics checks
|
|
temp_interface = copy.deepcopy(rpc_interface)
|
|
|
|
if 'forihostid' in rpc_interface:
|
|
ihostId = rpc_interface['forihostid']
|
|
else:
|
|
ihostId = rpc_interface['ihost_uuid']
|
|
|
|
ihost = pecan.request.dbapi.ihost_get(ihostId)
|
|
|
|
# Check mtu before updating ports
|
|
imtu = None
|
|
for p in patch:
|
|
if '/imtu' in p['path']:
|
|
# Update the imtu to the new value
|
|
if rpc_interface['imtu']:
|
|
if int(p['value']) != int(rpc_interface['imtu']):
|
|
imtu = p['value']
|
|
break
|
|
|
|
temp_interface['imtu'] = imtu
|
|
LOG.debug("rpc_mtu: %s" % rpc_interface['imtu'])
|
|
_check_interface_mtu(temp_interface.as_dict(), ihost)
|
|
|
|
# Check SR-IOV before updating the ports
|
|
for p in patch:
|
|
if '/ifclass' == p['path']:
|
|
temp_interface['ifclass'] = p['value']
|
|
elif '/sriov_numvfs' == p['path']:
|
|
temp_interface['sriov_numvfs'] = p['value']
|
|
# If network type is not pci-sriov, reset the sriov-numvfs to zero
|
|
if (temp_interface['sriov_numvfs'] is not None and
|
|
temp_interface['ifclass'] is not None and
|
|
temp_interface[
|
|
'ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV):
|
|
temp_interface['sriov_numvfs'] = None
|
|
_check_interface_sriov(temp_interface.as_dict(), ihost)
|
|
|
|
# Get the ethernet port associated with the interface if network type
|
|
# is changed
|
|
interface_ports = pecan.request.dbapi.ethernet_port_get_by_interface(
|
|
rpc_interface.uuid)
|
|
for p in interface_ports:
|
|
if p is not None:
|
|
ports = p.name
|
|
break
|
|
|
|
# Process updates
|
|
vlan_id = None
|
|
delete_addressing = False
|
|
|
|
for p in patch:
|
|
if '/vlan_id' in p['path']:
|
|
# Update vlan_id to the new value
|
|
if rpc_interface['vlan_id']:
|
|
if int(p['value']) != int(rpc_interface['vlan_id']):
|
|
vlan_id = p['value']
|
|
|
|
temp_interface['vlan_id'] = vlan_id
|
|
_check_interface_vlan_id("modify", temp_interface.as_dict(), ihost)
|
|
|
|
# replace ihost_uuid and iinterface_uuid with corresponding
|
|
patch_obj = jsonpatch.JsonPatch(patch)
|
|
for p in patch_obj:
|
|
if p['path'] == '/ihost_uuid':
|
|
p['path'] = '/forihostid'
|
|
ihost = objects.host.get_by_uuid(pecan.request.context,
|
|
p['value'])
|
|
p['value'] = ihost.id
|
|
|
|
try:
|
|
interface = Interface(**jsonpatch.apply_patch(
|
|
rpc_interface.as_dict(),
|
|
patch_obj)).as_dict()
|
|
except utils.JSONPATCH_EXCEPTIONS as e:
|
|
raise exception.PatchError(patch=patch, reason=e)
|
|
|
|
# if the aemode is changed adjust the txhashpolicy if necessary
|
|
if interface['aemode'] == 'active_standby':
|
|
interface['txhashpolicy'] = None
|
|
|
|
# The variable 'networks' contains a list of networks that the
|
|
# interface should have by the end of this update. These should be
|
|
# compared to the previous networks assigned to the interface
|
|
interface['networks'] = networks
|
|
|
|
if (not interface['ifclass'] or
|
|
interface['ifclass'] == constants.INTERFACE_CLASS_NONE):
|
|
# If the interface class is reset, make sure any network
|
|
# specific fields are reset as well
|
|
interface['networktype'] = None
|
|
interface['sriov_numvfs'] = 0
|
|
interface['ipv4_mode'] = None
|
|
interface['ipv6_mode'] = None
|
|
delete_addressing = True
|
|
else:
|
|
# Otherwise make sure that appropriate defaults are set.
|
|
interface = _set_defaults(interface)
|
|
|
|
# clear address pool values if address mode no longer set to pool
|
|
if interface['ipv4_mode'] != constants.IPV4_POOL:
|
|
interface['ipv4_pool'] = None
|
|
if interface['ipv6_mode'] != constants.IPV6_POOL:
|
|
interface['ipv6_pool'] = None
|
|
|
|
interface = _check("modify", interface,
|
|
ports=ports, ifaces=uses,
|
|
existing_interface=rpc_interface.as_dict())
|
|
|
|
if uses:
|
|
# Update MAC address if uses list changed
|
|
interface = set_interface_mac(ihost, interface)
|
|
update_upper_interface_macs(ihost, interface)
|
|
|
|
if ports:
|
|
_update_ports("modify", rpc_interface, ihost, ports)
|
|
|
|
if (not interface['ifclass'] or
|
|
interface['ifclass'] == constants.NETWORK_TYPE_NONE):
|
|
ifclass = None
|
|
else:
|
|
ifclass = interface['ifclass']
|
|
orig_ifclass = rpc_interface['ifclass']
|
|
if (not ifclass and
|
|
orig_ifclass == constants.INTERFACE_CLASS_PLATFORM):
|
|
for network_id in rpc_interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
if network.type == constants.NETWORK_TYPE_MGMT:
|
|
# Remove mgmt address associated with this interface
|
|
pecan.request.rpcapi.mgmt_ip_set_by_ihost(
|
|
pecan.request.context,
|
|
ihost['uuid'],
|
|
None)
|
|
elif network.type == constants.NETWORK_TYPE_INFRA:
|
|
# Remove infra address associated with this interface
|
|
pecan.request.rpcapi.infra_ip_set_by_ihost(
|
|
pecan.request.context,
|
|
ihost['uuid'],
|
|
None)
|
|
|
|
if delete_addressing:
|
|
for family in constants.IP_FAMILIES:
|
|
_delete_addressing(interface, family, rpc_interface)
|
|
else:
|
|
if _is_ipv4_address_mode_updated(interface, rpc_interface):
|
|
_update_ipv4_address_mode(interface)
|
|
if _is_ipv6_address_mode_updated(interface, rpc_interface):
|
|
_update_ipv6_address_mode(interface)
|
|
|
|
# Commit operation with neutron
|
|
if (interface['ifclass'] and
|
|
interface['ifclass'] in NEUTRON_INTERFACE_CLASS):
|
|
_neutron_bind_interface(ihost, interface)
|
|
if (rpc_interface['ifclass'] and
|
|
rpc_interface['ifclass'] in NEUTRON_INTERFACE_CLASS and
|
|
interface['ifclass'] not in NEUTRON_INTERFACE_CLASS):
|
|
_neutron_unbind_interface(ihost, rpc_interface)
|
|
|
|
saved_interface = copy.deepcopy(rpc_interface)
|
|
|
|
# Update interface-network
|
|
try:
|
|
if networks_to_add:
|
|
for network_id in networks_to_add:
|
|
values = {'interface_id': interface['id'],
|
|
'network_id': network_id}
|
|
pecan.request.dbapi.interface_network_create(values)
|
|
elif networks:
|
|
for network_id in networks:
|
|
values = {'interface_id': interface['id'],
|
|
'network_id': network_id}
|
|
pecan.request.dbapi.interface_network_create(values)
|
|
except exception.InterfaceNetworkAlreadyExists:
|
|
pass
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
msg = _("Failed to create interface network association for "
|
|
"interface %s" % (interface['ifname']))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
try:
|
|
# Remove old networks from the interface
|
|
if interface_networks_to_remove:
|
|
for ifnet_id in interface_networks_to_remove:
|
|
pecan.request.dbapi.interface_network_destroy(ifnet_id)
|
|
elif (orig_ifclass == constants.INTERFACE_CLASS_PLATFORM and
|
|
(not ifclass or
|
|
ifclass != constants.INTERFACE_CLASS_PLATFORM)):
|
|
ifnets = pecan.request.dbapi.interface_network_get_by_interface(
|
|
rpc_interface['uuid'])
|
|
for ifnet in ifnets:
|
|
pecan.request.dbapi.interface_network_destroy(ifnet.uuid)
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
msg = _("Failed to remove interface network association for "
|
|
"interface %s" % (interface['ifname']))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
try:
|
|
# Update only the fields that have changed
|
|
for field in objects.interface.fields:
|
|
if field in rpc_interface.as_dict():
|
|
if rpc_interface[field] != interface[field]:
|
|
rpc_interface[field] = interface[field]
|
|
|
|
rpc_interface.save()
|
|
# Re-read from the DB to populate extended attributes
|
|
new_interface = objects.interface.get_by_uuid(
|
|
pecan.request.context, rpc_interface.uuid)
|
|
|
|
networktypelist = []
|
|
if new_interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in new_interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
networktypelist.append(network.type)
|
|
elif new_interface['ifclass']:
|
|
networktypelist = [new_interface['ifclass']]
|
|
else:
|
|
networktypelist = [constants.NETWORK_TYPE_NONE]
|
|
|
|
# Update address (if required)
|
|
if constants.NETWORK_TYPE_MGMT in networktypelist:
|
|
_update_host_mgmt_address(ihost, interface)
|
|
if constants.NETWORK_TYPE_INFRA in networktypelist:
|
|
_update_host_infra_address(ihost, interface)
|
|
if ihost['personality'] == constants.CONTROLLER:
|
|
if constants.NETWORK_TYPE_OAM in networktypelist:
|
|
_update_host_oam_address(ihost, interface)
|
|
elif constants.NETWORK_TYPE_PXEBOOT in networktypelist:
|
|
_update_host_pxeboot_address(ihost, interface)
|
|
|
|
# Update the MTU of underlying interfaces of an AE
|
|
if new_interface['iftype'] == constants.INTERFACE_TYPE_AE:
|
|
for ifname in new_interface['uses']:
|
|
_update_interface_mtu(ifname, ihost, new_interface['imtu'])
|
|
|
|
# Restore the default MTU for removed AE members
|
|
old_members = set(saved_interface['uses'])
|
|
new_members = set(new_interface['uses'])
|
|
removed_members = old_members - new_members
|
|
for ifname in removed_members:
|
|
_update_interface_mtu(ifname, ihost, DEFAULT_MTU)
|
|
|
|
# Update shared data interface bindings, if required
|
|
_update_shared_interface_neutron_bindings(ihost, new_interface)
|
|
|
|
return Interface.convert_with_links(new_interface)
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
msg = _("Interface update failed: host %s if %s : patch %s"
|
|
% (ihost['hostname'], interface['ifname'], patch))
|
|
if (saved_interface['ifclass'] and
|
|
saved_interface['ifclass'] in NEUTRON_INTERFACE_CLASS):
|
|
# Restore Neutron bindings
|
|
_neutron_bind_interface(ihost, saved_interface)
|
|
|
|
# Update shared data interface bindings, if required
|
|
_update_shared_interface_neutron_bindings(ihost, saved_interface)
|
|
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
|
def delete(self, interface_uuid):
|
|
"""Delete a interface."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
interface = objects.interface.get_by_uuid(pecan.request.context,
|
|
interface_uuid)
|
|
interface = interface.as_dict()
|
|
|
|
_delete(interface)
|
|
|
|
|
|
##############
|
|
# UTILS
|
|
##############
|
|
|
|
def _dynamic_address_allocation():
|
|
mgmt_network = pecan.request.dbapi.network_get_by_type(
|
|
constants.NETWORK_TYPE_MGMT)
|
|
return mgmt_network.dynamic
|
|
|
|
|
|
def _set_address_family_defaults_by_pool(defaults, pool_type):
|
|
pool_uuid = pecan.request.dbapi.network_get_by_type(pool_type).pool_uuid
|
|
pool = pecan.request.dbapi.address_pool_get(pool_uuid)
|
|
if pool.family == constants.IPV4_FAMILY:
|
|
defaults['ipv4_mode'] = constants.IPV4_STATIC
|
|
defaults['ipv6_mode'] = constants.IPV6_DISABLED
|
|
else:
|
|
defaults['ipv6_mode'] = constants.IPV6_STATIC
|
|
defaults['ipv4_mode'] = constants.IPV4_DISABLED
|
|
|
|
|
|
def _set_defaults(interface):
|
|
defaults = {'imtu': DEFAULT_MTU,
|
|
'networktype': constants.NETWORK_TYPE_DATA,
|
|
'aemode': 'active_standby',
|
|
'txhashpolicy': None,
|
|
'vlan_id': None,
|
|
'sriov_numvfs': 0}
|
|
|
|
networktypelist = []
|
|
if interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM:
|
|
if interface['networks']:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
networktypelist.append(network.type)
|
|
interface['networktype'] = ",".join(networktypelist)
|
|
elif interface['networktype']:
|
|
networks = []
|
|
networktypelist = interface['networktype'].split(',')
|
|
for network_type in networktypelist:
|
|
if network_type in constants.PLATFORM_NETWORK_TYPES:
|
|
network = pecan.request.dbapi.network_get_by_type(
|
|
network_type
|
|
)
|
|
networks.append(str(network.id))
|
|
interface['networks'] = networks
|
|
elif interface['ifclass'] in NEUTRON_NETWORK_TYPES:
|
|
interface['networktype'] = interface['ifclass']
|
|
|
|
family_defaults = [constants.NETWORK_TYPE_MGMT,
|
|
constants.NETWORK_TYPE_OAM,
|
|
constants.NETWORK_TYPE_INFRA]
|
|
if interface['ifclass'] == constants.INTERFACE_CLASS_DATA:
|
|
defaults['ipv4_mode'] = constants.IPV4_DISABLED
|
|
defaults['ipv6_mode'] = constants.IPV6_DISABLED
|
|
else:
|
|
for network_type in networktypelist:
|
|
if network_type in family_defaults:
|
|
_set_address_family_defaults_by_pool(defaults,
|
|
network_type)
|
|
|
|
interface_merged = interface.copy()
|
|
for key in interface_merged:
|
|
if interface_merged[key] is None and key in defaults:
|
|
interface_merged[key] = defaults[key]
|
|
|
|
return interface_merged
|
|
|
|
|
|
def _check_interface_vlan_id(op, interface, ihost, from_profile=False):
|
|
# Check vlan_id
|
|
if 'vlan_id' in interface.keys() and interface['vlan_id'] is not None:
|
|
if not str(interface['vlan_id']).isdigit():
|
|
raise wsme.exc.ClientSideError(_("VLAN id is an integer value."))
|
|
|
|
interface['vlan_id'] = int(interface['vlan_id'])
|
|
if interface['vlan_id'] < 1 or interface['vlan_id'] > 4094:
|
|
raise wsme.exc.ClientSideError(_("VLAN id must be between 1 and 4094."))
|
|
else:
|
|
interface['vlan_id'] = six.text_type(interface['vlan_id'])
|
|
return interface
|
|
|
|
|
|
def _check_interface_name(op, interface, ihost, from_profile=False):
|
|
ihost_id = interface['forihostid']
|
|
ifname = interface['ifname']
|
|
iftype = interface['iftype']
|
|
|
|
# Check for ifname that has only spaces
|
|
if ifname and not ifname.strip():
|
|
raise wsme.exc.ClientSideError(_("Interface name cannot be "
|
|
"whitespace."))
|
|
# Check that ifname contains only lower case
|
|
if not ifname.islower():
|
|
raise wsme.exc.ClientSideError(_("Interface name must be in "
|
|
"lower case."))
|
|
|
|
# Check that the ifname is the right character length
|
|
# Account for VLAN interfaces
|
|
iflen = MAX_IFNAME_LEN
|
|
if iftype == constants.INTERFACE_TYPE_VLAN:
|
|
iflen = iflen + MAX_VLAN_ID_LEN
|
|
if ifname and len(ifname) > iflen:
|
|
raise wsme.exc.ClientSideError(_("Interface {} has name length "
|
|
"greater than {}.".
|
|
format(ifname, iflen)))
|
|
|
|
# Check for invalid characters
|
|
vlan_id = None
|
|
if iftype == constants.INTERFACE_TYPE_VLAN:
|
|
vlan_id = interface['vlan_id']
|
|
invalidChars = set(string.punctuation.replace("_", ""))
|
|
if vlan_id is not None:
|
|
# Allow VLAN interfaces to have "." in the name
|
|
invalidChars.remove(".")
|
|
if any(char in invalidChars for char in ifname):
|
|
msg = _("Cannot use special characters in interface name.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# ifname must be unique within the host
|
|
if op == "add":
|
|
this_interface_id = 0
|
|
else:
|
|
this_interface_id = interface['id']
|
|
interface_list = pecan.request.dbapi.iinterface_get_all(
|
|
forihostid=ihost_id)
|
|
for i in interface_list:
|
|
if i.id == this_interface_id:
|
|
continue
|
|
if i.ifname == ifname:
|
|
raise wsme.exc.ClientSideError(_("Name must be unique."))
|
|
return interface
|
|
|
|
|
|
def _check_interface_mtu(interface, ihost, from_profile=False):
|
|
# Check imtu
|
|
if 'imtu' in interface.keys() and interface['imtu'] is not None:
|
|
if not str(interface['imtu']).isdigit():
|
|
raise wsme.exc.ClientSideError(_("MTU is an integer value."))
|
|
|
|
interface['imtu'] = int(interface['imtu'])
|
|
utils.validate_mtu(interface['imtu'])
|
|
return interface
|
|
|
|
|
|
def _check_interface_sriov(interface, ihost, from_profile=False):
|
|
if 'ifclass' in interface.keys() and not interface['ifclass']:
|
|
return interface
|
|
|
|
if (interface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV and
|
|
'sriov_numvfs' not in interface.keys()):
|
|
raise wsme.exc.ClientSideError(_("A network type of pci-sriov must specify "
|
|
"a number for SR-IOV VFs."))
|
|
|
|
if ('sriov_numvfs' in interface.keys() and interface['sriov_numvfs']
|
|
is not None and int(interface['sriov_numvfs']) > 0 and
|
|
('ifclass' not in interface.keys() or
|
|
interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV)):
|
|
raise wsme.exc.ClientSideError(_("Number of SR-IOV VFs is specified "
|
|
"but interface class is not "
|
|
"pci-sriov."))
|
|
|
|
if ('ifclass' in interface.keys() and
|
|
interface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV and
|
|
'sriov_numvfs' in interface.keys()):
|
|
|
|
if interface['sriov_numvfs'] is None:
|
|
raise wsme.exc.ClientSideError(_("Value for number of SR-IOV VFs must be specified."))
|
|
|
|
if not str(interface['sriov_numvfs']).isdigit():
|
|
raise wsme.exc.ClientSideError(_("Value for number of SR-IOV VFs is an integer value."))
|
|
|
|
if interface['sriov_numvfs'] <= 0:
|
|
raise wsme.exc.ClientSideError(_("Value for number of SR-IOV VFs must be > 0."))
|
|
|
|
ports = pecan.request.dbapi.ethernet_port_get_all(hostid=ihost['id'])
|
|
port_list = [
|
|
(p.name, p.sriov_totalvfs, p.driver) for p in ports
|
|
if p.interface_id and p.interface_id == interface['id']
|
|
]
|
|
|
|
if len(port_list) != 1:
|
|
raise wsme.exc.ClientSideError(_("At most one port must be enabled."))
|
|
|
|
sriov_totalvfs = port_list[0][1]
|
|
if sriov_totalvfs is None or sriov_totalvfs == 0:
|
|
raise wsme.exc.ClientSideError(_("SR-IOV can't be configured on this interface"))
|
|
|
|
if int(interface['sriov_numvfs']) > sriov_totalvfs:
|
|
raise wsme.exc.ClientSideError(_("The interface support a maximum of %s VFs" % sriov_totalvfs))
|
|
|
|
driver = port_list[0][2]
|
|
if driver is None or not driver:
|
|
raise wsme.exc.ClientSideError(_("Corresponding port has invalid driver"))
|
|
|
|
return interface
|
|
|
|
|
|
def _check_host(ihost):
|
|
if utils.is_aio_simplex_host_unlocked(ihost):
|
|
raise wsme.exc.ClientSideError(_("Host must be locked."))
|
|
elif ihost['administrative'] != 'locked' and not \
|
|
utils.is_host_simplex_controller(ihost):
|
|
unlocked = False
|
|
current_ihosts = pecan.request.dbapi.ihost_get_list()
|
|
for h in current_ihosts:
|
|
if h['administrative'] != 'locked' and h['hostname'] != ihost['hostname']:
|
|
unlocked = True
|
|
if unlocked:
|
|
raise wsme.exc.ClientSideError(_("Host must be locked."))
|
|
|
|
|
|
def _valid_network_types():
|
|
valid_types = set(VALID_NETWORK_TYPES)
|
|
system_mode = utils.get_system_mode()
|
|
|
|
if system_mode == constants.SYSTEM_MODE_SIMPLEX:
|
|
valid_types -= set([constants.NETWORK_TYPE_INFRA])
|
|
return list(valid_types)
|
|
|
|
|
|
def _check_network_type_validity(networktypelist):
|
|
if any(nt not in _valid_network_types() for nt in networktypelist):
|
|
msg = (_("Network type list may only contain one or more of these "
|
|
"values: {}").format(', '.join(_valid_network_types())))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_network_type_and_host_type(ihost, networktypelist):
|
|
for nt in DATA_NETWORK_TYPES:
|
|
if (nt in networktypelist and
|
|
constants.WORKER not in ihost['subfunctions']):
|
|
msg = _("The '%s' network type is only supported on nodes "
|
|
"supporting worker functions" % nt)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
if (constants.NETWORK_TYPE_OAM in networktypelist and
|
|
ihost['personality'] != constants.CONTROLLER):
|
|
msg = _("The '%s' network type is only supported on controller nodes." %
|
|
constants.NETWORK_TYPE_OAM)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
if (constants.NETWORK_TYPE_INFRA in networktypelist and
|
|
utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX):
|
|
msg = _("The '%s' network type is not supported on simplex nodes." %
|
|
constants.NETWORK_TYPE_INFRA)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_network_type_and_interface_type(interface, networktypelist):
|
|
if interface['iftype'] == 'vlan':
|
|
if constants.NETWORK_TYPE_NONE in networktypelist:
|
|
msg = _("VLAN interfaces cannot have an interface class of %s." %
|
|
constants.NETWORK_TYPE_NONE)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
if (any(nt in networktypelist for nt in PCI_NETWORK_TYPES) and
|
|
interface['iftype'] != "ethernet"):
|
|
msg = (_("The {} network types are only valid on Ethernet interfaces").
|
|
format(', '.join(PCI_NETWORK_TYPES)))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_network_type_duplicates(ihost, interface, networktypelist):
|
|
# Check that we are not creating duplicate interface types
|
|
interfaces = pecan.request.dbapi.iinterface_get_by_ihost(ihost['uuid'])
|
|
for host_interface in interfaces:
|
|
if not host_interface['networks']:
|
|
continue
|
|
host_networktypelist = []
|
|
for network_id in host_interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
host_networktypelist.append(network.type)
|
|
|
|
for nt in interface_network.NONDUPLICATE_NETWORK_TYPES:
|
|
if nt in host_networktypelist and nt in networktypelist:
|
|
if host_interface['uuid'] != interface['uuid']:
|
|
msg = _("An interface with '%s' network type is "
|
|
"already provisioned on this node" % nt)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_interface_class_transition(interface, existing_interface):
|
|
if not existing_interface:
|
|
return
|
|
ifclass = interface['ifclass']
|
|
existing_ifclass = existing_interface['ifclass']
|
|
if ifclass == existing_ifclass:
|
|
return
|
|
if (ifclass and
|
|
existing_interface[
|
|
'ifclass'] == constants.INTERFACE_CLASS_PLATFORM and
|
|
existing_interface['used_by'] and
|
|
existing_interface['networks']):
|
|
msg = _("The class of an interface with platform networks cannot "
|
|
"be changed to %s since it is being used by %s" %
|
|
(ifclass, existing_interface['used_by']))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
elif (ifclass and ifclass == constants.INTERFACE_CLASS_PLATFORM and
|
|
existing_interface['ifclass'] in NEUTRON_INTERFACE_CLASS and
|
|
existing_interface['used_by']):
|
|
msg = _("The class of a non-platform interface cannot "
|
|
"be changed to platform since it is being used by %s" %
|
|
existing_interface['used_by'])
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_network_type_and_interface_name(interface, networktypelist):
|
|
if (utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX and
|
|
constants.NETWORK_TYPE_NONE in networktypelist and
|
|
interface['ifname'] == constants.LOOPBACK_IFNAME):
|
|
msg = _("The loopback interface cannot be changed for an all-in-one "
|
|
"simplex system")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_network_type(op, interface, ihost, existing_interface):
|
|
networktypelist = []
|
|
if interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
networktypelist.append(network.type)
|
|
elif interface['ifclass']:
|
|
networktypelist.append(interface['ifclass'])
|
|
else:
|
|
networktypelist.append(constants.INTERFACE_CLASS_NONE)
|
|
|
|
_check_network_type_validity(networktypelist)
|
|
_check_interface_class_transition(interface, existing_interface)
|
|
_check_network_type_and_host_type(ihost, networktypelist)
|
|
_check_network_type_and_interface_type(interface, networktypelist)
|
|
_check_network_type_duplicates(ihost, interface, networktypelist)
|
|
_check_network_type_and_interface_name(interface, networktypelist)
|
|
|
|
|
|
def _check_network_type_and_port(interface, ihost,
|
|
interface_port,
|
|
host_port,
|
|
networktypelist):
|
|
if interface_port.pciaddr == host_port.pciaddr and \
|
|
interface_port.dev_id != host_port.dev_id:
|
|
pif = pecan.request.dbapi.iinterface_get(host_port.interface_id)
|
|
if interface['id'] == pif['id']:
|
|
return
|
|
# shared devices cannot be assigned to a data and non-data
|
|
# interface at the same time
|
|
pif_networktypelist = []
|
|
if pif.networktype is None and pif.used_by:
|
|
for name in pif.used_by:
|
|
used_by_if = pecan.request.dbapi.iinterface_get(name,
|
|
ihost['uuid'])
|
|
if used_by_if and used_by_if.networktype:
|
|
pif_networktypelist = cutils.get_network_type_list(used_by_if)
|
|
elif pif.networktype:
|
|
pif_networktypelist = cutils.get_network_type_list(pif)
|
|
if (pif_networktypelist and
|
|
((constants.NETWORK_TYPE_DATA in pif_networktypelist and
|
|
constants.NETWORK_TYPE_DATA not in networktypelist) or
|
|
(constants.NETWORK_TYPE_DATA not in pif_networktypelist and
|
|
constants.NETWORK_TYPE_DATA in networktypelist))):
|
|
msg = (_("Shared device %(device)s cannot be shared "
|
|
"with different network types when device "
|
|
"is associated with a data network type") %
|
|
{'device': interface_port.pciaddr})
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_interface_class(interface, existing_interface):
|
|
if not interface['ifclass'] or interface['ifclass'] == constants.INTERFACE_CLASS_NONE:
|
|
return
|
|
|
|
if interface['ifclass'] not in VALID_INTERFACE_CLASS:
|
|
msg = (_("Invalid interface class %s" % interface['ifclass']))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
if interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
if network.type not in constants.PLATFORM_NETWORK_TYPES:
|
|
msg = (_("Invalid network type %s for interface class %s" %
|
|
(network.type, interface['ifclass'])))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
if (interface['ifclass'] in NEUTRON_INTERFACE_CLASS and
|
|
interface['networks']):
|
|
msg = _("Associating platform network to interface with %s class "
|
|
"is not allowed" % interface['ifclass'])
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_address_mode(op, interface, ihost, existing_interface):
|
|
# Check for valid values:
|
|
interface_id = interface['id']
|
|
ipv4_mode = interface.get('ipv4_mode')
|
|
ipv6_mode = interface.get('ipv6_mode')
|
|
object_utils.ipv4_mode_or_none(ipv4_mode)
|
|
object_utils.ipv6_mode_or_none(ipv6_mode)
|
|
|
|
# Check for supported interface network types
|
|
networktypelist = []
|
|
if interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
networktypelist.append(network.type)
|
|
elif interface['ifclass']:
|
|
networktypelist.append(interface['ifclass'])
|
|
else:
|
|
networktypelist.append(constants.INTERFACE_CLASS_NONE)
|
|
|
|
if not any(network_type in address.ALLOWED_NETWORK_TYPES
|
|
for network_type in networktypelist):
|
|
if (ipv4_mode and ipv4_mode != constants.IPV4_DISABLED):
|
|
raise exception.AddressModeOnlyOnSupportedTypes(
|
|
types=", ".join(address.ALLOWED_NETWORK_TYPES))
|
|
if (ipv6_mode and ipv6_mode != constants.IPV6_DISABLED):
|
|
raise exception.AddressModeOnlyOnSupportedTypes(
|
|
types=", ".join(address.ALLOWED_NETWORK_TYPES))
|
|
|
|
# Check for infrastructure specific requirements
|
|
if any(network_type == constants.NETWORK_TYPE_INFRA
|
|
for network_type in networktypelist):
|
|
if ipv4_mode != constants.IPV4_STATIC:
|
|
if ipv6_mode != constants.IPV6_STATIC:
|
|
raise exception.AddressModeMustBeStaticOnInfra()
|
|
|
|
# Check for valid combinations of mode+pool
|
|
ipv4_pool = interface.get('ipv4_pool')
|
|
ipv6_pool = interface.get('ipv6_pool')
|
|
if ipv4_mode != constants.IPV4_POOL and ipv4_pool:
|
|
raise exception.AddressPoolRequiresAddressMode(
|
|
family=constants.IP_FAMILIES[constants.IPV4_FAMILY])
|
|
|
|
if ipv4_mode == constants.IPV4_POOL:
|
|
if not ipv4_pool:
|
|
raise exception.AddressPoolRequired(
|
|
family=constants.IP_FAMILIES[constants.IPV4_FAMILY])
|
|
pool = pecan.request.dbapi.address_pool_get(ipv4_pool)
|
|
if pool['family'] != constants.IPV4_FAMILY:
|
|
raise exception.AddressPoolFamilyMismatch()
|
|
# Convert to UUID
|
|
ipv4_pool = pool['uuid']
|
|
interface['ipv4_pool'] = ipv4_pool
|
|
|
|
if ipv6_mode != constants.IPV6_POOL and ipv6_pool:
|
|
raise exception.AddressPoolRequiresAddressMode(
|
|
family=constants.IP_FAMILIES[constants.IPV6_FAMILY])
|
|
|
|
if ipv6_mode == constants.IPV6_POOL:
|
|
if not ipv6_pool:
|
|
raise exception.AddressPoolRequired(
|
|
family=constants.IP_FAMILIES[constants.IPV6_FAMILY])
|
|
pool = pecan.request.dbapi.address_pool_get(ipv6_pool)
|
|
if pool['family'] != constants.IPV6_FAMILY:
|
|
raise exception.AddressPoolFamilyMismatch()
|
|
# Convert to UUID
|
|
ipv6_pool = pool['uuid']
|
|
interface['ipv6_pool'] = ipv6_pool
|
|
|
|
if existing_interface:
|
|
# Check for valid transitions
|
|
existing_ipv4_mode = existing_interface.get('ipv4_mode')
|
|
if ipv4_mode != existing_ipv4_mode:
|
|
if (existing_ipv4_mode == constants.IPV4_STATIC and
|
|
(ipv4_mode and ipv4_mode != constants.IPV4_DISABLED)):
|
|
if pecan.request.dbapi.addresses_get_by_interface(
|
|
interface_id, constants.IPV4_FAMILY):
|
|
raise exception.AddressesStillExist(
|
|
family=constants.IP_FAMILIES[constants.IPV4_FAMILY])
|
|
|
|
existing_ipv6_mode = existing_interface.get('ipv6_mode')
|
|
if ipv6_mode != existing_ipv6_mode:
|
|
if (existing_ipv6_mode == constants.IPV6_STATIC and
|
|
(ipv6_mode and ipv6_mode != constants.IPV6_DISABLED)):
|
|
if pecan.request.dbapi.addresses_get_by_interface(
|
|
interface_id, constants.IPV6_FAMILY):
|
|
raise exception.AddressesStillExist(
|
|
family=constants.IP_FAMILIES[constants.IPV6_FAMILY])
|
|
|
|
|
|
def _check_networks(interface):
|
|
NONASSIGNABLE_WITH_OAM = [constants.NETWORK_TYPE_MGMT,
|
|
constants.NETWORK_TYPE_PXEBOOT,
|
|
constants.NETWORK_TYPE_INFRA]
|
|
ifclass = interface['ifclass']
|
|
networks = interface['networks']
|
|
if ifclass == constants.INTERFACE_CLASS_PLATFORM and len(networks) > 1:
|
|
networktypelist = []
|
|
for network_id in networks:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
networktypelist.append(network.type)
|
|
if constants.NETWORK_TYPE_PXEBOOT in networktypelist:
|
|
msg = _("An interface assigned with a network of "
|
|
"type '%s' cannot contain additional networks."
|
|
% constants.NETWORK_TYPE_PXEBOOT)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
elif any(network_type in NONASSIGNABLE_WITH_OAM
|
|
for network_type in networktypelist) and \
|
|
any(network_type == constants.NETWORK_TYPE_OAM
|
|
for network_type in networktypelist):
|
|
msg = _("An interface assigned with a network of "
|
|
"type '%s' cannot assign any networks "
|
|
"of type '%s'."
|
|
% (constants.NETWORK_TYPE_OAM, NONASSIGNABLE_WITH_OAM))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _check_interface_data(op, interface, ihost, existing_interface):
|
|
# Get data
|
|
ihost_id = interface['forihostid']
|
|
ihost_uuid = interface['ihost_uuid']
|
|
providernetworks = interface['providernetworks']
|
|
ifclass = interface['ifclass']
|
|
networktypelist = []
|
|
if ifclass == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in interface['networks']:
|
|
platform_network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
networktypelist.append(platform_network.type)
|
|
elif ifclass:
|
|
networktypelist.append(ifclass)
|
|
else:
|
|
networktypelist.append(constants.INTERFACE_CLASS_NONE)
|
|
|
|
# Get providernet dict
|
|
all_providernetworks = _neutron_providernet_list()
|
|
|
|
# Check interface name for validity
|
|
_check_interface_name(op, interface, ihost, existing_interface)
|
|
|
|
if op == "add":
|
|
this_interface_id = 0
|
|
else:
|
|
this_interface_id = interface['id']
|
|
|
|
iftype = interface['iftype']
|
|
|
|
# Check vlan interfaces
|
|
if iftype == constants.INTERFACE_TYPE_VLAN:
|
|
vlan_id = interface['vlan_id']
|
|
lower_ifname = interface['uses'][0]
|
|
lower_iface = (
|
|
pecan.request.dbapi.iinterface_get(lower_ifname, ihost_uuid))
|
|
if lower_iface['iftype'] == constants.INTERFACE_TYPE_VLAN:
|
|
msg = _("VLAN interfaces cannot be created over existing "
|
|
"VLAN interfaces")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
vlans = _get_interface_vlans(ihost_uuid, lower_iface)
|
|
if op != "modify" and str(vlan_id) in vlans.split(","):
|
|
msg = _("VLAN id %s already in use on interface %s" %
|
|
(str(vlan_id), lower_iface['ifname']))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
if (lower_iface['ifclass'] == constants.INTERFACE_CLASS_DATA and
|
|
interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM):
|
|
msg = _("Platform VLAN interface cannot be created over a data "
|
|
"interface ")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
elif (lower_iface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM and
|
|
interface['ifclass'] == constants.INTERFACE_CLASS_DATA):
|
|
msg = _("Data VLAN interface cannot be created over a platform "
|
|
"interface ")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Check if the 'uses' interface is already used by another AE or VLAN
|
|
# interface
|
|
interface_list = pecan.request.dbapi.iinterface_get_all(
|
|
forihostid=ihost_id)
|
|
for i in interface_list:
|
|
if i.id == this_interface_id:
|
|
continue
|
|
if (iftype != constants.INTERFACE_TYPE_ETHERNET and
|
|
i.uses is not None):
|
|
for p in i.uses:
|
|
parent = pecan.request.dbapi.iinterface_get(p, ihost_uuid)
|
|
if (parent.uuid in interface['uses'] or
|
|
parent.ifname in interface['uses']):
|
|
if i.iftype == constants.INTERFACE_TYPE_AE:
|
|
msg = _("Interface '{}' is already used by another"
|
|
" AE interface '{}'".format(p, i.ifname))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
elif (i.iftype == constants.INTERFACE_TYPE_VLAN and
|
|
iftype != constants.INTERFACE_TYPE_VLAN):
|
|
msg = _("Interface '{}' is already used by another"
|
|
" VLAN interface '{}'".format(p, i.ifname))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Ensure that the interfaces being used in the AE interface
|
|
# are originally set to None when creating the AE interface
|
|
if iftype == constants.INTERFACE_TYPE_AE:
|
|
for i in interface['uses']:
|
|
iface_lower = pecan.request.dbapi.iinterface_get(i, ihost_uuid)
|
|
if iface_lower.ifclass:
|
|
msg = _("All interfaces being used in an AE interface "
|
|
"must have the interface class set to 'none'.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Ensure that the interfaces being used in the AE interface
|
|
# are not changed after the AE interface has been created
|
|
if interface['used_by']:
|
|
for i in interface['used_by']:
|
|
iface = pecan.request.dbapi.iinterface_get(i, ihost_uuid)
|
|
if iface.iftype == constants.INTERFACE_TYPE_AE and \
|
|
interface['ifclass']:
|
|
msg = _("Interface '{}' is being used by interface '{}' "
|
|
"as an AE interface and therefore the interface "
|
|
"class cannot be changed from 'none'.".format(interface['ifname'],
|
|
iface.ifname))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# check interface class validity
|
|
_check_interface_class(interface, existing_interface)
|
|
|
|
# check networktype combinations and transitions for validity
|
|
_check_network_type(op, interface, ihost, existing_interface)
|
|
|
|
# check to ensure that the interface assigned with an OAM or
|
|
# PXEBOOT network has no other networks
|
|
_check_networks(interface)
|
|
|
|
# check mode/pool combinations and transitions for validity
|
|
_check_address_mode(op, interface, ihost, existing_interface)
|
|
|
|
# Make sure txhashpolicy for data is layer2
|
|
aemode = interface['aemode']
|
|
txhashpolicy = interface['txhashpolicy']
|
|
|
|
if aemode in ['balanced', '802.3ad'] and not txhashpolicy:
|
|
msg = _("Device interface with interface type 'aggregated ethernet' "
|
|
"in 'balanced' or '802.3ad' mode require a valid Tx Hash "
|
|
"Policy.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
elif aemode in ['active_standby'] and txhashpolicy is not None:
|
|
msg = _("Device interface with interface type 'aggregated ethernet' "
|
|
"in '%s' mode should not specify a Tx Hash Policy." % aemode)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Make sure interface type is valid
|
|
supported_type = [constants.INTERFACE_TYPE_AE,
|
|
constants.INTERFACE_TYPE_VLAN,
|
|
constants.INTERFACE_TYPE_ETHERNET]
|
|
# only allows add operation for the virtual interface
|
|
if op == 'add':
|
|
supported_type.append(constants.INTERFACE_TYPE_VIRTUAL)
|
|
if not iftype or iftype not in supported_type:
|
|
msg = (_("Device interface type must be one of "
|
|
"{}").format(', '.join(supported_type)))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Make sure network type 'data' with if type 'ae' can only be in ae mode
|
|
# 'active_standby', 'balanced', or '802.3ad', and can only support a
|
|
# txhashpolicy of 'layer2'.
|
|
for nt in DATA_NETWORK_TYPES:
|
|
if iftype == 'ae' and nt in networktypelist:
|
|
if aemode not in ['balanced', 'active_standby', '802.3ad']:
|
|
msg = _("Device interface with network type '%s', and interface "
|
|
"type 'aggregated ethernet' must be in mode "
|
|
"'active_standby', 'balanced', or '802.3ad'." % nt)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
if aemode in ['balanced', '802.3ad'] and txhashpolicy != 'layer2':
|
|
msg = _("Device interface with network type '%s', and interface "
|
|
"type 'aggregated ethernet' must have a Tx Hash Policy of "
|
|
"'layer2'." % nt)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Make sure network type 'mgmt', with if type 'ae',
|
|
# can only be in ae mode 'active_standby' or '802.3ad'
|
|
valid_mgmt_aemode = ['802.3ad', 'active_standby']
|
|
if (constants.NETWORK_TYPE_MGMT in networktypelist and iftype == 'ae' and
|
|
aemode not in valid_mgmt_aemode):
|
|
msg = _("Device interface with network type {}, and interface "
|
|
"type 'aggregated ethernet' must be in mode {}").format(
|
|
(str(networktypelist)), ', '.join(valid_mgmt_aemode))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Make sure network type 'oam' or 'infra', with if type 'ae',
|
|
# can only be in ae mode 'active_standby' or 'balanced'
|
|
if (any(network in [constants.NETWORK_TYPE_OAM, constants.NETWORK_TYPE_INFRA] for network in networktypelist) and
|
|
iftype == 'ae' and (aemode not in VALID_AEMODE_LIST)):
|
|
msg = _("Device interface with network type '%s', and interface "
|
|
"type 'aggregated ethernet' must be in mode 'active_standby' "
|
|
"or 'balanced' or '802.3ad'." % (str(networktypelist)))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Ensure that data and non-data interfaces are not using the same
|
|
# shared device
|
|
if (iftype != constants.INTERFACE_TYPE_VLAN and
|
|
iftype != constants.INTERFACE_TYPE_VIRTUAL):
|
|
port_list_host = \
|
|
pecan.request.dbapi.ethernet_port_get_all(hostid=ihost['id'])
|
|
for name in interface['uses']:
|
|
uses_if = pecan.request.dbapi.iinterface_get(name, ihost['uuid'])
|
|
uses_if_port = pecan.request.dbapi.ethernet_port_get_all(
|
|
interfaceid=uses_if.id)
|
|
for interface_port in uses_if_port:
|
|
for host_port in port_list_host:
|
|
_check_network_type_and_port(interface, ihost,
|
|
interface_port,
|
|
host_port,
|
|
networktypelist)
|
|
|
|
# Ensure a valid providernetwork is specified
|
|
# Ensure at least one providernetwork is selected for 'data',
|
|
# or interface (when SDN L3 services are enabled)
|
|
# and none for 'oam', 'mgmt' and 'infra'
|
|
# Ensure uniqueness wrt the providernetworks
|
|
if (_neutron_providernet_extension_supported() and
|
|
interface['ifclass'] in NEUTRON_INTERFACE_CLASS):
|
|
if not providernetworks:
|
|
msg = _("At least one provider network must be selected.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
if len(providernetworks) > MAX_PROVIDERNETWORK_LEN:
|
|
msg = _("Provider network list must not exceed %d characters." %
|
|
MAX_PROVIDERNETWORK_LEN)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
providernetworks_list = providernetworks.split(',')
|
|
for pn in [n.strip() for n in providernetworks_list]:
|
|
if pn not in all_providernetworks.keys():
|
|
msg = _("Provider network '%s' does not exist." % pn)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
if providernetworks_list.count(pn) > 1:
|
|
msg = (_("Specifying duplicate provider network '%(name)s' "
|
|
"is not permitted") % {'name': pn})
|
|
raise wsme.exc.ClientSideError(msg)
|
|
providernet = all_providernetworks[pn]
|
|
if iftype == constants.INTERFACE_TYPE_VLAN:
|
|
if providernet['type'] == 'vlan':
|
|
msg = _("VLAN based provider network '%s' cannot be "
|
|
"assigned to a VLAN interface" % pn)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# If pxeboot, Mgmt, Infra network types are consolidated
|
|
# with a data network type on the same interface,
|
|
# in which case, they would be the primary network
|
|
# type. Ensure that the only provider type that
|
|
# can be assigned is VLAN.
|
|
if (providernet['type'] != constants.NEUTRON_PROVIDERNET_VLAN and
|
|
ifclass not in NEUTRON_NETWORK_TYPES):
|
|
msg = _("Provider network '%s' of type '%s' cannot be assigned "
|
|
"to an interface with interface class '%s'"
|
|
% (pn, providernet['type'], ifclass))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# This ensures that a specific provider network type can
|
|
# only be assigned to 1 data interface. Such as the case of
|
|
# when only 1 vxlan provider is required when SDN is enabled
|
|
if constants.NETWORK_TYPE_DATA in networktypelist and interface_list:
|
|
for pn in [n.strip() for n in providernetworks.split(',')]:
|
|
for i in interface_list:
|
|
if i.id == this_interface_id:
|
|
continue
|
|
if not i.ifclass or not i.providernetworks:
|
|
continue
|
|
if constants.NETWORK_TYPE_DATA != i.ifclass:
|
|
continue
|
|
other_providernetworks = i.providernetworks.split(',')
|
|
if pn in other_providernetworks:
|
|
msg = _("Data interface %(ifname)s is already "
|
|
"attached to this Provider Network: "
|
|
"%(network)s." %
|
|
{'ifname': i.ifname, 'network': pn})
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Send the interface and provider network details to neutron for
|
|
# additional validation.
|
|
_neutron_bind_interface(ihost, interface, test=True)
|
|
# Send the shared data interface(s) and provider networks details to
|
|
# neutron for additional validation, if required
|
|
_update_shared_interface_neutron_bindings(ihost, interface, test=True)
|
|
|
|
elif (not _neutron_providernet_extension_supported() and
|
|
any(nt in PCI_NETWORK_TYPES for nt in networktypelist)):
|
|
# When the neutron implementation is not our own and it does not
|
|
# support our provider network extension we still want to do minimal
|
|
# validation of the provider network list but we cannot do more
|
|
# complex validation because we do not have any additional information
|
|
# about the provider networks.
|
|
if not providernetworks:
|
|
msg = _("At least one provider network must be selected.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
elif (interface['ifclass'] and
|
|
interface['ifclass'] not in NEUTRON_INTERFACE_CLASS and
|
|
not existing_interface):
|
|
if providernetworks is not None:
|
|
msg = _("Provider network(s) not supported "
|
|
"for non-data interfaces. (%s) (%s)" % (interface['ifclass'], str(existing_interface)))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
elif (_neutron_providernet_extension_supported() or
|
|
interface['ifclass'] not in NEUTRON_INTERFACE_CLASS):
|
|
interface['providernetworks'] = None
|
|
|
|
# check MTU
|
|
if interface['iftype'] == constants.INTERFACE_TYPE_VLAN:
|
|
vlan_mtu = interface['imtu']
|
|
for name in interface['uses']:
|
|
parent = pecan.request.dbapi.iinterface_get(name, ihost_uuid)
|
|
if int(vlan_mtu) > int(parent['imtu']):
|
|
msg = _("VLAN MTU (%s) cannot be larger than MTU of "
|
|
"underlying interface (%s)" % (vlan_mtu, parent['imtu']))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
elif interface['used_by']:
|
|
mtus = _get_interface_mtus(ihost_uuid, interface)
|
|
for mtu in mtus:
|
|
if int(interface['imtu']) < int(mtu):
|
|
msg = _("Interface MTU (%s) cannot be smaller than the "
|
|
"interface MTU (%s) using this interface" %
|
|
(interface['imtu'], mtu))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Check if infra exists on controller, if it doesn't then fail
|
|
if (ihost['personality'] != constants.CONTROLLER and
|
|
constants.NETWORK_TYPE_INFRA in networktypelist):
|
|
host_list = pecan.request.dbapi.ihost_get_by_personality(
|
|
personality=constants.CONTROLLER)
|
|
infra_on_controller = False
|
|
for h in host_list:
|
|
# find any interface in controller host that is of type infra
|
|
interfaces = pecan.request.dbapi.iinterface_get_by_ihost(ihost=h['uuid'])
|
|
for host_interface in interfaces:
|
|
if host_interface['ifclass']:
|
|
hi_networktypelist = []
|
|
if host_interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in host_interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
hi_networktypelist.append(network.type)
|
|
else:
|
|
hi_networktypelist.append(host_interface['ifclass'])
|
|
if constants.NETWORK_TYPE_INFRA in hi_networktypelist:
|
|
infra_on_controller = True
|
|
break
|
|
if infra_on_controller is True:
|
|
break
|
|
if not infra_on_controller:
|
|
msg = _("Interface %s does not have associated"
|
|
" infra interface on controller." % interface['ifname'])
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
return interface
|
|
|
|
|
|
def _check_ports(op, interface, ihost, ports):
|
|
port_list = []
|
|
|
|
if ports:
|
|
port_list = ports.split(',')
|
|
|
|
if op == "add":
|
|
this_interface_id = 0
|
|
else:
|
|
this_interface_id = interface['id']
|
|
|
|
# Basic checks on number of ports for Ethernet vs Aggregated Ethernet
|
|
if not port_list or len(port_list) == 0:
|
|
raise wsme.exc.ClientSideError(_("A port must be selected."))
|
|
elif (interface['iftype'] == constants.INTERFACE_TYPE_ETHERNET and
|
|
len(port_list) > 1):
|
|
raise wsme.exc.ClientSideError(_(
|
|
"For Ethernet, select a single port."))
|
|
|
|
# Make sure that no other interface is currently using these ports
|
|
host_ports = pecan.request.dbapi.ethernet_port_get_all(hostid=ihost['id'])
|
|
for p in host_ports:
|
|
if p.name in port_list or p.uuid in port_list:
|
|
if p.interface_id and p.interface_id != this_interface_id:
|
|
pif = pecan.request.dbapi.iinterface_get(p.interface_id)
|
|
msg = _("Another interface %s is already using this port"
|
|
% pif.uuid)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# If someone enters name with spaces anywhere, such as " eth2", "eth2 "
|
|
# The the bottom line will prevent it
|
|
if p.name == "".join(interface['ifname'].split()):
|
|
|
|
if interface['iftype'] == 'ae':
|
|
msg = _("Aggregated Ethernet interface name cannot be '%s'. "
|
|
"An Aggregated Ethernet name must not be the same as"
|
|
" an existing port name. " % p.name)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
if (p.uuid not in port_list) and (p.name not in port_list):
|
|
msg = _("Interface name cannot be '%s'. Port name can be "
|
|
"used as interface name only if the interface uses"
|
|
" that port. " % p.name)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Check to see if the physical port actually exists
|
|
for p in port_list:
|
|
port_exists = False
|
|
for pTwo in host_ports:
|
|
if p == pTwo.name or p == pTwo.uuid:
|
|
# port exists
|
|
port_exists = True
|
|
break
|
|
|
|
if not port_exists:
|
|
# Port does not exist
|
|
msg = _("Port %s does not exist." % p)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Semantic check not needed as the node is locked
|
|
# Make sure the Boot IF is not removed from the management interface
|
|
# networktype = interface['networktype']
|
|
# if networktype == constants.NETWORK_TYPE_MGMT:
|
|
# for p in port_list:
|
|
# if (p.uuid in ports or p.name in ports) and p.bootp:
|
|
# break
|
|
# else:
|
|
# msg = _("The boot interface can NOT be removed from the mgmt interface.")
|
|
# raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Perform network type checks for shared PCI devices.
|
|
networktypelist = []
|
|
if interface['networktype']:
|
|
networktypelist = cutils.get_network_type_list(interface)
|
|
if constants.NETWORK_TYPE_NONE not in networktypelist:
|
|
for p in port_list:
|
|
interface_port = \
|
|
pecan.request.dbapi.ethernet_port_get(p, ihost['id'])
|
|
for host_port in host_ports:
|
|
_check_network_type_and_port(interface, ihost,
|
|
interface_port,
|
|
host_port,
|
|
networktypelist)
|
|
|
|
|
|
def _update_address_mode(interface, family, mode, pool):
|
|
interface_id = interface['id']
|
|
pool_id = pecan.request.dbapi.address_pool_get(pool)['id'] if pool else None
|
|
try:
|
|
# retrieve the existing value and compare
|
|
existing = pecan.request.dbapi.address_mode_query(
|
|
interface_id, family)
|
|
if existing.mode == mode:
|
|
if (mode != 'pool' or existing.pool_uuid == pool):
|
|
return
|
|
if existing.mode == 'pool' or (not mode or mode == 'disabled'):
|
|
pecan.request.dbapi.routes_destroy_by_interface(
|
|
interface_id, family)
|
|
pecan.request.dbapi.addresses_destroy_by_interface(
|
|
interface_id, family)
|
|
except exception.AddressModeNotFoundByFamily:
|
|
# continue and update DB with new record
|
|
pass
|
|
updates = {'family': family, 'mode': mode, 'address_pool_id': pool_id}
|
|
pecan.request.dbapi.address_mode_update(interface_id, updates)
|
|
|
|
|
|
def _delete_addressing(interface, family, existing_interface):
|
|
interface_id = interface['id']
|
|
pecan.request.dbapi.routes_destroy_by_interface(
|
|
interface_id, family)
|
|
for network_id in existing_interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
orig_networktype = network.type
|
|
if ((orig_networktype == constants.NETWORK_TYPE_OAM) or
|
|
(orig_networktype == constants.NETWORK_TYPE_PXEBOOT)):
|
|
pecan.request.dbapi.addresses_remove_interface_by_interface(
|
|
interface['id']
|
|
)
|
|
elif ((orig_networktype != constants.NETWORK_TYPE_MGMT) and
|
|
(orig_networktype != constants.NETWORK_TYPE_INFRA)):
|
|
pecan.request.dbapi.addresses_destroy_by_interface(
|
|
interface_id, family)
|
|
pecan.request.dbapi.address_modes_destroy_by_interface(
|
|
interface_id, family)
|
|
|
|
|
|
def _allocate_pool_address(interface_id, pool_uuid, address_name=None):
|
|
address_pool.AddressPoolController.assign_address(
|
|
interface_id, pool_uuid, address_name)
|
|
|
|
|
|
def _update_ipv6_address_mode(interface, mode=None, pool=None,
|
|
from_profile=False):
|
|
mode = interface['ipv6_mode'] if not mode else mode
|
|
pool = interface['ipv6_pool'] if not pool else pool
|
|
_update_address_mode(interface, constants.IPV6_FAMILY, mode, pool)
|
|
if mode == constants.IPV6_POOL and not from_profile:
|
|
_allocate_pool_address(interface['id'], pool)
|
|
|
|
|
|
def _update_ipv4_address_mode(interface, mode=None, pool=None,
|
|
interface_profile=False):
|
|
mode = interface['ipv4_mode'] if not mode else mode
|
|
pool = interface['ipv4_pool'] if not pool else pool
|
|
_update_address_mode(interface, constants.IPV4_FAMILY, mode, pool)
|
|
if mode == constants.IPV4_POOL and not interface_profile:
|
|
_allocate_pool_address(interface['id'], pool)
|
|
|
|
|
|
def _is_ipv4_address_mode_updated(interface, existing_interface):
|
|
if interface['ipv4_mode'] != existing_interface['ipv4_mode']:
|
|
return True
|
|
if interface['ipv4_pool'] != existing_interface['ipv4_pool']:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _is_ipv6_address_mode_updated(interface, existing_interface):
|
|
if interface['ipv6_mode'] != existing_interface['ipv6_mode']:
|
|
return True
|
|
if interface['ipv6_pool'] != existing_interface['ipv6_pool']:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _add_extended_attributes(ihost, interface, attributes):
|
|
"""
|
|
Adds additional attributes to a newly create interface database instance.
|
|
The attributes argument is actually the interface object as it was
|
|
received on the initial API post() request with some additional values
|
|
that got added before sending the object to the database.
|
|
"""
|
|
interface_data = interface.as_dict()
|
|
networktype = interface_data['networktype']
|
|
if networktype not in address.ALLOWED_NETWORK_TYPES:
|
|
# No need to create new address mode records if the interface type
|
|
# does not support it
|
|
return
|
|
if attributes.get('ipv4_mode'):
|
|
_update_ipv4_address_mode(interface_data,
|
|
attributes.get('ipv4_mode'),
|
|
attributes.get('ipv4_pool'),
|
|
attributes.get('interface_profile'))
|
|
if attributes.get('ipv6_mode'):
|
|
_update_ipv6_address_mode(interface_data,
|
|
attributes.get('ipv6_mode'),
|
|
attributes.get('ipv6_pool'),
|
|
attributes.get('interface_profile'))
|
|
|
|
|
|
def _update_ports(op, interface, ihost, ports):
|
|
port_list = ports.split(',')
|
|
|
|
if op == "add":
|
|
this_interface_id = 0
|
|
else:
|
|
this_interface_id = interface['id']
|
|
|
|
# Update Ports' iinterface_uuid attribute
|
|
host_ports = pecan.request.dbapi.ethernet_port_get_all(hostid=ihost['id'])
|
|
if port_list:
|
|
for p in host_ports:
|
|
# if new port associated
|
|
if (p.uuid in port_list or p.name in port_list) and \
|
|
not p.interface_id:
|
|
values = {'interface_id': interface['id']}
|
|
# else if old port disassociated
|
|
elif ((p.uuid not in port_list and p.name not in port_list) and
|
|
p.interface_id and p.interface_id == this_interface_id):
|
|
values = {'interface_id': None}
|
|
# else move on
|
|
else:
|
|
continue
|
|
try:
|
|
pecan.request.dbapi.port_update(p.uuid, values)
|
|
except exception.HTTPNotFound:
|
|
msg = _("Port update of interface uuid failed: host %s port %s"
|
|
% (ihost['hostname'], p.name))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _update_host_mgmt_address(host, interface):
|
|
"""Check if the host has a static management IP address assigned
|
|
and ensure the address is populated against the interface. Otherwise,
|
|
if using dynamic address allocation, then allocate an address
|
|
"""
|
|
|
|
mgmt_ip = utils.lookup_static_ip_address(
|
|
host.hostname, constants.NETWORK_TYPE_MGMT)
|
|
|
|
if mgmt_ip:
|
|
pecan.request.rpcapi.mgmt_ip_set_by_ihost(
|
|
pecan.request.context, host.uuid, mgmt_ip)
|
|
elif _dynamic_address_allocation():
|
|
mgmt_pool_uuid = pecan.request.dbapi.network_get_by_type(
|
|
constants.NETWORK_TYPE_MGMT
|
|
).pool_uuid
|
|
address_name = cutils.format_address_name(host.hostname,
|
|
constants.NETWORK_TYPE_MGMT)
|
|
_allocate_pool_address(interface['id'], mgmt_pool_uuid, address_name)
|
|
|
|
|
|
def _update_host_infra_address(host, interface):
|
|
"""Check if the host has a static infrastructure IP address assigned
|
|
and ensure the address is populated against the interface. Otherwise,
|
|
if using dynamic address allocation, then allocate an address
|
|
"""
|
|
infra_ip = utils.lookup_static_ip_address(
|
|
host.hostname, constants.NETWORK_TYPE_INFRA)
|
|
if infra_ip:
|
|
pecan.request.rpcapi.infra_ip_set_by_ihost(
|
|
pecan.request.context, host.uuid, infra_ip)
|
|
elif _dynamic_address_allocation():
|
|
infra_pool_uuid = pecan.request.dbapi.network_get_by_type(
|
|
constants.NETWORK_TYPE_INFRA
|
|
).pool_uuid
|
|
address_name = cutils.format_address_name(host.hostname,
|
|
constants.NETWORK_TYPE_INFRA)
|
|
_allocate_pool_address(interface['id'], infra_pool_uuid, address_name)
|
|
|
|
|
|
def _update_host_oam_address(host, interface):
|
|
if utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX:
|
|
address_name = cutils.format_address_name(constants.CONTROLLER_HOSTNAME,
|
|
constants.NETWORK_TYPE_OAM)
|
|
else:
|
|
address_name = cutils.format_address_name(host.hostname,
|
|
constants.NETWORK_TYPE_OAM)
|
|
address = pecan.request.dbapi.address_get_by_name(address_name)
|
|
if not interface['networktype']:
|
|
updates = {'interface_id': None}
|
|
else:
|
|
updates = {'interface_id': interface['id']}
|
|
pecan.request.dbapi.address_update(address.uuid, updates)
|
|
|
|
|
|
def _update_host_pxeboot_address(host, interface):
|
|
address_name = cutils.format_address_name(host.hostname,
|
|
constants.NETWORK_TYPE_PXEBOOT)
|
|
address = pecan.request.dbapi.address_get_by_name(address_name)
|
|
updates = {'interface_id': interface['id']}
|
|
pecan.request.dbapi.address_update(address.uuid, updates)
|
|
|
|
|
|
def _clean_providernetworks(providernetworks):
|
|
pn = [','.join(p['name']) for p in providernetworks]
|
|
return pn
|
|
|
|
|
|
"""
|
|
Params:
|
|
pn_all: all providernets stored in neutron
|
|
pn_names: providernets specified for this interface
|
|
|
|
Return:
|
|
pn_dict: a dictionary of providernets specified
|
|
for this interface: item format {name:body}
|
|
"""
|
|
|
|
|
|
def _get_providernetworksdict(pn_all, pn_names):
|
|
pn_dict = {}
|
|
if pn_names:
|
|
for name, body in pn_all.iteritems():
|
|
if name in pn_names.split(','):
|
|
pn_dict.update({name: body})
|
|
return pn_dict
|
|
|
|
|
|
def _get_interface_vlans(ihost_uuid, interface):
|
|
"""
|
|
Retrieve the VLAN id values (if any) that are dependent on this
|
|
interface.
|
|
"""
|
|
used_by = interface['used_by']
|
|
vlans = []
|
|
for ifname in used_by:
|
|
child = pecan.request.dbapi.iinterface_get(ifname, ihost_uuid)
|
|
if child.get('iftype') != constants.INTERFACE_TYPE_VLAN:
|
|
continue
|
|
vlan_id = child.get('vlan_id', 0)
|
|
if vlan_id:
|
|
vlans.append(str(vlan_id))
|
|
return ','.join(vlans)
|
|
|
|
|
|
def _get_interface_mtus(ihost_uuid, interface):
|
|
"""
|
|
Retrieve the MTU values of interfaces that are dependent on this
|
|
interface.
|
|
"""
|
|
used_by = interface['used_by']
|
|
mtus = []
|
|
for ifname in used_by:
|
|
child = pecan.request.dbapi.iinterface_get(ifname, ihost_uuid)
|
|
mtu = child.get('imtu', 0)
|
|
if mtu:
|
|
mtus.append(str(mtu))
|
|
return mtus
|
|
|
|
|
|
def _update_interface_mtu(ifname, host, mtu):
|
|
"""Update the MTU of the interface on this host with the supplied ifname"""
|
|
interface = pecan.request.dbapi.iinterface_get(ifname, host['uuid'])
|
|
values = {'imtu': mtu}
|
|
pecan.request.dbapi.iinterface_update(interface['uuid'], values)
|
|
|
|
|
|
def _get_shared_data_interfaces(ihost, interface):
|
|
"""
|
|
Retrieve a list of data interfaces, if any, that are dependent on
|
|
this interface (used_by) as well as the data interface(s) that
|
|
this interface depends on (uses).
|
|
"""
|
|
used_by = []
|
|
shared_data_interfaces = []
|
|
uses = interface['uses']
|
|
if uses:
|
|
for ifname in uses:
|
|
parent = pecan.request.dbapi.iinterface_get(ifname, ihost['uuid'])
|
|
used_by.extend(parent['used_by'])
|
|
interface_class = parent.get('ifclass', None)
|
|
if interface_class:
|
|
# This should only match 'data' interface class since that
|
|
# is the only type that can be shared on multiple interfaces.
|
|
if interface_class == constants.INTERFACE_CLASS_DATA:
|
|
shared_data_interfaces.append(parent)
|
|
else:
|
|
used_by = interface['used_by']
|
|
|
|
for ifname in used_by:
|
|
child = pecan.request.dbapi.iinterface_get(ifname, ihost['uuid'])
|
|
interface_class = child.get('ifclass', None)
|
|
if interface_class:
|
|
# This should only match 'data' interface class since that
|
|
# is the only type that can be shared on multiple interfaces.
|
|
if interface_class == constants.INTERFACE_CLASS_DATA:
|
|
shared_data_interfaces.append(child)
|
|
|
|
return shared_data_interfaces
|
|
|
|
|
|
def _neutron_host_extension_supported():
|
|
"""
|
|
Reports whether the neutron "host" extension is supported or not. This
|
|
indicator is used to determine whether certain neutron operations are
|
|
necessary or not. If it is not supported then this is an indication that
|
|
we are running against a vanilla openstack installation.
|
|
"""
|
|
return True
|
|
# TODO: This should be looking at the neutron extension list, but because
|
|
# our config file is not setup properly to have a different region on a per
|
|
# service basis we cannot.
|
|
#
|
|
# The code should like something like this:
|
|
#
|
|
# extensions = pecan.request.rpcapi.neutron_extension_list(
|
|
# pecan.request.context)
|
|
# return bool(constants.NEUTRON_HOST_ALIAS in extensions)
|
|
|
|
|
|
def _neutron_providernet_extension_supported():
|
|
"""
|
|
Reports whether the neutron "wrs-provider" extension is supported or not.
|
|
This indicator is used to determine whether certain neutron operations are
|
|
necessary or not. If it is not supported then this is an indication that
|
|
we are running against a vanilla openstack installation.
|
|
"""
|
|
# In the case of a kubernetes config, neutron may not be running, and
|
|
# sysinv should not rely on talking to containerized neutron.
|
|
if utils.is_kubernetes_config():
|
|
return False
|
|
|
|
return True
|
|
# TODO: This should be looking at the neutron extension list, but because
|
|
# our config file is not setup properly to have a different region on a per
|
|
# service basis we cannot.
|
|
#
|
|
# The code should like something like this:
|
|
#
|
|
# extensions = pecan.request.rpcapi.neutron_extension_list(
|
|
# pecan.request.context)
|
|
# return bool(constants.NEUTRON_WRS_PROVIDER_ALIAS in extensions)
|
|
|
|
|
|
def _neutron_providernet_list():
|
|
pnets = {}
|
|
if _neutron_providernet_extension_supported():
|
|
pnets = pecan.request.rpcapi.iinterface_get_providernets(
|
|
pecan.request.context)
|
|
return pnets
|
|
|
|
|
|
def _update_shared_interface_neutron_bindings(ihost, interface, test=False):
|
|
if not _neutron_host_extension_supported():
|
|
# No action required if neutron does not support the host extension
|
|
return
|
|
shared_data_interfaces = _get_shared_data_interfaces(ihost, interface)
|
|
for shared_interface in shared_data_interfaces:
|
|
if shared_interface['uuid'] != interface['uuid']:
|
|
_neutron_bind_interface(ihost, shared_interface, test)
|
|
|
|
|
|
def _neutron_bind_interface(ihost, interface, test=False):
|
|
"""
|
|
Send a request to neutron to bind the interface to the specified
|
|
providernetworks and perform validation against a subset of the interface
|
|
attributes.
|
|
"""
|
|
ihost_uuid = ihost['uuid']
|
|
recordtype = ihost['recordtype']
|
|
if recordtype in ['profile']:
|
|
# No action required if we are operating on a profile record
|
|
return
|
|
if not _neutron_providernet_extension_supported():
|
|
# No action required if neutron does not support the pnet extension
|
|
return
|
|
if not _neutron_host_extension_supported():
|
|
# No action required if neutron does not support the host extension
|
|
return
|
|
|
|
if interface['ifclass'] == constants.INTERFACE_CLASS_DATA:
|
|
networktype = constants.NETWORK_TYPE_DATA
|
|
elif interface['ifclass'] == constants.INTERFACE_CLASS_PCI_PASSTHROUGH:
|
|
networktype = constants.NETWORK_TYPE_PCI_PASSTHROUGH
|
|
elif interface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV:
|
|
networktype = constants.NETWORK_TYPE_PCI_SRIOV
|
|
else:
|
|
msg = _("Invalid interface class %s: " % interface['ifclass'])
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
interface_uuid = interface['uuid']
|
|
providernetworks = interface.get('providernetworks', '')
|
|
vlans = _get_interface_vlans(ihost_uuid, interface)
|
|
try:
|
|
# Send the request to neutron
|
|
pecan.request.rpcapi.neutron_bind_interface(
|
|
pecan.request.context,
|
|
ihost_uuid, interface_uuid, networktype, providernetworks,
|
|
interface['imtu'], vlans=vlans, test=test)
|
|
except rpc_common.RemoteError as e:
|
|
raise wsme.exc.ClientSideError(str(e.value))
|
|
|
|
|
|
def _neutron_unbind_interface(ihost, interface):
|
|
"""
|
|
Send a request to neutron to unbind the interface from all provider
|
|
networks.
|
|
"""
|
|
ihost_uuid = ihost['uuid']
|
|
recordtype = ihost['recordtype']
|
|
if recordtype in ['profile']:
|
|
# No action required if we are operating on a profile record
|
|
return
|
|
if not _neutron_providernet_extension_supported():
|
|
# No action required if neutron does not support the pnet extension
|
|
return
|
|
if not _neutron_host_extension_supported():
|
|
# No action required if neutron does not support the host extension
|
|
return
|
|
try:
|
|
# Send the request to neutron
|
|
pecan.request.rpcapi.neutron_unbind_interface(
|
|
pecan.request.context, ihost_uuid, interface['uuid'])
|
|
except rpc_common.RemoteError as e:
|
|
raise wsme.exc.ClientSideError(str(e.value))
|
|
|
|
|
|
def _get_boot_interface(ihost):
|
|
"""
|
|
Find the interface from which this host booted.
|
|
"""
|
|
ports = pecan.request.dbapi.ethernet_port_get_all(hostid=ihost['id'])
|
|
for p in ports:
|
|
if p.bootp == 'True':
|
|
return pecan.request.dbapi.iinterface_get(p.interface_id,
|
|
ihost['uuid'])
|
|
return None
|
|
|
|
|
|
def _get_lower_interface_macs(ihost, interface):
|
|
"""
|
|
Return a dictionary mapping interface name to MAC address for any interface
|
|
in the 'uses' list of the given interface object.
|
|
"""
|
|
result = {}
|
|
for lower_ifname in interface['uses']:
|
|
lower_iface = pecan.request.dbapi.iinterface_get(lower_ifname,
|
|
ihost['uuid'])
|
|
result[lower_iface['ifname']] = lower_iface['imac']
|
|
return result
|
|
|
|
|
|
def set_interface_mac(ihost, interface):
|
|
"""
|
|
Sets the MAC address on new interface. The MAC is selected from the list
|
|
of lower interface MAC addresses.
|
|
|
|
1) If this is a VLAN interface then there is only 1 choice.
|
|
2) If this is an AE interface then we select the first available lower
|
|
interface unless the interface type is a mgmt interface in which case
|
|
it may include the bootif which we prefer.
|
|
"""
|
|
selected_mac = None
|
|
selected_ifname = None
|
|
if interface['iftype'] == constants.INTERFACE_TYPE_VIRTUAL:
|
|
selected_mac = constants.ETHERNET_NULL_MAC
|
|
if interface['iftype'] == constants.INTERFACE_TYPE_AE:
|
|
boot_interface = _get_boot_interface(ihost)
|
|
if boot_interface:
|
|
boot_ifname = boot_interface['ifname']
|
|
boot_uuid = boot_interface['uuid']
|
|
if (any(x in interface['uses'] for x in [boot_ifname, boot_uuid])):
|
|
selected_mac = boot_interface['imac']
|
|
selected_ifname = boot_interface['ifname']
|
|
else:
|
|
LOG.warn("No boot interface found for host {}".format(
|
|
ihost['hostname']))
|
|
if not selected_mac:
|
|
# Fallback to selecting the first interface in the list.
|
|
available_macs = _get_lower_interface_macs(ihost, interface)
|
|
selected_ifname = sorted(available_macs)[0]
|
|
selected_mac = available_macs[selected_ifname]
|
|
if interface.get('imac') != selected_mac:
|
|
interface['imac'] = selected_mac
|
|
LOG.info("Setting MAC of interface {} to {}; taken from {}".format(
|
|
interface['ifname'], interface['imac'], selected_ifname))
|
|
return interface
|
|
|
|
|
|
def update_upper_interface_macs(ihost, interface):
|
|
"""
|
|
Updates the MAC address on any interface that uses this interface.
|
|
"""
|
|
for upper_ifname in interface['used_by']:
|
|
upper_iface = pecan.request.dbapi.iinterface_get(upper_ifname,
|
|
ihost['uuid'])
|
|
if upper_iface['imac'] != interface['imac']:
|
|
values = {'imac': interface['imac']}
|
|
pecan.request.dbapi.iinterface_update(upper_iface['uuid'], values)
|
|
LOG.info("Updating MAC address of {} from {} to {}".format(
|
|
upper_iface['ifname'], upper_iface['imac'], values['imac']))
|
|
|
|
|
|
# This method allows creating an interface through a non-HTTP
|
|
# request e.g. through profile.py while still passing
|
|
# through interface semantic checks and osd configuration
|
|
# Hence, not declared inside a class
|
|
#
|
|
# Param:
|
|
# interface - dictionary of interface values
|
|
def _create(interface, from_profile=False):
|
|
# Get host
|
|
ihostId = interface.get('forihostid') or interface.get('ihost_uuid')
|
|
ihost = pecan.request.dbapi.ihost_get(ihostId)
|
|
if uuidutils.is_uuid_like(ihostId):
|
|
forihostid = ihost['id']
|
|
else:
|
|
forihostid = ihostId
|
|
|
|
LOG.debug("iinterface post interfaces ihostid: %s" % forihostid)
|
|
|
|
interface.update({'forihostid': ihost['id'],
|
|
'ihost_uuid': ihost['uuid']})
|
|
|
|
# Assign an UUID if not already done.
|
|
if not interface.get('uuid'):
|
|
interface['uuid'] = str(uuid.uuid4())
|
|
|
|
if 'ifclass' in interface \
|
|
and interface['ifclass'] == constants.INTERFACE_CLASS_NONE:
|
|
interface.update({'ifclass': None})
|
|
|
|
# Get ports
|
|
ports = None
|
|
uses_if = None
|
|
if 'uses' in interface:
|
|
uses_if = interface['uses']
|
|
if 'ports' in interface:
|
|
ports = interface['ports']
|
|
|
|
if 'uses' in interface and interface['uses'] is None:
|
|
interface.update({'uses': []})
|
|
elif 'uses' not in interface:
|
|
interface.update({'uses': []})
|
|
|
|
if 'used_by' in interface and interface['used_by'] is None:
|
|
interface.update({'used_by': []})
|
|
elif 'used_by' not in interface:
|
|
interface.update({'used_by': []})
|
|
|
|
if 'networks' in interface and interface['networks'] is None:
|
|
interface.update({'networks': []})
|
|
elif 'networks' not in interface:
|
|
interface.update({'networks': []})
|
|
|
|
# Check mtu before setting defaults
|
|
interface = _check_interface_mtu(interface, ihost, from_profile=from_profile)
|
|
|
|
# Check vlan_id before setting defaults
|
|
interface = _check_interface_vlan_id("add", interface, ihost, from_profile=from_profile)
|
|
|
|
# Set defaults - before checks to allow for optional attributes
|
|
if not from_profile:
|
|
interface = _set_defaults(interface)
|
|
|
|
# Semantic checks
|
|
interface = _check("add", interface, ports=ports, ifaces=uses_if, from_profile=from_profile)
|
|
|
|
if not from_profile:
|
|
# Select appropriate MAC address from lower interface(s)
|
|
interface = set_interface_mac(ihost, interface)
|
|
|
|
new_interface = pecan.request.dbapi.iinterface_create(
|
|
forihostid,
|
|
interface)
|
|
|
|
# Create network-interface
|
|
try:
|
|
if (new_interface['ifclass'] and
|
|
new_interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM):
|
|
if 'networks' in interface.keys() and interface['networks']:
|
|
for network_id in interface['networks']:
|
|
values = {'interface_id': new_interface['id'],
|
|
'network_id': network_id}
|
|
pecan.request.dbapi.interface_network_create(values)
|
|
except Exception as e:
|
|
LOG.exception("Failed to create network interface association: "
|
|
"new_interface={} interface={}".format(
|
|
new_interface.as_dict(), interface))
|
|
pecan.request.dbapi.iinterface_destroy(new_interface.as_dict()['uuid'])
|
|
raise e
|
|
|
|
try:
|
|
# Add extended attributes stored in other tables
|
|
_add_extended_attributes(ihost['uuid'], new_interface, interface)
|
|
except Exception as e:
|
|
LOG.exception("Failed to set extended attributes on interface: "
|
|
"new_interface={} interface={}".format(
|
|
new_interface.as_dict(), interface))
|
|
pecan.request.dbapi.iinterface_destroy(new_interface.as_dict()['uuid'])
|
|
raise e
|
|
|
|
try:
|
|
if (interface['ifclass'] and
|
|
interface['ifclass'] in NEUTRON_INTERFACE_CLASS):
|
|
_neutron_bind_interface(ihost, new_interface.as_dict())
|
|
except Exception as e:
|
|
LOG.exception("Failed to update neutron bindings: "
|
|
"new_interface={} interface={}".format(
|
|
new_interface.as_dict(), interface))
|
|
pecan.request.dbapi.iinterface_destroy(new_interface.as_dict()['uuid'])
|
|
raise e
|
|
|
|
try:
|
|
_update_shared_interface_neutron_bindings(ihost, new_interface.as_dict())
|
|
except Exception as e:
|
|
LOG.exception("Failed to update neutron bindings for shared "
|
|
"interfaces: new_interface={} interface={}".format(
|
|
new_interface.as_dict(), interface))
|
|
pecan.request.dbapi.iinterface_destroy(interface['uuid'])
|
|
_neutron_unbind_interface(ihost, new_interface.as_dict())
|
|
_update_shared_interface_neutron_bindings(ihost, new_interface.as_dict())
|
|
raise e
|
|
|
|
# Update ports
|
|
if ports:
|
|
try:
|
|
_update_ports("modify", new_interface.as_dict(), ihost, ports)
|
|
except Exception as e:
|
|
LOG.exception("Failed to update ports for interface "
|
|
"interfaces: new_interface={} ports={}".format(
|
|
new_interface.as_dict(), ports))
|
|
if (interface['ifclass'] and
|
|
interface['ifclass'] in NEUTRON_INTERFACE_CLASS):
|
|
_neutron_unbind_interface(ihost, new_interface.as_dict())
|
|
pecan.request.dbapi.iinterface_destroy(new_interface.as_dict()['uuid'])
|
|
raise e
|
|
|
|
# Update the MTU of underlying interfaces of an AE
|
|
if new_interface['iftype'] == constants.INTERFACE_TYPE_AE:
|
|
try:
|
|
for ifname in new_interface['uses']:
|
|
_update_interface_mtu(ifname, ihost, new_interface['imtu'])
|
|
except Exception as e:
|
|
LOG.exception("Failed to update AE member MTU: "
|
|
"new_interface={} mtu={}".format(
|
|
new_interface.as_dict(), new_interface['imtu']))
|
|
|
|
pecan.request.dbapi.iinterface_destroy(new_interface['uuid'])
|
|
raise e
|
|
|
|
if ihost['recordtype'] != "profile":
|
|
try:
|
|
ifclass = new_interface['ifclass']
|
|
if ifclass == constants.INTERFACE_CLASS_PLATFORM and interface['networks']:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
if network.type == constants.NETWORK_TYPE_MGMT:
|
|
_update_host_mgmt_address(ihost, new_interface.as_dict())
|
|
elif network.type == constants.NETWORK_TYPE_INFRA:
|
|
_update_host_infra_address(ihost, new_interface.as_dict())
|
|
if ihost['personality'] == constants.CONTROLLER:
|
|
if network.type == constants.NETWORK_TYPE_OAM:
|
|
_update_host_oam_address(ihost, new_interface.as_dict())
|
|
elif network.type == constants.NETWORK_TYPE_PXEBOOT:
|
|
_update_host_pxeboot_address(ihost, new_interface.as_dict())
|
|
except Exception as e:
|
|
LOG.exception(
|
|
"Failed to add static infrastructure interface address: "
|
|
"interface={}".format(new_interface.as_dict()))
|
|
pecan.request.dbapi.iinterface_destroy(
|
|
new_interface.as_dict()['uuid'])
|
|
raise e
|
|
|
|
# Covers off LAG case here.
|
|
ifclass = new_interface['ifclass']
|
|
if ifclass == constants.INTERFACE_CLASS_PLATFORM and interface['networks']:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
if network.type == constants.NETWORK_TYPE_MGMT:
|
|
cutils.perform_distributed_cloud_config(pecan.request.dbapi,
|
|
new_interface['id'])
|
|
|
|
return new_interface
|
|
|
|
|
|
def _check(op, interface, ports=None, ifaces=None, from_profile=False,
|
|
existing_interface=None):
|
|
# Semantic checks
|
|
ihost = pecan.request.dbapi.ihost_get(interface['ihost_uuid']).as_dict()
|
|
_check_host(ihost)
|
|
if not from_profile:
|
|
if ports:
|
|
_check_ports(op, interface, ihost, ports)
|
|
if ifaces:
|
|
interfaces = pecan.request.dbapi.iinterface_get_by_ihost(interface['ihost_uuid'])
|
|
if len(ifaces) > 1 and \
|
|
interface['iftype'] == constants.INTERFACE_TYPE_VLAN:
|
|
# Can only have one interface associated to vlan interface type
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can only have one interface for vlan type. (%s)" % ifaces))
|
|
for i in ifaces:
|
|
for iface in interfaces:
|
|
if iface['uuid'] == i or iface['ifname'] == i:
|
|
existing_iface = copy.deepcopy(iface)
|
|
|
|
# Get host
|
|
ihost = pecan.request.dbapi.ihost_get(
|
|
iface.get('forihostid'))
|
|
|
|
if 'vlan_id' not in iface:
|
|
iface['vlan_id'] = None
|
|
|
|
if 'aemode' not in iface:
|
|
iface['aemode'] = None
|
|
|
|
if 'txhashpolicy' not in iface:
|
|
iface['txhashpolicy'] = None
|
|
|
|
_check_interface_data("modify", iface, ihost, existing_iface)
|
|
|
|
interface = _check_interface_data(op, interface, ihost, existing_interface)
|
|
|
|
return interface
|
|
|
|
|
|
def _update(interface_uuid, interface_values, from_profile):
|
|
return objects.interface.get_by_uuid(pecan.request.context, interface_uuid)
|
|
|
|
|
|
def _get_port_entity_type_id():
|
|
return "{}.{}".format(fm_constants.FM_ENTITY_TYPE_HOST,
|
|
fm_constants.FM_ENTITY_TYPE_PORT)
|
|
|
|
|
|
def _get_port_entity_instance_id(hostname, port_uuid):
|
|
return "{}={}.{}={}".format(fm_constants.FM_ENTITY_TYPE_HOST,
|
|
hostname,
|
|
fm_constants.FM_ENTITY_TYPE_PORT,
|
|
port_uuid)
|
|
|
|
|
|
def _clear_port_state_fault(hostname, port_uuid):
|
|
"""
|
|
Clear a fault management alarm condition for port state fault
|
|
"""
|
|
LOG.debug("Clear port state fault: {}".format(port_uuid))
|
|
|
|
entity_instance_id = _get_port_entity_instance_id(hostname, port_uuid)
|
|
FM.clear_fault(fm_constants.FM_ALARM_ID_NETWORK_PORT, entity_instance_id)
|
|
|
|
|
|
def _get_interface_entity_type_id():
|
|
return "{}.{}".format(fm_constants.FM_ENTITY_TYPE_HOST,
|
|
fm_constants.FM_ENTITY_TYPE_INTERFACE)
|
|
|
|
|
|
def _get_interface_entity_instance_id(hostname, interface_uuid):
|
|
return "{}={}.{}={}".format(fm_constants.FM_ENTITY_TYPE_HOST,
|
|
hostname,
|
|
fm_constants.FM_ENTITY_TYPE_INTERFACE,
|
|
interface_uuid)
|
|
|
|
|
|
def _clear_interface_state_fault(hostname, interface_uuid):
|
|
"""
|
|
Clear a fault management alarm condition for interface state fault
|
|
"""
|
|
LOG.debug("Clear interface state fault: {}".format(interface_uuid))
|
|
|
|
entity_instance_id = _get_interface_entity_instance_id(hostname, interface_uuid)
|
|
FM.clear_fault(fm_constants.FM_ALARM_ID_NETWORK_INTERFACE, entity_instance_id)
|
|
|
|
|
|
def _delete(interface, from_profile=False):
|
|
ihost = pecan.request.dbapi.ihost_get(interface['forihostid']).as_dict()
|
|
|
|
if not from_profile:
|
|
# Semantic checks
|
|
_check_host(ihost)
|
|
|
|
if not from_profile and interface['iftype'] == 'ethernet':
|
|
msg = _("Cannot delete an ethernet interface type.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
if interface['networks']:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
if interface['iftype'] == constants.INTERFACE_TYPE_VIRTUAL and \
|
|
network.type == constants.NETWORK_TYPE_MGMT:
|
|
msg = _("Cannot delete a virtual management interface.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Update ports
|
|
ports = pecan.request.dbapi.ethernet_port_get_all(
|
|
hostid=ihost['id'], interfaceid=interface['id'])
|
|
for port in ports:
|
|
values = {'interface_id': None}
|
|
try:
|
|
pecan.request.dbapi.port_update(port.id, values)
|
|
# Clear outstanding alarms that were raised by the neutron vswitch
|
|
# agent against ports associated with this interface
|
|
_clear_port_state_fault(ihost['hostname'], port.uuid)
|
|
except exception.HTTPNotFound:
|
|
msg = _("Port update of iinterface_uuid failed: "
|
|
"host %s port %s"
|
|
% (ihost['hostname'], port.name))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# Clear any faults on underlying ports, Eg. when deleting an
|
|
# AE interface, we do not want to leave a dangling port fault (that may
|
|
# never be cleared). We purposefully do not remove the underlying ports
|
|
# from their respective interfaces.
|
|
for ifname in interface['uses']:
|
|
lower_iface = (
|
|
pecan.request.dbapi.iinterface_get(ifname, ihost['uuid']))
|
|
lports = pecan.request.dbapi.ethernet_port_get_all(
|
|
hostid=ihost['id'], interfaceid=lower_iface['id'])
|
|
for lport in lports:
|
|
_clear_port_state_fault(ihost['hostname'], lport.uuid)
|
|
|
|
# Restore the default MTU for AE members
|
|
if interface['iftype'] == constants.INTERFACE_TYPE_AE:
|
|
for ifname in interface['uses']:
|
|
_update_interface_mtu(ifname, ihost, DEFAULT_MTU)
|
|
|
|
# Delete interface
|
|
try:
|
|
primary_ifclass = interface['ifclass']
|
|
if primary_ifclass == constants.INTERFACE_CLASS_PLATFORM:
|
|
for network_id in interface['networks']:
|
|
network = pecan.request.dbapi.network_get_by_id(network_id)
|
|
if ((network.type == constants.NETWORK_TYPE_MGMT) or
|
|
(network.type == constants.NETWORK_TYPE_INFRA) or
|
|
(network.type == constants.NETWORK_TYPE_PXEBOOT) or
|
|
(network.type == constants.NETWORK_TYPE_OAM)):
|
|
pecan.request.dbapi.addresses_remove_interface_by_interface(
|
|
interface['id']
|
|
)
|
|
pecan.request.dbapi.iinterface_destroy(interface['uuid'])
|
|
if (interface['ifclass'] and
|
|
interface['ifclass'] in NEUTRON_INTERFACE_CLASS):
|
|
# Unbind the interface in neutron
|
|
_neutron_unbind_interface(ihost, interface)
|
|
# Update shared data interface bindings, if required
|
|
_update_shared_interface_neutron_bindings(ihost, interface)
|
|
# Clear outstanding alarms that were raised by the neutron vswitch
|
|
# agent against interface
|
|
_clear_interface_state_fault(ihost['hostname'], interface['uuid'])
|
|
except exception.HTTPNotFound:
|
|
msg = _("Delete interface failed: host %s if %s"
|
|
% (ihost['hostname'], interface['ifname']))
|
|
raise wsme.exc.ClientSideError(msg)
|