# 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.COMPUTE not in ihost['subfunctions']): msg = _("The '%s' network type is only supported on nodes " "supporting compute 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)