def get_free_port_like_object(task, vif_id, physnets): """Find free port-like object (portgroup or port) VIF will be attached to. Ensures that the VIF is not already attached to this node. When selecting a port or portgroup to attach the virtual interface to, the following ordered criteria are applied: * Require ports or portgroups to have a physical network that is either None or one of the VIF's allowed physical networks. * Prefer ports or portgroups with a physical network field which is not None. * Prefer portgroups to ports. * Prefer ports with PXE enabled. :param task: a TaskManager instance. :param vif_id: Name or UUID of a VIF. :param physnets: Set of physical networks on which the VIF may be attached. This is governed by the segments of the VIF's network. An empty set indicates that the ports' physical networks should be ignored. :raises: VifAlreadyAttached, if VIF is already attached to the node. :raises: NoFreePhysicalPorts, if there is no port-like object VIF can be attached to. :raises: PortgroupPhysnetInconsistent if one of the node's portgroups has ports which are not all assigned the same physical network. :returns: port-like object VIF will be attached to. """ free_port_like_objs = _get_free_portgroups_and_ports( task, vif_id, physnets) if not free_port_like_objs: raise exception.NoFreePhysicalPorts(vif=vif_id) def sort_key(port_like_obj): """Key function for sorting a combined list of ports and portgroups. We key the port-like objects using the following precedence: 1. Prefer objects with a physical network field which is in the physnets set. 2. Prefer portgroups to ports. 3. Prefer ports with PXE enabled. :param port_like_obj: The port or portgroup to key. :returns: A key value for sorting the object. """ is_pg = isinstance(port_like_obj, objects.Portgroup) if is_pg: pg_physnets = network.get_physnets_by_portgroup_id( task, port_like_obj.id) pg_physnet = pg_physnets.pop() physnet_matches = pg_physnet in physnets pxe_enabled = True else: physnet_matches = port_like_obj.physical_network in physnets pxe_enabled = port_like_obj.pxe_enabled return (physnet_matches, is_pg, pxe_enabled) sorted_free_plos = sorted(free_port_like_objs, key=sort_key, reverse=True) return sorted_free_plos[0]
def get_free_port_like_object(task, vif_id): """Find free port-like object (portgroup or port) VIF will be attached to. Ensures that VIF is not already attached to this node. It will return the first free port group. If there are no free port groups, then the first available port (pxe_enabled preferably) is used. :param task: a TaskManager instance. :param vif_id: Name or UUID of a VIF. :raises: VifAlreadyAttached, if VIF is already attached to the node. :raises: NoFreePhysicalPorts, if there is no port-like object VIF can be attached to. :returns: port-like object VIF will be attached to. """ free_portgroups, free_ports = _get_free_portgroups_and_ports(task, vif_id) if free_portgroups: # portgroups are higher priority return free_portgroups[0] if not free_ports: raise exception.NoFreePhysicalPorts(vif=vif_id) # Sort ports by pxe_enabled to ensure we always bind pxe_enabled ports # first sorted_free_ports = sorted(free_ports, key=lambda p: p.pxe_enabled, reverse=True) return sorted_free_ports[0]
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node :param task: A TaskManager instance. :param vif_info: a dictionary of information about a VIF. It must have an 'id' key, whose value is a unique identifier for that VIF. :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts """ vif_id = vif_info['id'] # Sort ports by pxe_enabled to ensure we always bind pxe_enabled ports # first sorted_ports = sorted(task.ports, key=lambda p: p.pxe_enabled, reverse=True) free_ports = [] # Check all ports to ensure this VIF isn't already attached for port in sorted_ports: port_id = port.internal_info.get(TENANT_VIF_KEY, port.extra.get('vif_port_id')) if port_id is None: free_ports.append(port) elif port_id == vif_id: raise exception.VifAlreadyAttached( vif=vif_id, port_uuid=port.uuid) if not free_ports: raise exception.NoFreePhysicalPorts(vif=vif_id) # Get first free port port = free_ports.pop(0) # Check if the requested vif_id is a neutron port. If it is # then attempt to update the port's MAC address. try: client = neutron.get_client(task.context.auth_token) client.show_port(vif_id) except neutron_exceptions.NeutronClientException: # NOTE(sambetts): If a client error occurs this is because either # neutron doesn't exist because we're running in standalone # environment or we can't find a matching neutron port which means # a user might be requesting a non-neutron port. So skip trying to # update the neutron port MAC address in these cases. pass else: try: neutron.update_port_address(vif_id, port.address) except exception.FailedToUpdateMacOnPort: raise exception.NetworkError(_( "Unable to attach VIF %(vif)s because Ironic can not " "update Neutron port %(port)s MAC address to match " "physical MAC address %(mac)s") % { 'vif': vif_id, 'port': vif_id, 'mac': port.address}) int_info = port.internal_info int_info[TENANT_VIF_KEY] = vif_id port.internal_info = int_info port.save()