def _verify_security_groups(security_groups, client): """Verify that the security groups exist. :param security_groups: a list of security group UUIDs; may be None or empty :param client: Neutron client :raises: NetworkError """ if not security_groups: return try: neutron_sec_groups = ( client.list_security_groups(id=security_groups, fields='id').get( 'security_groups', [])) except neutron_exceptions.NeutronClientException as e: msg = (_("Could not retrieve security groups from neutron: %(exc)s") % {'exc': e}) LOG.exception(msg) raise exception.NetworkError(msg) if set(security_groups).issubset(x['id'] for x in neutron_sec_groups): return missing_sec_groups = set(security_groups).difference( x['id'] for x in neutron_sec_groups) msg = (_('Could not find these security groups (specified via ironic ' 'config) in neutron: %(ir-sg)s') % {'ir-sg': list(missing_sec_groups)}) LOG.error(msg) raise exception.NetworkError(msg)
def remove_neutron_ports(task, params): """Deletes the neutron ports matched by params. :param task: a TaskManager instance. :param params: Dict of params to filter ports. :raises: NetworkError """ client = get_client(context=task.context) node_uuid = task.node.uuid try: response = client.list_ports(**params) except neutron_exceptions.NeutronClientException as e: msg = (_('Could not get given network VIF for %(node)s ' 'from neutron, possible network issue. %(exc)s') % { 'node': node_uuid, 'exc': e }) LOG.exception(msg) raise exception.NetworkError(msg) ports = response.get('ports', []) if not ports: LOG.debug('No ports to remove for node %s', node_uuid) return for port in ports: LOG.debug( 'Deleting neutron port %(vif_port_id)s of node ' '%(node_id)s.', { 'vif_port_id': port['id'], 'node_id': node_uuid }) if is_smartnic_port(port): wait_for_host_agent(client, port['binding:host_id']) try: client.delete_port(port['id']) # NOTE(mgoddard): Ignore if the port was deleted by nova. except neutron_exceptions.PortNotFoundClient: LOG.info('Port %s was not found while deleting.', port['id']) except neutron_exceptions.NeutronClientException as e: msg = (_('Could not remove VIF %(vif)s of node %(node)s, possibly ' 'a network issue: %(exc)s') % { 'vif': port['id'], 'node': node_uuid, 'exc': e }) LOG.exception(msg) raise exception.NetworkError(msg) LOG.info('Successfully removed node %(node_uuid)s neutron ports.', {'node_uuid': node_uuid})
def remove_neutron_ports(task, params): """Deletes the neutron ports matched by params. :param task: a TaskManager instance. :param params: Dict of params to filter ports. :raises: NetworkError """ client = get_client() node_uuid = task.node.uuid try: response = client.list_ports(**params) except neutron_exceptions.NeutronClientException as e: msg = (_('Could not get given network VIF for %(node)s ' 'from neutron, possible network issue. %(exc)s') % { 'node': node_uuid, 'exc': e }) LOG.exception(msg) raise exception.NetworkError(msg) ports = response.get('ports', []) if not ports: LOG.debug('No ports to remove for node %s', node_uuid) return for port in ports: LOG.debug( 'Deleting neutron port %(vif_port_id)s of node ' '%(node_id)s.', { 'vif_port_id': port['id'], 'node_id': node_uuid }) try: client.delete_port(port['id']) except neutron_exceptions.NeutronClientException as e: msg = (_('Could not remove VIF %(vif)s of node %(node)s, possibly ' 'a network issue: %(exc)s') % { 'vif': port['id'], 'node': node_uuid, 'exc': e }) LOG.exception(msg) raise exception.NetworkError(msg) LOG.info(_LI('Successfully removed node %(node_uuid)s neutron ports.'), {'node_uuid': node_uuid})
def unbind_neutron_port(port_id, client=None): """Unbind a neutron port Remove a neutron port's binding profile and host ID so that it returns to an unbound state. :param port_id: Neutron port ID. :param client: Optional a Neutron client object. :raises: NetworkError """ if not client: client = get_client() body = {'port': {'binding:host_id': '', 'binding:profile': {}}} try: client.update_port(port_id, body) # NOTE(vsaienko): Ignore if port was deleted before calling vif detach. except neutron_exceptions.PortNotFoundClient: LOG.info('Port %s was not found while unbinding.', port_id) except neutron_exceptions.NeutronClientException as e: msg = (_('Unable to clear binding profile for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % {'port_id': port_id, 'err': e}) LOG.exception(msg) raise exception.NetworkError(msg)
def unbind_neutron_port(port_id, client=None, context=None): """Unbind a neutron port Remove a neutron port's binding profile and host ID so that it returns to an unbound state. :param port_id: Neutron port ID. :param client: Optional a Neutron client object. :param context: request context :type context: ironic.common.context.RequestContext :raises: NetworkError """ body_unbind = {'port': {'binding:host_id': '', 'binding:profile': {}}} body_reset_mac = {'port': {'mac_address': None}} try: update_neutron_port(context, port_id, body_unbind, client) # NOTE(hjensas): We need to reset the mac address in a separate step. # Exception PortBound will be raised by neutron as it refuses to # update the mac address of a bound port if we attempt to unbind and # reset the mac in the same call. update_neutron_port(context, port_id, body_reset_mac, client) # NOTE(vsaienko): Ignore if port was deleted before calling vif detach. except neutron_exceptions.PortNotFoundClient: LOG.info('Port %s was not found while unbinding.', port_id) except neutron_exceptions.NeutronClientException as e: msg = (_('Unable to clear binding profile for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % {'port_id': port_id, 'err': e}) LOG.exception(msg) raise exception.NetworkError(msg)
def _get_network_by_uuid_or_name(client, uuid_or_name, net_type=_('network')): """Return a neutron network by UUID or name. :param client: A Neutron client object. :param uuid_or_name: network UUID or name :param net_type: human-readable network type for error messages :returns: A dict describing the neutron network. :raises: NetworkError on failure to contact Neutron :raises: InvalidParameterValue for missing or duplicated network """ try: network = client.find_network(uuid_or_name, ignore_missing=False) except openstack_exc.DuplicateResource: network_ids = [net.id for net in client.networks(name=uuid_or_name)] raise exception.InvalidParameterValue( _('More than one %(type)s was found for name %(name)s: %(nets)s') % {'name': uuid_or_name, 'nets': ', '.join(network_ids), 'type': net_type}) except openstack_exc.ResourceNotFound: raise exception.InvalidParameterValue( _('%(type)s with name or UUID %(uuid_or_name)s was not found') % {'type': net_type, 'uuid_or_name': uuid_or_name}) except openstack_exc.OpenStackCloudException as exc: raise exception.NetworkError(_('Could not retrieve network: %s') % exc) LOG.debug('Got network matching %(uuid_or_name)s: %(result)s', {'uuid_or_name': uuid_or_name, 'result': network}) return network
def unbind_neutron_port(port_id, client=None): """Unbind a neutron port Remove a neutron port's binding profile and host ID so that it returns to an unbound state. :param port_id: Neutron port ID. :param client: Optional a Neutron client object. :raises: NetworkError """ if not client: client = get_client() body = {'port': {'binding:host_id': '', 'binding:profile': {}}} try: client.update_port(port_id, body) except neutron_exceptions.NeutronClientException as e: msg = (_('Unable to clear binding profile for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % {'port_id': port_id, 'err': e}) LOG.exception(msg) raise exception.NetworkError(msg)
def wait_for_host_agent(client, host_id, target_state='up'): """Wait for neutron agent to become target state :param client: A Neutron client object. :param host_id: Agent host_id :param target_state: up: wait for up status, down: wait for down status :returns: boolean indicates the agent state matches param value target_state_up. :raises: exception.Invalid if 'target_state' is not valid. :raises: exception.NetworkError if host status didn't match the required status after max retry attempts. """ if target_state not in ['up', 'down']: raise exception.Invalid( 'Invalid requested agent state to validate, accepted values: ' 'up, down. Requested state: %(target_state)s' % { 'target_state': target_state}) LOG.debug('Validating host %(host_id)s agent is %(status)s', {'host_id': host_id, 'status': target_state}) is_alive = _validate_agent(client, host=host_id) LOG.debug('Agent on host %(host_id)s is %(status)s', {'host_id': host_id, 'status': 'up' if is_alive else 'down'}) if ((target_state == 'up' and is_alive) or (target_state == 'down' and not is_alive)): return True raise exception.NetworkError( 'Agent on host %(host)s failed to reach state %(state)s' % { 'host': host_id, 'state': target_state})
def _bind_flat_ports(self, task): LOG.debug("Binding flat network ports") client = neutron.get_client(context=task.context) for port_like_obj in task.ports + task.portgroups: vif_port_id = (port_like_obj.internal_info.get( common.TENANT_VIF_KEY) or port_like_obj.extra.get('vif_port_id')) if not vif_port_id: continue body = { 'port': { 'binding:host_id': task.node.uuid, 'binding:vnic_type': neutron.VNIC_BAREMETAL, 'mac_address': port_like_obj.address } } try: client.update_port(vif_port_id, body) except neutron_exceptions.NeutronClientException as e: msg = (_('Unable to set binding:host_id for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % { 'port_id': vif_port_id, 'err': e }) LOG.exception(msg) raise exception.NetworkError(msg)
def add_provisioning_network(self, task): """Add the provisioning network to a node. :param task: A TaskManager instance. :raises: NetworkError when failed to set binding:host_id """ LOG.debug("Binding flat network ports") node = task.node host_id = node.instance_info.get('nova_host_id') if not host_id: return client = neutron.get_client() for port_like_obj in task.ports + task.portgroups: vif_port_id = ( port_like_obj.internal_info.get(common.TENANT_VIF_KEY) or port_like_obj.extra.get('vif_port_id') ) if not vif_port_id: continue body = { 'port': { 'binding:host_id': host_id } } try: client.update_port(vif_port_id, body) except neutron_exceptions.NeutronClientException as e: msg = (_('Unable to set binding:host_id for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % {'port_id': vif_port_id, 'err': e}) LOG.exception(msg) raise exception.NetworkError(msg)
def wait_for_port_status(client, port_id, status): """Wait for port status to be the desired status :param client: A Neutron client object. :param port_id: Neutron port_id :param status: Port's target status, can be ACTIVE, DOWN ... etc. :returns: boolean indicates that the port status matches the required value passed by param status. :raises: InvalidParameterValue if the port does not exist. :raises: exception.NetworkError if port status didn't match the required status after max retry attempts. """ LOG.debug('Validating Port %(port_id)s status is %(status)s', { 'port_id': port_id, 'status': status }) port_info = _get_port_by_uuid(client, port_id) LOG.debug('Port %(port_id)s status is: %(status)s', { 'port_id': port_id, 'status': port_info['status'] }) if port_info['status'] == status: return True raise exception.NetworkError( 'Port %(port_id)s failed to reach status %(status)s' % { 'port_id': port_id, 'status': status })
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()
def _verify_security_groups(security_groups, client): if not security_groups: return try: neutron_sec_groups = ( client.list_security_groups().get('security_groups') or []) except neutron_exceptions.NeutronClientException as e: msg = (_("Could not retrieve neutron security groups %(exc)s") % {'exc': e}) LOG.exception(msg) raise exception.NetworkError(msg) existing_sec_groups = [sec_group['id'] for sec_group in neutron_sec_groups] missing_sec_groups = set(security_groups) - set(existing_sec_groups) if missing_sec_groups: msg = (_('Security Groups specified in Ironic config ' '%(ir-sg)s are not found') % {'ir-sg': list(missing_sec_groups)}) LOG.exception(msg) raise exception.NetworkError(msg)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node Attach a virtual interface to a node. It will use 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_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'] port_like_obj = get_free_port_like_object(task, vif_id) client = neutron.get_client() # Address is optional for portgroups if port_like_obj.address: # Check if the requested vif_id is a neutron port. If it is # then attempt to update the port's MAC address. try: 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_like_obj.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_like_obj.address }) int_info = port_like_obj.internal_info int_info[TENANT_VIF_KEY] = vif_id port_like_obj.internal_info = int_info port_like_obj.save() # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client)
def configure_tenant_networks(self, task): """Configure tenant networks for a node. :param task: A TaskManager instance. :raises: NetworkError """ node = task.node ports = task.ports LOG.info(_LI('Mapping instance ports to %s'), node.uuid) # TODO(russell_h): this is based on the broken assumption that the # number of Neutron ports will match the number of physical ports. # Instead, we should probably list ports for this instance in # Neutron and update all of those with the appropriate portmap. if not ports: msg = _("No ports are associated with node %s") % node.uuid LOG.error(msg) raise exception.NetworkError(msg) ports = [p for p in ports if not p.portgroup_id] portgroups = task.portgroups client = neutron.get_client() pobj_without_vif = 0 for port_like_obj in ports + portgroups: try: common.plug_port_to_tenant_network(task, port_like_obj, client=client) except exception.VifNotAttached: pobj_without_vif += 1 continue if pobj_without_vif == len(ports + portgroups): msg = _("No neutron ports or portgroups are associated with " "node %s") % node.uuid LOG.error(msg) raise exception.NetworkError(msg)
def test_update_port_address_unbind_port_failed(self, mock_unp, mock_client): address = 'fe:54:00:77:07:d9' port_id = 'fake-port-id' mock_client.return_value.show_port.return_value = { 'port': { 'binding:profile': 'foo', 'binding:host_id': 'host' } } mock_unp.side_effect = (exception.NetworkError('boom')) self.assertRaises(exception.FailedToUpdateMacOnPort, neutron.update_port_address, port_id, address) mock_unp.assert_called_once_with(port_id, client=mock_client()) self.assertFalse(mock_client.return_value.update_port.called)
def validate_network(uuid_or_name, net_type=_('network')): """Check that the given network is present. :param uuid_or_name: network UUID or name :param net_type: human-readable network type for error messages :return: network UUID :raises: MissingParameterValue if uuid_or_name is empty :raises: NetworkError on failure to contact Neutron :raises: InvalidParameterValue for missing or duplicated network """ if not uuid_or_name: raise exception.MissingParameterValue( _('UUID or name of %s is not set in configuration') % net_type) if uuidutils.is_uuid_like(uuid_or_name): filters = {'id': uuid_or_name} else: filters = {'name': uuid_or_name} try: client = get_client() networks = client.list_networks(fields=['id'], **filters) except neutron_exceptions.NeutronClientException as exc: raise exception.NetworkError( _('Could not retrieve network list: %s') % exc) LOG.debug('Got list of networks matching %(cond)s: %(result)s', { 'cond': filters, 'result': networks }) networks = [n['id'] for n in networks.get('networks', [])] if not networks: raise exception.InvalidParameterValue( _('%(type)s with name or UUID %(uuid_or_name)s was not found') % { 'type': net_type, 'uuid_or_name': uuid_or_name }) elif len(networks) > 1: raise exception.InvalidParameterValue( _('More than one %(type)s was found for name %(name)s: %(nets)s') % { 'name': uuid_or_name, 'nets': ', '.join(networks), 'type': net_type }) return networks[0]
def _get_network_by_uuid_or_name(client, uuid_or_name, net_type=_('network'), **params): """Return a neutron network by UUID or name. :param client: A Neutron client object. :param uuid_or_name: network UUID or name :param net_type: human-readable network type for error messages :param params: Additional parameters to pass to the neutron client list_networks method. :returns: A dict describing the neutron network. :raises: NetworkError on failure to contact Neutron :raises: InvalidParameterValue for missing or duplicated network """ if uuidutils.is_uuid_like(uuid_or_name): params['id'] = uuid_or_name else: params['name'] = uuid_or_name try: networks = client.list_networks(**params) except neutron_exceptions.NeutronClientException as exc: raise exception.NetworkError( _('Could not retrieve network list: %s') % exc) LOG.debug('Got list of networks matching %(cond)s: %(result)s', { 'cond': params, 'result': networks }) networks = networks.get('networks', []) if not networks: raise exception.InvalidParameterValue( _('%(type)s with name or UUID %(uuid_or_name)s was not found') % { 'type': net_type, 'uuid_or_name': uuid_or_name }) elif len(networks) > 1: network_ids = [n['id'] for n in networks] raise exception.InvalidParameterValue( _('More than one %(type)s was found for name %(name)s: %(nets)s') % { 'name': uuid_or_name, 'nets': ', '.join(network_ids), 'type': net_type }) return networks[0]
def _get_fixed_ip_address(self, port_id, client): """Get a Neutron port's fixed ip address. :param port_id: Neutron port id. :param client: Neutron client instance. :returns: Neutron port ip address. :raises: NetworkError :raises: InvalidIPv4Address :raises: FailedToGetIPAddressOnPort """ ip_address = None try: neutron_port = client.get_port(port_id) except openstack_exc.OpenStackCloudException: raise exception.NetworkError( _('Could not retrieve neutron port: %s') % port_id) fixed_ips = neutron_port.get('fixed_ips') # NOTE(faizan) At present only the first fixed_ip assigned to this # neutron port will be used, since nova allocates only one fixed_ip # for the instance. if fixed_ips: ip_address = fixed_ips[0].get('ip_address', None) if ip_address: try: if (ipaddress.ip_address(ip_address).version == 4 or ipaddress.ip_address(ip_address).version == 6): return ip_address else: LOG.error( "Neutron returned invalid IP " "address %(ip_address)s on port %(port_id)s.", { 'ip_address': ip_address, 'port_id': port_id }) raise exception.InvalidIPv4Address(ip_address=ip_address) except ValueError as exc: LOG.error( "An Invalid IP address was supplied and failed " "basic validation: %s", exc) raise exception.InvalidIPAddress(ip_address=ip_address) else: LOG.error("No IP address assigned to Neutron port %s.", port_id) raise exception.FailedToGetIPAddressOnPort(port_id=port_id)
def _validate_agent(client, **kwargs): """Check that the given neutron agent is alive :param client: Neutron client :param kwargs: Additional parameters to pass to the neutron client list_agents method. :returns: A boolean to describe the agent status, if more than one agent returns by the client then return True if at least one of them is alive. :raises: NetworkError in case of failure contacting Neutron. """ try: agents = client.list_agents(**kwargs)['agents'] for agent in agents: if agent['alive']: return True return False except neutron_exceptions.NeutronClientException: raise exception.NetworkError('Failed to contact Neutron server')
def _get_port_by_uuid(client, port_uuid): """Return a neutron port by UUID. :param client: A Neutron client object. :param port_uuid: UUID of a Neutron port to query. :returns: A dict describing the neutron port. :raises: InvalidParameterValue if the port does not exist. :raises: NetworkError on failure to contact Neutron. """ try: port = client.get_port(port_uuid) except openstack_exc.ResourceNotFound: raise exception.InvalidParameterValue( _('Neutron port %(port_uuid)s was not found') % {'port_uuid': port_uuid}) except openstack_exc.OpenStackCloudException as exc: raise exception.NetworkError( _('Could not retrieve neutron port: %s') % exc) return port
def _get_port_by_uuid(client, port_uuid, **params): """Return a neutron port by UUID. :param client: A Neutron client object. :param port_uuid: UUID of a Neutron port to query. :param params: Additional parameters to pass to the neutron client show_port method. :returns: A dict describing the neutron port. :raises: InvalidParameterValue if the port does not exist. :raises: NetworkError on failure to contact Neutron. """ try: port = client.show_port(port_uuid, **params) except neutron_exceptions.PortNotFoundClient: raise exception.InvalidParameterValue( _('Neutron port %(port_uuid)s was not found') % {'port_uuid': port_uuid}) except neutron_exceptions.NeutronClientException as exc: raise exception.NetworkError(_('Could not retrieve neutron port: %s') % exc) return port['port']
def _bind_flat_ports(self, task): LOG.debug("Binding flat network ports") for port_like_obj in task.ports + task.portgroups: vif_port_id = ( port_like_obj.internal_info.get(common.TENANT_VIF_KEY) or port_like_obj.extra.get('vif_port_id') ) if not vif_port_id: continue port_attrs = {'binding:host_id': task.node.uuid, 'binding:vnic_type': neutron.VNIC_BAREMETAL, 'mac_address': port_like_obj.address} try: neutron.update_neutron_port(task.context, vif_port_id, port_attrs) except openstack_exc.OpenStackCloudException as e: msg = (_('Unable to set binding:host_id for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % {'port_id': vif_port_id, 'err': e}) LOG.exception(msg) raise exception.NetworkError(msg)
def _get_fixed_ip_address(self, port_uuid, client): """Get a Neutron port's fixed ip address. :param port_uuid: Neutron port id. :param client: Neutron client instance. :returns: Neutron port ip address. :raises: NetworkError :raises: InvalidIPv4Address :raises: FailedToGetIPAddressOnPort """ ip_address = None try: neutron_port = client.show_port(port_uuid).get('port') except neutron_client_exc.NeutronClientException: raise exception.NetworkError( _('Could not retrieve neutron port: %s') % port_uuid) fixed_ips = neutron_port.get('fixed_ips') # NOTE(faizan) At present only the first fixed_ip assigned to this # neutron port will be used, since nova allocates only one fixed_ip # for the instance. if fixed_ips: ip_address = fixed_ips[0].get('ip_address', None) if ip_address: if netutils.is_valid_ipv4(ip_address): return ip_address else: LOG.error( "Neutron returned invalid IPv4 " "address %(ip_address)s on port %(port_uuid)s.", { 'ip_address': ip_address, 'port_uuid': port_uuid }) raise exception.InvalidIPv4Address(ip_address=ip_address) else: LOG.error("No IP address assigned to Neutron port %s.", port_uuid) raise exception.FailedToGetIPAddressOnPort(port_id=port_uuid)
def add_provisioning_network(self, task): """Add the provisioning network to a node. :param task: A TaskManager instance. :raises: NetworkError when failed to set binding:host_id """ LOG.debug("Binding flat network ports") node = task.node host_id = node.instance_info.get('nova_host_id') if not host_id: return # FIXME(sambetts): Uncomment when we support vifs attached to # portgroups # # ports = [p for p in task.ports if not p.portgroup_id] # portgroups = task.portgroups client = neutron.get_client(task.context.auth_token) for port_like_obj in task.ports: # + portgroups: vif_port_id = ( port_like_obj.extra.get('vif_port_id') or port_like_obj.internal_info.get('tenant_vif_port_id')) if not vif_port_id: continue body = {'port': {'binding:host_id': host_id}} try: client.update_port(vif_port_id, body) except neutron_exceptions.NeutronClientException as e: msg = (_('Unable to set binding:host_id for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % { 'port_id': vif_port_id, 'err': e }) LOG.exception(msg) raise exception.NetworkError(msg)
def plug_port_to_tenant_network(task, port_like_obj, client=None): """Plug port like object to tenant network. :param task: A TaskManager instance. :param port_like_obj: port-like object to plug. :param client: Neutron client instance. :raises NetworkError: if failed to update Neutron port. :raises VifNotAttached if tenant VIF is not associated with port_like_obj. """ node = task.node local_link_info = [] client_id_opt = None vif_id = (port_like_obj.internal_info.get(TENANT_VIF_KEY) or port_like_obj.extra.get('vif_port_id')) if not vif_id: obj_name = port_like_obj.__class__.__name__.lower() raise exception.VifNotAttached( _("Tenant VIF is not associated with %(obj_name)s " "%(obj_id)s") % { 'obj_name': obj_name, 'obj_id': port_like_obj.uuid }) LOG.debug('Mapping tenant port %(vif_id)s to node ' '%(node_id)s', { 'vif_id': vif_id, 'node_id': node.uuid }) if isinstance(port_like_obj, objects.Portgroup): pg_ports = [ p for p in task.ports if p.portgroup_id == port_like_obj.id ] for port in pg_ports: local_link_info.append(port.local_link_connection) else: # We iterate only on ports or portgroups, no need to check # that it is a port local_link_info.append(port_like_obj.local_link_connection) client_id = port_like_obj.extra.get('client-id') if client_id: client_id_opt = ({'opt_name': 'client-id', 'opt_value': client_id}) # NOTE(sambetts) Only update required binding: attributes, # because other port attributes may have been set by the user or # nova. body = { 'port': { 'binding:vnic_type': 'baremetal', 'binding:host_id': node.uuid, 'binding:profile': { 'local_link_information': local_link_info, }, } } if client_id_opt: body['port']['extra_dhcp_opts'] = [client_id_opt] if not client: client = neutron.get_client() try: client.update_port(vif_id, body) except neutron_exceptions.ConnectionFailed as e: msg = (_('Could not add public network VIF %(vif)s ' 'to node %(node)s, possible network issue. %(exc)s') % { 'vif': vif_id, 'node': node.uuid, 'exc': e }) LOG.error(msg) raise exception.NetworkError(msg)
def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node Attach a virtual interface to a 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_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 :raises: PortgroupPhysnetInconsistent if one of the node's portgroups has ports which are not all assigned the same physical network. """ vif_id = vif_info['id'] client = neutron.get_client(context=task.context) # Determine whether any of the node's ports have a physical network. If # not, we don't need to check the VIF's network's physical networks as # they will not affect the VIF to port mapping. physnets = set() if any(port.physical_network is not None for port in task.ports): physnets = neutron.get_physnets_by_port_uuid(client, vif_id) if len(physnets) > 1: # NOTE(mgoddard): Neutron cannot currently handle hosts which # are mapped to multiple segments in the same routed network. node_physnets = network.get_physnets_for_node(task) if len(node_physnets.intersection(physnets)) > 1: reason = _("Node has ports which map to multiple segments " "of the routed network to which the VIF is " "attached. Currently neutron only supports " "hosts which map to one segment of a routed " "network") raise exception.VifInvalidForAttach(node=task.node.uuid, vif=vif_id, reason=reason) port_like_obj = get_free_port_like_object(task, vif_id, physnets) # Address is optional for portgroups if port_like_obj.address: try: neutron.update_port_address(vif_id, port_like_obj.address, context=task.context) 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_like_obj.address }) self._save_vif_to_port_like_obj(port_like_obj, vif_id) # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client)
def plug_port_to_tenant_network(task, port_like_obj, client=None): """Plug port like object to tenant network. :param task: A TaskManager instance. :param port_like_obj: port-like object to plug. :param client: Neutron client instance. :raises: NetworkError if failed to update Neutron port. :raises: VifNotAttached if tenant VIF is not associated with port_like_obj. """ node = task.node local_link_info = [] local_group_info = None client_id_opt = None vif_id = (port_like_obj.internal_info.get(TENANT_VIF_KEY) or port_like_obj.extra.get('vif_port_id')) if not vif_id: obj_name = port_like_obj.__class__.__name__.lower() raise exception.VifNotAttached( _("Tenant VIF is not associated with %(obj_name)s " "%(obj_id)s") % { 'obj_name': obj_name, 'obj_id': port_like_obj.uuid }) LOG.debug('Mapping tenant port %(vif_id)s to node ' '%(node_id)s', { 'vif_id': vif_id, 'node_id': node.uuid }) if isinstance(port_like_obj, objects.Portgroup): pg_ports = [ p for p in task.ports if p.portgroup_id == port_like_obj.id ] for port in pg_ports: local_link_info.append(port.local_link_connection) local_group_info = neutron.get_local_group_information( task, port_like_obj) else: # We iterate only on ports or portgroups, no need to check # that it is a port local_link_info.append(port_like_obj.local_link_connection) client_id = port_like_obj.extra.get('client-id') if client_id: client_id_opt = ({ 'opt_name': DHCP_CLIENT_ID, 'opt_value': client_id }) # NOTE(sambetts) Only update required binding: attributes, # because other port attributes may have been set by the user or # nova. port_attrs = { 'binding:vnic_type': neutron.VNIC_BAREMETAL, 'binding:host_id': node.uuid } # NOTE(kaifeng) Only update mac address when it's available if port_like_obj.address: port_attrs['mac_address'] = port_like_obj.address binding_profile = {'local_link_information': local_link_info} if local_group_info: binding_profile['local_group_information'] = local_group_info port_attrs['binding:profile'] = binding_profile if client_id_opt: port_attrs['extra_dhcp_opts'] = [client_id_opt] is_smart_nic = neutron.is_smartnic_port(port_like_obj) if is_smart_nic: link_info = local_link_info[0] LOG.debug( 'Setting hostname as host_id in case of Smart NIC, ' 'port %(port_id)s, hostname %(hostname)s', { 'port_id': vif_id, 'hostname': link_info['hostname'] }) port_attrs['binding:host_id'] = link_info['hostname'] port_attrs['binding:vnic_type'] = neutron.VNIC_SMARTNIC if not client: client = neutron.get_client(context=task.context) if is_smart_nic: neutron.wait_for_host_agent(client, port_attrs['binding:host_id']) try: neutron.update_neutron_port(task.context, vif_id, port_attrs) if is_smart_nic: neutron.wait_for_port_status(client, vif_id, 'ACTIVE') except openstack_exc.OpenStackCloudException as e: msg = (_('Could not add public network VIF %(vif)s ' 'to node %(node)s, possible network issue. %(exc)s') % { 'vif': vif_id, 'node': node.uuid, 'exc': e }) LOG.error(msg) raise exception.NetworkError(msg)
def configure_tenant_networks(self, task): """Configure tenant networks for a node. :param task: A TaskManager instance. :raises: NetworkError """ node = task.node ports = task.ports LOG.info(_LI('Mapping instance ports to %s'), node.uuid) # TODO(russell_h): this is based on the broken assumption that the # number of Neutron ports will match the number of physical ports. # Instead, we should probably list ports for this instance in # Neutron and update all of those with the appropriate portmap. if not ports: msg = _("No ports are associated with node %s") % node.uuid LOG.error(msg) raise exception.NetworkError(msg) ports = [p for p in ports if not p.portgroup_id] portgroups = task.portgroups portmap = neutron.get_node_portmap(task) client = neutron.get_client(task.context.auth_token) pobj_without_vif = 0 for port_like_obj in ports + portgroups: vif_port_id = ( port_like_obj.internal_info.get('tenant_vif_port_id') or port_like_obj.extra.get('vif_port_id')) if not vif_port_id: pobj_without_vif += 1 continue LOG.debug( 'Mapping tenant port %(vif_port_id)s to node ' '%(node_id)s', { 'vif_port_id': vif_port_id, 'node_id': node.uuid }) local_link_info = [] client_id_opt = None if isinstance(port_like_obj, objects.Portgroup): pg_ports = [ p for p in task.ports if p.portgroup_id == port_like_obj.id ] for port in pg_ports: local_link_info.append(portmap[port.uuid]) else: # We iterate only on ports or portgroups, no need to check # that it is a port local_link_info.append(portmap[port_like_obj.uuid]) client_id = port_like_obj.extra.get('client-id') if client_id: client_id_opt = ({ 'opt_name': 'client-id', 'opt_value': client_id }) body = { 'port': { 'device_owner': 'baremetal:none', 'device_id': node.instance_uuid or node.uuid, 'admin_state_up': True, 'binding:vnic_type': 'baremetal', 'binding:host_id': node.uuid, 'binding:profile': { 'local_link_information': local_link_info, }, } } if client_id_opt: body['port']['extra_dhcp_opts'] = [client_id_opt] try: client.update_port(vif_port_id, body) except neutron_exceptions.ConnectionFailed as e: msg = (_('Could not add public network VIF %(vif)s ' 'to node %(node)s, possible network issue. %(exc)s') % { 'vif': vif_port_id, 'node': node.uuid, 'exc': e }) LOG.error(msg) raise exception.NetworkError(msg) if pobj_without_vif == len(ports + portgroups): msg = _("No neutron ports or portgroups are associated with " "node %s") % node.uuid LOG.error(msg) raise exception.NetworkError(msg)
def add_ports_to_network(task, network_uuid, security_groups=None): """Create neutron ports to boot the ramdisk. Create neutron ports for each pxe_enabled port on task.node to boot the ramdisk. If the config option 'neutron.add_all_ports' is set, neutron ports for non-pxe-enabled ports are also created -- these neutron ports will not have any assigned IP addresses. :param task: a TaskManager instance. :param network_uuid: UUID of a neutron network where ports will be created. :param security_groups: List of Security Groups UUIDs to be used for network. :raises: NetworkError :returns: a dictionary in the form {port.uuid: neutron_port['id']} """ client = get_client(context=task.context) node = task.node add_all_ports = CONF.neutron.add_all_ports # If Security Groups are specified, verify that they exist _verify_security_groups(security_groups, client) LOG.debug('For node %(node)s, creating neutron ports on network ' '%(network_uuid)s using %(net_iface)s network interface.', {'net_iface': task.driver.network.__class__.__name__, 'node': node.uuid, 'network_uuid': network_uuid}) body = { 'port': { 'network_id': network_uuid, 'admin_state_up': True, 'binding:vnic_type': VNIC_BAREMETAL, } } # separate out fields that can only be updated by admins update_body = { 'port': { 'binding:host_id': node.uuid, 'device_owner': 'baremetal:none', } } if security_groups: body['port']['security_groups'] = security_groups # Since instance_uuid will not be available during cleaning # operations, we need to check that and populate them only when # available body['port']['device_id'] = node.instance_uuid or node.uuid ports = {} failures = [] portmap = get_node_portmap(task) if not add_all_ports: ports_to_create = [p for p in task.ports if p.pxe_enabled] else: ports_to_create = task.ports if not ports_to_create: pxe_enabled = 'PXE-enabled ' if not add_all_ports else '' raise exception.NetworkError(_( "No available %(enabled)sports on node %(node)s.") % {'enabled': pxe_enabled, 'node': node.uuid}) for ironic_port in ports_to_create: # Start with a clean state for each port port_body = copy.deepcopy(body) update_port_body = copy.deepcopy(update_body) # Skip ports that are missing required information for deploy. if not validate_port_info(node, ironic_port): failures.append(ironic_port.uuid) continue update_port_body['port']['mac_address'] = ironic_port.address binding_profile = {'local_link_information': [portmap[ironic_port.uuid]]} update_port_body['port']['binding:profile'] = binding_profile if not ironic_port.pxe_enabled: LOG.debug("Adding port %(port)s to network %(net)s for " "provisioning without an IP allocation.", {'port': ironic_port.uuid, 'net': network_uuid}) port_body['fixed_ips'] = [] is_smart_nic = is_smartnic_port(ironic_port) if is_smart_nic: link_info = binding_profile['local_link_information'][0] LOG.debug('Setting hostname as host_id in case of Smart NIC, ' 'port %(port_id)s, hostname %(hostname)s', {'port_id': ironic_port.uuid, 'hostname': link_info['hostname']}) update_port_body['port']['binding:host_id'] = link_info['hostname'] # TODO(hamdyk): use portbindings.VNIC_SMARTNIC from neutron-lib port_body['port']['binding:vnic_type'] = VNIC_SMARTNIC client_id = ironic_port.extra.get('client-id') if client_id: client_id_opt = {'opt_name': DHCP_CLIENT_ID, 'opt_value': client_id} extra_dhcp_opts = port_body['port'].get('extra_dhcp_opts', []) extra_dhcp_opts.append(client_id_opt) port_body['port']['extra_dhcp_opts'] = extra_dhcp_opts try: if is_smart_nic: wait_for_host_agent( client, update_port_body['port']['binding:host_id']) port = client.create_port(port_body) update_neutron_port(task.context, port['port']['id'], update_port_body) if CONF.neutron.dhcpv6_stateful_address_count > 1: _add_ip_addresses_for_ipv6_stateful(task.context, port, client) if is_smart_nic: wait_for_port_status(client, port['port']['id'], 'ACTIVE') except neutron_exceptions.NeutronClientException as e: failures.append(ironic_port.uuid) LOG.warning("Could not create neutron port for node's " "%(node)s port %(ir-port)s on the neutron " "network %(net)s. %(exc)s", {'net': network_uuid, 'node': node.uuid, 'ir-port': ironic_port.uuid, 'exc': e}) else: ports[ironic_port.uuid] = port['port']['id'] if failures: if len(failures) == len(ports_to_create): rollback_ports(task, network_uuid) raise exception.NetworkError(_( "Failed to create neutron ports for node's %(node)s ports " "%(ports)s.") % {'node': node.uuid, 'ports': ports_to_create}) else: LOG.warning("Some errors were encountered when updating " "vif_port_id for node %(node)s on " "the following ports: %(ports)s.", {'node': node.uuid, 'ports': failures}) else: LOG.info('For node %(node_uuid)s in network %(net)s, successfully ' 'created ports (ironic ID: neutron ID): %(ports)s.', {'node_uuid': node.uuid, 'net': network_uuid, 'ports': ports}) return ports