# 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-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # import copy import jsonpatch import pecan from pecan import rest import six import string import uuid import wsme from wsme import types as wtypes import wsmeext.pecan as wsme_pecan from oslo_log import log from oslo_utils import uuidutils from sysinv._i18n import _ 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.api.controllers.v1 import interface_datanetwork from sysinv.api.controllers.v1 import ptp_interface 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 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_CLUSTER_HOST, constants.NETWORK_TYPE_DATA, constants.NETWORK_TYPE_PCI_PASSTHROUGH, constants.NETWORK_TYPE_PCI_SRIOV, constants.NETWORK_TYPE_IRONIC, constants.NETWORK_TYPE_STORAGE, constants.NETWORK_TYPE_ADMIN] 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] PCI_INTERFACE_CLASS = [constants.INTERFACE_CLASS_PCI_PASSTHROUGH, constants.INTERFACE_CLASS_PCI_SRIOV] DATA_NETWORK_TYPES = [constants.NETWORK_TYPE_DATA] # Kernel allows max 15 chars. MAX_IFNAME_LEN = 15 # Maximum number of characters in data network list MAX_DATANETWORK_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" 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" primary_reselect = wtypes.text "Represent the primary_reselect mode 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" sriov_vf_driver = wtypes.text "The driver of configured SR-IOV VFs" ptp_role = wtypes.text "The PTP role for this interface" max_tx_rate = int "The value of configured max tx rate of VF, Mbps" def __init__(self, **kwargs): self.fields = list(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) kwargs = rpc_interface.as_dict() interface = Interface(**kwargs) if not expand: interface.unset_fields_except(['uuid', 'ifname', 'iftype', 'imac', 'imtu', 'ifclass', 'ihost_uuid', 'forihostid', 'aemode', 'schedpolicy', 'txhashpolicy', 'vlan_id', 'uses', 'usesmodify', 'used_by', 'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool', 'sriov_numvfs', 'sriov_vf_driver', 'ptp_role', 'max_tx_rate', 'primary_reselect']) # never expose the ihost_id attribute interface.ihost_id = 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) ] if not _is_interface_address_allowed(rpc_interface.as_dict()): # Hide this functionality when the network type does not support # setting or updating the address 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" ptp_interfaces = ptp_interface.PtpInterfaceController(parent="iinterface") "Expose PTP interfaces as a sub-element of interface" interface_networks = interface_network.InterfaceNetworkController( parent="iinterfaces") "Expose interface_networks as a sub-element of interface" interface_datanetworks = \ interface_datanetwork.InterfaceDataNetworkController( parent="iinterfaces") "Expose interface_datanetworks 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)) 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) rpc_interface = objects.interface.get_by_uuid(pecan.request.context, interface_uuid) uses = None ports = None has_ptp_interfaces = False patches_to_remove = [] for p in patch: if '/ifclass' == p['path']: if p['value'] == constants.INTERFACE_CLASS_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 constants.PTP_INTERFACE_ARRAY_PATH == p['path']: has_ptp_interfaces = True ptp_interface_id = p['value'] try: # Check PTP interface exists pecan.request.dbapi.ptp_interface_get(ptp_interface_id) except exception.PtpInterfaceNotFound: raise wsme.exc.ClientSideError( _("No PTP interface object with id %s" % ptp_interface_id)) values = {'interface_id': rpc_interface.id, 'ptp_interface_id': ptp_interface_id} if p.get('op') == constants.PTP_PATCH_OPERATION_ADD: pecan.request.dbapi.ptp_interface_assign(values) else: pecan.request.dbapi.ptp_interface_remove(values) if has_ptp_interfaces: return Interface.convert_with_links(rpc_interface) 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) # 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 sriov_numvfs = None sriov_vf_driver = None for p in patch: if '/ifclass' == p['path']: temp_interface['ifclass'] = p['value'] elif '/sriov_numvfs' == p['path']: sriov_numvfs = p['value'] temp_interface['sriov_numvfs'] = sriov_numvfs elif '/sriov_vf_driver' == p['path']: sriov_vf_driver = p['value'] temp_interface['sriov_vf_driver'] = sriov_vf_driver # If the interface class is no longer pci-sriov, reset the VF # parameters if they haven't been specified in the patch if temp_interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV: if sriov_numvfs is None: temp_interface['sriov_numvfs'] = 0 if sriov_vf_driver is None: temp_interface['sriov_vf_driver'] = None sriov_update = _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'] == constants.AE_MODE_ACTIVE_STANDBY: interface['txhashpolicy'] = None 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['sriov_numvfs'] = 0 interface['sriov_vf_driver'] = None 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()) # Clear the vf fields if class is not sriov if interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV: interface["sriov_numvfs"] = 0 interface["sriov_vf_driver"] = None 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): if _is_interface_network_assigned(interface, constants.NETWORK_TYPE_MGMT): # Remove mgmt address associated with this interface pecan.request.rpcapi.mgmt_ip_set_by_ihost( pecan.request.context, ihost['uuid'], interface['id'], 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) saved_interface = copy.deepcopy(rpc_interface) 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) # 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) if sriov_update: pecan.request.rpcapi.update_sriov_config( pecan.request.context, ihost['uuid']) 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)) 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, 'aemode': constants.AE_MODE_ACTIVE_STANDBY, 'txhashpolicy': None, 'primary_reselect': None, 'vlan_id': None, 'sriov_numvfs': 0, 'sriov_vf_driver': None, 'ptp_role': constants.INTERFACE_PTP_ROLE_NONE, 'max_tx_rate': None} if interface['ifclass'] == constants.INTERFACE_CLASS_DATA: defaults['ipv4_mode'] = constants.IPV4_DISABLED defaults['ipv6_mode'] = constants.IPV6_DISABLED elif interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM: defaults['ipv4_mode'] = None defaults['ipv6_mode'] = None 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): vlan_id = None # Check vlan_id if 'vlan_id' in interface.keys(): vlan_id = interface['vlan_id'] if 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']) if ('iftype' in interface.keys() and interface['iftype'] == constants.INTERFACE_TYPE_VLAN and vlan_id is None): raise wsme.exc.ClientSideError(_("VLAN id must be specified.")) return interface def _check_interface_name(op, interface, ihost): 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 if ifname and len(ifname) > MAX_IFNAME_LEN: raise wsme.exc.ClientSideError(_("Interface {} has name length " "greater than {}.". format(ifname, MAX_IFNAME_LEN))) # Check for invalid characters in vlan's ifname vlan_id = None if iftype == constants.INTERFACE_TYPE_VLAN: vlan_id = interface['vlan_id'] invalidChars_vlan = set(string.punctuation.replace("_", "")) if vlan_id is not None: # Allow VLAN interfaces to have "." in the name invalidChars_vlan.remove(".") if any(char in invalidChars_vlan for char in ifname) and (vlan_id is not None): msg = _("Cannot use special characters in vlan interface name.") raise wsme.exc.ClientSideError(msg) # Check for invalid characters in other if names invalidChars = set(string.punctuation) # Allow the use of "-", "_", and "." in ifname invalidChars.remove("-") invalidChars.remove("_") invalidChars.remove(".") for char in invalidChars: if char in ifname: msg = _("Cannot use '{}' as a special character in interface name.".format(char)) 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(_("Interface Name {} must be unique.".format(ifname))) return interface def _check_interface_mtu(interface, ihost): # 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): sriov_update = False if 'ifclass' in interface.keys() and not interface['ifclass']: return sriov_update 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 ('sriov_vf_driver' in interface.keys() and interface['sriov_vf_driver'] is not None and ('ifclass' not in interface.keys() or interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV)): raise wsme.exc.ClientSideError(_("SR-IOV VF driver 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.")) if interface['sriov_vf_driver'] is not None: if interface['sriov_vf_driver'] not in constants.SRIOV_DRIVER_TYPES: msg = (_("Value for SR-IOV VF driver must be one of " "{}").format(', '.join(constants.SRIOV_DRIVER_TYPES))) raise wsme.exc.ClientSideError(msg) if interface['iftype'] == constants.INTERFACE_TYPE_ETHERNET: 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(_("Exactly 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")) sriov_update = True return sriov_update def _check_host(ihost): if ihost['administrative'] != constants.ADMIN_LOCKED: raise wsme.exc.ClientSideError(_("Host must be locked.")) def _check_interface_class_and_host_type(ihost, interface): if (interface['ifclass'] == constants.INTERFACE_CLASS_DATA and constants.WORKER not in ihost['subfunctions']): msg = _("The data interface class is only supported on nodes " "supporting worker functions") raise wsme.exc.ClientSideError(msg) def _check_interface_class_and_type(interface): if (interface['ifclass'] in PCI_INTERFACE_CLASS and interface['iftype'] not in [constants.INTERFACE_TYPE_ETHERNET, constants.INTERFACE_TYPE_VF]): msg = (_("The {} interface class is only valid on Ethernet and " "VF interfaces").format(', '.join(PCI_INTERFACE_CLASS))) 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 # to share single vf capable nic, we need to allow # platform to pci-sriov class transition if (ifclass == constants.INTERFACE_CLASS_PCI_SRIOV and existing_ifclass == constants.INTERFACE_CLASS_PLATFORM): return if (ifclass and existing_interface[ 'ifclass'] == constants.INTERFACE_CLASS_PLATFORM and existing_interface['used_by']): 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_interface_class_and_interface_name(interface): if (utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX and interface['ifclass'] == constants.INTERFACE_CLASS_NONE 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_interface_class(ihost, 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) _check_interface_class_transition(interface, existing_interface) _check_interface_class_and_host_type(ihost, interface) _check_interface_class_and_type(interface) _check_interface_class_and_interface_name(interface) 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 if not _is_interface_address_allowed(interface): 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 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_network_type_and_port(interface, ihost, interface_port, host_port): 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 different interface classes # at the same time error_found = False if not pif['ifclass'] 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['ifclass'] != interface['ifclass']: error_found = True break elif pif['ifclass'] and pif['ifclass'] != interface['ifclass']: error_found = True if error_found: msg = (_("Shared device %(device)s cannot be shared " "with different interface classes ") % {'device': interface_port.pciaddr}) raise wsme.exc.ClientSideError(msg) def _check_interface_ratelimit(interface): # Ensure rate limit is valid for VF interfaces if interface['max_tx_rate'] is not None: if not str(interface['max_tx_rate']).isdigit(): msg = _("max_tx_rate must be an integer value.") raise wsme.exc.ClientSideError(msg) if interface['iftype'] != constants.INTERFACE_TYPE_VF: msg = _("max_tx_rate is only allowed to be configured for VF interfaces") raise wsme.exc.ClientSideError(msg) # check if an overcommitted config max_tx_rate = interface['max_tx_rate'] ihost_uuid = interface['ihost_uuid'] lower_ifname = interface['uses'][0] lower_iface = ( pecan.request.dbapi.iinterface_get(lower_ifname, ihost_uuid)) ports = pecan.request.dbapi.ethernet_port_get_by_interface( lower_iface['uuid']) if len(ports) > 0: if ports[0]['speed'] is None: msg = _("Port speed for %s could not be determined. " "Check if the port is cabled correctly." % ports[0]['name']) raise wsme.exc.ClientSideError(msg) # keep 10% of the bandwidth for PF traffic total_rate_for_vf = int(ports[0]['speed'] * constants.VF_TOTAL_RATE_RATIO) total_rate_used = 0 this_interface_id = interface.get('id', 0) interface_list = pecan.request.dbapi.iinterface_get_all( forihostid=ihost_uuid) for i in interface_list: if (i['iftype'] == constants.INTERFACE_TYPE_VF and lower_ifname == i['uses'][0] and i.id != this_interface_id): if i['max_tx_rate'] is not None: total_rate_used += i['max_tx_rate'] * i['sriov_numvfs'] vfs_config = interface['sriov_numvfs'] if total_rate_used + (max_tx_rate * vfs_config) > total_rate_for_vf: msg = _("Configured (max_tx_rate*sriov_numvfs) exceeds " "available link speed bandwidth: %d Mbps." % (total_rate_for_vf - total_rate_used)) raise wsme.exc.ClientSideError(msg) def _check_interface_ptp(interface): # Ensure PTP settings are valid for this interface # Validate PTP role value ptp_role = interface['ptp_role'] if ptp_role is None: return supported_ptp_values = [constants.INTERFACE_PTP_ROLE_MASTER, constants.INTERFACE_PTP_ROLE_SLAVE, constants.INTERFACE_PTP_ROLE_NONE] if ptp_role not in supported_ptp_values: msg = (_("Interface ptp_role must be one of " "{}").format(', '.join(supported_ptp_values))) raise wsme.exc.ClientSideError(msg) # Validate interface class for PTP if ptp_role != constants.INTERFACE_PTP_ROLE_NONE: ifclass = interface['ifclass'] supported_ptp_classes = [constants.INTERFACE_CLASS_PLATFORM, constants.INTERFACE_CLASS_PCI_SRIOV, constants.INTERFACE_TYPE_VF] if not ifclass or ifclass not in supported_ptp_classes: msg = (_("Invalid interface class for ptp_role: {0}. Device interface class must be one of " "{1}").format(ptp_role, ', '.join(supported_ptp_classes))) raise wsme.exc.ClientSideError(msg) def _check_ae_primary_reselect(interface): ifclass = interface['ifclass'] iftype = interface['iftype'] primary_reselect = interface['primary_reselect'] aemode = interface['aemode'] if primary_reselect is not None: if iftype != constants.INTERFACE_TYPE_AE: msg = _("The option primary_reselect is only applicable to bonded interface. ") raise wsme.exc.ClientSideError(msg) if aemode != constants.AE_MODE_ACTIVE_STANDBY and primary_reselect is not None: msg = _("Device interface with interface type 'aggregated ethernet' " "in '%s' mode should not specify primary_reselect option." % aemode) raise wsme.exc.ClientSideError(msg) if (ifclass != constants.INTERFACE_CLASS_PLATFORM and primary_reselect != constants.PRIMARY_RESELECT_ALWAYS): msg = _("The option primary_reselect must be 'always' for non-platform interfaces. ") raise wsme.exc.ClientSideError(msg) if primary_reselect not in constants.VALID_PRIMARY_RESELECT_LIST: msg = _("Invalid bonding primary reselect option: '{}'. " "Valid options must be one of {}".format(primary_reselect, ', '.join(constants.VALID_PRIMARY_RESELECT_LIST))) raise wsme.exc.ClientSideError(msg) def _check_interface_data(op, interface, ihost, existing_interface, datanetworks=None): # Get data ihost_id = interface['forihostid'] ihost_uuid = interface['ihost_uuid'] # Check interface name for validity _check_interface_name(op, interface, ihost) 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) if (interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM): vlans = _get_platform_interface_vlans(ihost_uuid) if op != "modify" and str(vlan_id) in vlans: msg = _("VLAN id %s already in use on another platform " "interface" % str(vlan_id)) 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']): supported_type = [constants.INTERFACE_TYPE_VLAN, constants.INTERFACE_TYPE_VF] if i.iftype == constants.INTERFACE_TYPE_AE: msg = _("Interface '{}' is already used by another" " 'aggregated ethernet' interface " "'{}'".format(p, i.ifname)) raise wsme.exc.ClientSideError(msg) elif (i.iftype == constants.INTERFACE_TYPE_VLAN and iftype not in supported_type): 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 or 'pci-sriov' when creating # the AE interface and have no PTP role if iftype == constants.INTERFACE_TYPE_AE: for i in interface['uses']: msg = None iface_lower = pecan.request.dbapi.iinterface_get(i, ihost_uuid) if iface_lower.ptp_role != constants.INTERFACE_PTP_ROLE_NONE: msg = _("None of interfaces being used in an 'aggregated " "ethernet' interface can have a PTP role " "(must be 'none').") elif iface_lower.ifclass and \ iface_lower.ifclass != constants.INTERFACE_CLASS_PCI_SRIOV: msg = _("All interfaces being used in an 'aggregated ethernet' " "interface must have the interface class set to " "'none' or 'pci-sriov'.") if msg: raise wsme.exc.ClientSideError(msg) if interface['used_by']: used_vfs = 0 for i in interface['used_by']: iface = pecan.request.dbapi.iinterface_get(i, ihost_uuid) # Ensure that the interfaces being used in the AE interface # are not changed after the AE interface has been created if iface.iftype == constants.INTERFACE_TYPE_AE: msg = None if interface['ptp_role'] != constants.INTERFACE_PTP_ROLE_NONE: msg = _("Interface '{}' is being used by interface '{}' " "as an 'aggregated ethernet' interface and " "therefore the PTP role cannot be changed from " "'none'.".format( interface['ifname'], iface.ifname)) elif interface['ifclass'] and \ interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV: msg = _("Interface '{}' is being used by interface '{}' " "as an 'aggregated ethernet' interface and " "therefore the interface class cannot be changed " "from 'none' or 'pci-sriov'.".format( interface['ifname'], iface.ifname)) if msg: raise wsme.exc.ClientSideError(msg) # Ensure that if the interface has any virtual function (VF) # interfaces, that the interface class does not change from # pci-sriov, and that the number of requested virtual functions is # greater than the sum of the virtual functions on the upper # VF interfaces. if iface.iftype == constants.INTERFACE_TYPE_VF: if interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV: msg = _("Interface '{}' is being used by VF interface '{}' " "and therefore the interface class cannot be changed " "from 'pci-sriov'.".format(interface['ifname'], iface.ifname)) raise wsme.exc.ClientSideError(msg) else: used_vfs += iface['sriov_numvfs'] if interface['sriov_numvfs'] <= used_vfs: msg = _("The number of virtual functions (%s) must be greater " "than the number of consumed VFs used by the " "upper VF interfaces (%s)" % (interface['sriov_numvfs'], interface['used_by'])) raise wsme.exc.ClientSideError(msg) # check interface class validity _check_interface_class(ihost, interface, existing_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 iftype == constants.INTERFACE_TYPE_AE and aemode not in constants.VALID_AEMODE_LIST: msg = _("Invalid aggregated ethernet mode '%s' " % aemode) raise wsme.exc.ClientSideError(msg) if aemode in [constants.AE_MODE_BALANCED, constants.AE_MODE_LACP]: msg = None if interface['ptp_role'] != constants.INTERFACE_PTP_ROLE_NONE: msg = _("PTP isn't supported by 'aggregated ethernet' interface " "type in 'balanced' or '802.3ad' mode.") elif not txhashpolicy: msg = _("Device interface with 'aggregated ethernet' interface " "type in 'balanced' or '802.3ad' mode require a valid " "Tx Hash Policy.") if msg: raise wsme.exc.ClientSideError(msg) elif aemode in [constants.AE_MODE_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) _check_ae_primary_reselect(interface) # Make sure interface type is valid supported_type = [constants.INTERFACE_TYPE_AE, constants.INTERFACE_TYPE_VLAN, constants.INTERFACE_TYPE_ETHERNET, constants.INTERFACE_TYPE_VIRTUAL, constants.INTERFACE_TYPE_VF] 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'. if (iftype == constants.INTERFACE_TYPE_AE and interface['ifclass'] == constants.INTERFACE_CLASS_DATA): if aemode not in constants.VALID_AEMODE_LIST: msg = _("Device interface with interface class '%s', and interface " "type 'aggregated ethernet' must be in mode " "'active_standby', 'balanced', or '802.3ad'." % constants.INTERFACE_CLASS_DATA) raise wsme.exc.ClientSideError(msg) if aemode in [constants.AE_MODE_BALANCED, constants.AE_MODE_LACP] and txhashpolicy != 'layer2': msg = _("Device interface with interface class '%s', and interface " "type 'aggregated ethernet' must have a Tx Hash Policy of " "'layer2'." % constants.INTERFACE_CLASS_DATA) 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 = [constants.AE_MODE_LACP, constants.AE_MODE_ACTIVE_STANDBY] if interface['networktypelist'] is not None: if (constants.NETWORK_TYPE_MGMT in interface['networktypelist'] and iftype == constants.INTERFACE_TYPE_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(interface['networktypelist'])), ', '.join(valid_mgmt_aemode)) raise wsme.exc.ClientSideError(msg) # Make sure network type 'oam' or 'cluster-host', with if type 'ae', # can only be in ae mode 'active_standby' or 'balanced' or '802.3ad' if interface['networktypelist'] is not None: if (any(network in [constants.NETWORK_TYPE_OAM, constants.NETWORK_TYPE_CLUSTER_HOST] for network in interface['networktypelist']) and iftype == constants.INTERFACE_TYPE_AE and (aemode not in constants.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(interface['networktypelist']))) raise wsme.exc.ClientSideError(msg) # Ensure that data and non-data interfaces are not using the same # shared device if iftype not in [constants.INTERFACE_TYPE_VLAN, constants.INTERFACE_TYPE_VF, 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) # check MTU if interface['iftype'] in [constants.INTERFACE_TYPE_VLAN, constants.INTERFACE_TYPE_VF, constants.INTERFACE_TYPE_ETHERNET]: interface_mtu = interface['imtu'] for name in interface['uses']: parent = pecan.request.dbapi.iinterface_get(name, ihost_uuid) if int(interface_mtu) > int(parent['imtu']): msg = _("Interface MTU (%s) cannot be larger than MTU of " "underlying interface (%s)" % (interface_mtu, parent['imtu'])) raise wsme.exc.ClientSideError(msg) if 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 vf interfaces if iftype == constants.INTERFACE_TYPE_VF: if interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV: msg = _("VF interfaces must have an interface class of %s" % constants.INTERFACE_CLASS_PCI_SRIOV) raise wsme.exc.ClientSideError(msg) if len(interface['uses']) > 1: msg = _("VF interfaces can only use one lower pci-sriov interface") raise wsme.exc.ClientSideError(msg) lower_ifname = interface['uses'][0] lower_iface = ( pecan.request.dbapi.iinterface_get(lower_ifname, ihost_uuid)) if lower_iface['iftype'] not in [constants.INTERFACE_TYPE_VF, constants.INTERFACE_TYPE_ETHERNET]: msg = _("VF interfaces cannot be created over interfaces " "of type %s" % lower_iface['iftype']) raise wsme.exc.ClientSideError(msg) if (lower_iface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV): msg = _("VF interfaces must be created over an interface of class" " %s" % constants.INTERFACE_CLASS_PCI_SRIOV) raise wsme.exc.ClientSideError(msg) if interface['sriov_numvfs'] <= 0: raise wsme.exc.ClientSideError(_("Value for number of SR-IOV VFs must be > 0.")) avail_vfs = lower_iface['sriov_numvfs'] - 1 for i in lower_iface['used_by']: if i != interface['ifname']: iface = pecan.request.dbapi.iinterface_get(i, ihost_uuid) if iface.get('sriov_numvfs', 0): avail_vfs -= iface.get('sriov_numvfs') if interface['sriov_numvfs'] > avail_vfs: msg = _("The number of virtual functions (%s) must be less " "than or equal to the available VFs (%s) available " "on the underlying interface (%s)" % (interface['sriov_numvfs'], avail_vfs, lower_iface['ifname'])) raise wsme.exc.ClientSideError(msg) _check_interface_ptp(interface) _check_interface_ratelimit(interface) return interface def _is_port_sriov_restricted(port): return port.pvendor in constants.SRIOV_RESTRICTED_NET_DEVICES and \ port.pdevice in constants.SRIOV_RESTRICTED_NET_DEVICES[port.pvendor] 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 the port is not in the SR-IOV restricted list and # 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 (interface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV and _is_port_sriov_restricted(p)): pif = pecan.request.dbapi.iinterface_get(p.interface_id) msg = _("Cannot use port. PCI vendor '%s' and device '%s' is not allowed." % (p.pvendor, p.pdevice)) raise wsme.exc.ClientSideError(msg) 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'] == constants.INTERFACE_TYPE_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. if interface['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) def _delete_addressing(interface, family, existing_interface): interface_id = interface['id'] pecan.request.dbapi.routes_destroy_by_interface( interface_id, family) if (existing_interface['ifclass'] and existing_interface['ifclass'] != constants.INTERFACE_CLASS_PLATFORM): 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): mode = interface['ipv6_mode'] if not mode else mode pool = interface['ipv6_pool'] if not pool else pool utils.update_address_mode(interface, constants.IPV6_FAMILY, mode, pool) if mode == constants.IPV6_POOL: _allocate_pool_address(interface['id'], pool) def _update_ipv4_address_mode(interface, mode=None, pool=None): mode = interface['ipv4_mode'] if not mode else mode pool = interface['ipv4_pool'] if not pool else pool utils.update_address_mode(interface, constants.IPV4_FAMILY, mode, pool) if mode == constants.IPV4_POOL: _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() if interface_data['ifclass'] not in [constants.INTERFACE_CLASS_DATA, constants.INTERFACE_CLASS_PLATFORM]: # 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')) if attributes.get('ipv6_mode'): _update_ipv6_address_mode(interface_data, attributes.get('ipv6_mode'), attributes.get('ipv6_pool')) 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 _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_platform_interface_vlans(ihost_uuid): """ Retrieve the VLAN id values (if any) of platform interfaces on this host. """ vlans = [] ifaces = pecan.request.dbapi.vlan_interface_get_by_ihost(ihost_uuid) for iface in ifaces: if iface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM: vlans.append(str(iface['vlan_id'])) return 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 _datanetworks_get_by_interface(interface_uuid): ifdatanets = pecan.request.dbapi.interface_datanetwork_get_by_interface( interface_uuid) LOG.debug("_datanetworks_get_by_interface %s ifdnets=%s" % (interface_uuid, ifdatanets)) datanetworks = [] for ifdatanet in ifdatanets: datanetworks.append(ifdatanet.datanetwork_uuid) datanetworks_list = [] datanetworks_names_list = [] for datanetwork in datanetworks: dn = pecan.request.dbapi.datanetwork_get(datanetwork) datanetwork_dict = \ {'name': dn.name, 'uuid': dn.uuid, 'network_type': dn.network_type, 'mtu': dn.mtu} datanetworks_names_list.append(dn.name) if dn.network_type == constants.DATANETWORK_TYPE_VXLAN: datanetwork_dict.update( {'port_num': dn.port_num, 'multicast_group': dn.multicast_group, 'ttl': dn.ttl, 'mode': dn.mode}) datanetworks_list.append(datanetwork_dict) return datanetworks_names_list, datanetworks_list 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 available_macs = _get_lower_interface_macs(ihost, interface) 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 one of the interfaces in the bond is the original mgmt mac # of controller-0, select that interface and its mac. if (ihost['hostname'] == constants.CONTROLLER_0_HOSTNAME and ihost['mgmt_mac'] is not None and interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM): for ifname in interface['uses']: if ihost['mgmt_mac'] == available_macs[ifname]: selected_ifname = ifname selected_mac = available_macs[selected_ifname] break if not selected_mac: # Fallback to selecting the first interface in the list. 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 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): # 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': []}) # Check mtu before setting defaults interface = _check_interface_mtu(interface, ihost) # Check vlan_id before setting defaults interface = _check_interface_vlan_id("add", interface, ihost) # Set defaults - before checks to allow for optional attributes interface = _set_defaults(interface) # Semantic checks interface = _check("add", interface, ports=ports, ifaces=uses_if) # Select appropriate MAC address from lower interface(s) interface = set_interface_mac(ihost, interface) new_interface = pecan.request.dbapi.iinterface_create( forihostid, interface) 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 # 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)) 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 'aggregated ethernet' member MTU: " "new_interface={} mtu={}".format( new_interface.as_dict(), new_interface['imtu'])) pecan.request.dbapi.iinterface_destroy(new_interface['uuid']) raise e if (cutils.is_aio_simplex_system(pecan.request.dbapi) and new_interface['iftype'] == constants.INTERFACE_TYPE_VF): try: pecan.request.rpcapi.update_sriov_vf_config( pecan.request.context, ihost['uuid']) except Exception as e: LOG.exception(e) msg = _("Interface pci-sriov-vf creation failed: host %s if %s" % (ihost['hostname'], interface['ifname'])) raise wsme.exc.ClientSideError(msg) return new_interface def _check(op, interface, ports=None, ifaces=None, existing_interface=None, datanetworks=None): # Semantic checks ihost = pecan.request.dbapi.ihost_get(interface['ihost_uuid']).as_dict() check_host = True if (cutils.is_aio_simplex_system(pecan.request.dbapi) and interface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV): if (op == 'modify' and interface['iftype'] == constants.INTERFACE_TYPE_ETHERNET and existing_interface['ifclass'] != constants.INTERFACE_CLASS_PCI_SRIOV and existing_interface['iftype'] == constants.INTERFACE_TYPE_ETHERNET): # user can modify interface to SR-IOV PF without host lock in AIO-SX check_host = False elif (op == 'add' and interface['iftype'] == constants.INTERFACE_TYPE_VF): # user can add interface SR-IOV VF without host lock in AIO-SX check_host = False if check_host: _check_host(ihost) 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)) if interface['iftype'] == constants.INTERFACE_TYPE_ETHERNET: if len(ifaces) > 1: raise wsme.exc.ClientSideError( _("Can only have one lower interface for ethernet type." "(%s)" % ifaces)) lower = pecan.request.dbapi.iinterface_get(ifaces[0], interface['ihost_uuid']) if not (lower['iftype'] == constants.INTERFACE_TYPE_ETHERNET and lower['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV): # Can only have pci_sriov ethernet type lower interface # associated to ethernet interface type raise wsme.exc.ClientSideError( _("Can only use pci-sriov ethernet interface for " "ethernet 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 if 'primary_reselect' not in iface: iface['primary_reselect'] = None _check_interface_data( "modify", iface, ihost, existing_iface, datanetworks) interface = _check_interface_data( op, interface, ihost, existing_interface, datanetworks) return interface def _update(interface_uuid, interface_values): 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): ihost = pecan.request.dbapi.ihost_get(interface['forihostid']).as_dict() check_host = True if (cutils.is_aio_simplex_system(pecan.request.dbapi) and interface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV and interface['iftype'] == constants.INTERFACE_TYPE_VF): # user can delete interface SR-IOV VF without host lock in AIO-SX check_host = False if check_host: _check_host(ihost) if interface['iftype'] == 'ethernet' and not interface['uses']: msg = _("Cannot delete a system created ethernet interface") raise wsme.exc.ClientSideError(msg) # Allow the removal of the virtual management interface during bootstrap. # Once the initial configuration is complete, this type of request will be # rejected. if cutils.is_initial_config_complete(): ifnets = pecan.request.dbapi.interface_network_get_by_interface(interface['uuid']) for ifnet in ifnets: network = pecan.request.dbapi.network_get_by_id(ifnet.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) # Request to delete interface that is assigned to admin-network will be rejected, # in case address pool still exist on the admin-network. This condition is relaxed # for the aio-simplex system. if (_is_interface_network_assigned(interface, constants.NETWORK_TYPE_ADMIN) and not cutils.is_aio_simplex_system(pecan.request.dbapi)): network = pecan.request.dbapi.network_get_by_type(constants.NETWORK_TYPE_ADMIN) if (network.pool_uuid is not None): msg = _("Cannot delete an interface still assigned to a network of " "type '%s'. Address pool %s still exists." % (constants.NETWORK_TYPE_ADMIN, network.pool_uuid)) 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: ifnets = pecan.request.dbapi.interface_network_get_by_interface(interface['uuid']) for ifnet in ifnets: network = pecan.request.dbapi.network_get_by_id(ifnet.network_id) if ((network.type == constants.NETWORK_TYPE_MGMT) or (network.type == constants.NETWORK_TYPE_CLUSTER_HOST) 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']) # 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) def _is_interface_network_assigned(interface, network_type): try: network = pecan.request.dbapi.network_get_by_type(network_type) except exception.NetworkTypeNotFound: return False interface_network_dict = {} interface_network_dict['interface_id'] = interface['id'] interface_network_dict['network_id'] = network['id'] try: pecan.request.dbapi.interface_network_query(interface_network_dict) except exception.InterfaceNetworkNotFoundByHostInterfaceNetwork: return False return True def _is_interface_address_allowed(interface): if interface['ifclass'] in PCI_INTERFACE_CLASS: return False elif interface['ifclass'] == constants.INTERFACE_CLASS_DATA: return True elif interface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM: return True return False # TODO (pbovina): Move utils methods within InterfaceController class