def update_port_address(self, port_id, address, token=None): """Update a port's mac address. :param port_id: Neutron port id. :param address: new MAC address. :param token: optional auth token. :raises: FailedToUpdateMacOnPort """ client = neutron.get_client(token) port_req_body = {'port': {'mac_address': address}} current_binding = self._get_binding(client, port_id) if current_binding: binding_clean_body = {'port': {'binding:host_id': ''}} try: client.update_port(port_id, binding_clean_body) except neutron_client_exc.NeutronClientException: LOG.exception(_LE("Failed to remove the current binding from " "Neutron port %s."), port_id) raise exception.FailedToUpdateMacOnPort(port_id=port_id) port_req_body['port']['binding:host_id'] = current_binding try: neutron.get_client(token).update_port(port_id, port_req_body) except neutron_client_exc.NeutronClientException: LOG.exception(_LE("Failed to update MAC address on Neutron " "port %s."), port_id) raise exception.FailedToUpdateMacOnPort(port_id=port_id)
def update_port_address(self, port_id, address, token=None): """Update a port's mac address. :param port_id: Neutron port id. :param address: new MAC address. :param token: optional auth token. :raises: FailedToUpdateMacOnPort """ client = neutron.get_client(token) port_req_body = {'port': {'mac_address': address}} current_binding = self._get_binding(client, port_id) if current_binding: binding_clean_body = {'port': {'binding:host_id': ''}} try: client.update_port(port_id, binding_clean_body) except neutron_client_exc.NeutronClientException: LOG.exception( _LE("Failed to remove the current binding from " "Neutron port %s."), port_id) raise exception.FailedToUpdateMacOnPort(port_id=port_id) port_req_body['port']['binding:host_id'] = current_binding try: neutron.get_client(token).update_port(port_id, port_req_body) except neutron_client_exc.NeutronClientException: LOG.exception( _LE("Failed to update MAC address on Neutron " "port %s."), port_id) raise exception.FailedToUpdateMacOnPort(port_id=port_id)
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None, context=None): """Update a port's attributes. Update one or more DHCP options on the specified port. For the relevant API spec, see https://developer.openstack.org/api-ref/network/v2/index.html#update-port :param port_id: designate which port these attributes will be applied to. :param dhcp_options: this will be a list of dicts, e.g. :: [{'opt_name': '67', 'opt_value': 'pxelinux.0'}, {'opt_name': '66', 'opt_value': '123.123.123.456'}] :param token: optional auth token. Deprecated, use context. :param context: request context :type context: ironic.common.context.RequestContext :raises: FailedToUpdateDHCPOptOnPort """ super(NeutronDHCPApi, self).update_port_dhcp_opts( port_id, dhcp_options, token=token, context=context) port_req_body = {'port': {'extra_dhcp_opts': dhcp_options}} try: neutron.get_client(token=token, context=context).update_port( port_id, port_req_body) except neutron_client_exc.NeutronClientException: LOG.exception("Failed to update Neutron port %s.", port_id) raise exception.FailedToUpdateDHCPOptOnPort(port_id=port_id)
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None): """Update a port's attributes. Update one or more DHCP options on the specified port. For the relevant API spec, see http://docs.openstack.org/api/openstack-network/2.0/content/extra-dhc-opt-ext-update.html :param port_id: designate which port these attributes will be applied to. :param dhcp_options: this will be a list of dicts, e.g. :: [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'}, {'opt_name': 'server-ip-address', 'opt_value': '123.123.123.456'}, {'opt_name': 'tftp-server', 'opt_value': '123.123.123.123'}] :param token: optional auth token. :raises: FailedToUpdateDHCPOptOnPort """ port_req_body = {'port': {'extra_dhcp_opts': dhcp_options}} try: neutron.get_client(token).update_port(port_id, port_req_body) except neutron_client_exc.NeutronClientException: LOG.exception(_LE("Failed to update Neutron port %s."), port_id) raise exception.FailedToUpdateDHCPOptOnPort(port_id=port_id)
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None): """Update a port's attributes. Update one or more DHCP options on the specified port. For the relevant API spec, see http://docs.openstack.org/api/openstack-network/2.0/content/extra-dhc-opt-ext-update.html :param port_id: designate which port these attributes will be applied to. :param dhcp_options: this will be a list of dicts, e.g. :: [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'}, {'opt_name': 'server-ip-address', 'opt_value': '123.123.123.456'}, {'opt_name': 'tftp-server', 'opt_value': '123.123.123.123'}] :param token: optional auth token. :raises: FailedToUpdateDHCPOptOnPort """ port_req_body = {'port': {'extra_dhcp_opts': dhcp_options}} try: neutron.get_client(token).update_port(port_id, port_req_body) except neutron_client_exc.NeutronClientException: LOG.exception("Failed to update Neutron port %s.", port_id) raise exception.FailedToUpdateDHCPOptOnPort(port_id=port_id)
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None, context=None): """Update a port's attributes. Update one or more DHCP options on the specified port. For the relevant API spec, see http://docs.openstack.org/api/openstack-network/2.0/content/extra-dhc-opt-ext-update.html :param port_id: designate which port these attributes will be applied to. :param dhcp_options: this will be a list of dicts, e.g. :: [{'opt_name': '67', 'opt_value': 'pxelinux.0'}, {'opt_name': '66', 'opt_value': '123.123.123.456'}] :param token: optional auth token. Deprecated, use context. :param context: request context :type context: ironic.common.context.RequestContext :raises: FailedToUpdateDHCPOptOnPort """ super(NeutronDHCPApi, self).update_port_dhcp_opts( port_id, dhcp_options, token=token, context=context) port_req_body = {'port': {'extra_dhcp_opts': dhcp_options}} try: neutron.get_client(token=token, context=context).update_port( port_id, port_req_body) except neutron_client_exc.NeutronClientException: LOG.exception("Failed to update Neutron port %s.", port_id) raise exception.FailedToUpdateDHCPOptOnPort(port_id=port_id)
def _call_and_assert_client(self, client_mock, url, auth=mock.sentinel.auth): neutron.get_client(context=self.context) client_mock.assert_called_once_with(mock.ANY, # this is 'self' session=mock.sentinel.session, auth=auth, retries=2, endpoint_override=url, global_request_id='global')
def test_get_neutron_client_without_token(self, mock_client_init, mock_session): self.config(url='test-url', group='neutron') sess = mock.Mock() mock_session.return_value = sess expected = {'retries': 2, 'endpoint_override': 'test-url', 'session': sess} mock_client_init.return_value = None neutron.get_client(token=None) mock_client_init.assert_called_once_with(**expected)
def test_get_neutron_client_noauth(self, mock_client_init): self.config(auth_strategy='noauth', group='neutron') expected = {'ca_cert': 'test-file', 'insecure': False, 'endpoint_url': 'test-url', 'timeout': 30, 'retries': 2, 'auth_strategy': 'noauth'} mock_client_init.return_value = None neutron.get_client(token=None) mock_client_init.assert_called_once_with(**expected)
def test_get_neutron_client_with_region(self, mock_client_init, mock_session): self.config(region_name='fake_region', group='keystone') sess = mock.Mock() mock_session.return_value = sess expected = {'retries': 2, 'region_name': 'fake_region', 'session': sess} mock_client_init.return_value = None neutron.get_client(token=None) mock_client_init.assert_called_once_with(**expected)
def test_get_neutron_client_noauth(self, mock_client_init, mock_session): self.config(auth_strategy='noauth', url='test-url', group='neutron') expected = { 'ca_cert': 'test-file', 'insecure': False, 'endpoint_url': 'test-url', 'timeout': 30, 'retries': 2, 'auth_strategy': 'noauth' } mock_client_init.return_value = None neutron.get_client(token=None) mock_client_init.assert_called_once_with(**expected)
def update_port_address(self, port_id, address, token=None): """Update a port's mac address. :param port_id: Neutron port id. :param address: new MAC address. :param token: optional auth token. :raises: FailedToUpdateMacOnPort """ port_req_body = {'port': {'mac_address': address}} try: neutron.get_client(token).update_port(port_id, port_req_body) except neutron_client_exc.NeutronClientException: LOG.exception(_LE("Failed to update MAC address on Neutron " "port %s."), port_id) raise exception.FailedToUpdateMacOnPort(port_id=port_id)
def test_get_neutron_client_without_token(self, mock_client_init): expected = {'timeout': 30, 'retries': 2, 'insecure': False, 'ca_cert': 'test-file', 'token': None, 'endpoint_url': 'test-url', 'username': '******', 'tenant_name': 'test-admin-tenant', 'password': '******', 'auth_url': 'test-auth-uri'} mock_client_init.return_value = None neutron.get_client(token=None) mock_client_init.assert_called_once_with(**expected)
def delete_cleaning_ports(self, task): """Deletes the neutron port created for booting the ramdisk. :param task: a TaskManager instance. """ neutron_client = neutron.get_client(task.context.auth_token) macs = [p.address for p in task.ports] params = { 'network_id': CONF.neutron.cleaning_network_uuid } try: ports = neutron_client.list_ports(**params) except neutron_client_exc.ConnectionFailed as e: msg = (_('Could not get cleaning network vif for %(node)s ' 'from Neutron, possible network issue. %(exc)s') % {'node': task.node.uuid, 'exc': e}) LOG.exception(msg) raise exception.NodeCleaningFailure(msg) # Iterate the list of Neutron port dicts, remove the ones we added for neutron_port in ports.get('ports', []): # Only delete ports using the node's mac addresses if neutron_port.get('mac_address') in macs: try: neutron_client.delete_port(neutron_port.get('id')) except neutron_client_exc.ConnectionFailed as e: msg = (_('Could not remove cleaning ports on network ' '%(net)s from %(node)s, possible network issue. ' '%(exc)s') % {'net': CONF.neutron.cleaning_network_uuid, 'node': task.node.uuid, 'exc': e}) LOG.exception(msg) raise exception.NodeCleaningFailure(msg)
def power_on_node_if_needed(task): """Powers on node if it is powered off and has a Smart NIC port :param task: A TaskManager object :returns: the previous power state or None if no changes were made :raises: exception.NetworkError if agent status didn't match the required status after max retry attempts. """ if not task.driver.network.need_power_on(task): return previous_power_state = task.driver.power.get_power_state(task) if previous_power_state == states.POWER_OFF: node_set_boot_device( task, boot_devices.BIOS, persistent=False) node_power_action(task, states.POWER_ON) # local import is necessary to avoid circular import from ironic.common import neutron host_id = None for port in task.ports: if neutron.is_smartnic_port(port): link_info = port.local_link_connection host_id = link_info['hostname'] break if host_id: LOG.debug('Waiting for host %(host)s agent to be down', {'host': host_id}) client = neutron.get_client(context=task.context) neutron.wait_for_host_agent( client, host_id, target_state='down') return previous_power_state
def unconfigure_tenant_networks(self, task): """Unconfigure tenant networks for a node. Nova takes care of port removal from tenant network, we unbind it here/now to avoid the possibility of the ironic port being bound to the tenant and cleaning networks at the same time. :param task: A TaskManager instance. :raises: NetworkError """ node = task.node LOG.info('Unbinding instance ports from node %s', node.uuid) ports = [p for p in task.ports if not p.portgroup_id] portgroups = task.portgroups for port_like_obj in ports + 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 is_smart_nic = neutron.is_smartnic_port(port_like_obj) if is_smart_nic: client = neutron.get_client(context=task.context) link_info = port_like_obj.local_link_connection neutron.wait_for_host_agent(client, link_info['hostname']) neutron.unbind_neutron_port(vif_port_id, context=task.context)
def update_port_dhcp_opts(self, port_id, dhcp_options, token=None, context=None): """Update a port's attributes. Update one or more DHCP options on the specified port. For the relevant API spec, see https://docs.openstack.org/api-ref/network/v2/index.html#update-port :param port_id: designate which port these attributes will be applied to. :param dhcp_options: this will be a list of dicts, e.g. :: [{'opt_name': '67', 'opt_value': 'pxelinux.0', 'ip_version': 4}, {'opt_name': '66', 'opt_value': '123.123.123.456'}, 'ip_version': 4}] :param token: optional auth token. Deprecated, use context. :param context: request context :type context: ironic.common.context.RequestContext :raises: FailedToUpdateDHCPOptOnPort """ super(NeutronDHCPApi, self).update_port_dhcp_opts( port_id, dhcp_options, token=token, context=context) try: neutron_client = neutron.get_client(token=token, context=context) fip = None port = neutron_client.get_port(port_id) try: if port: # TODO(TheJulia): We need to retool this down the # road so that we handle ports and allow preferences # for multi-address ports with different IP versions # and enable operators to possibly select preferences # for provisionioning operations. # This is compounded by v6 mainly only being available # with UEFI machines, so the support matrix also gets # a little "weird". # Ideally, we should work on this in Victoria. fip = port.get('fixed_ips')[0] except (TypeError, IndexError): fip = None update_opts = [] if fip: ip_version = ipaddress.ip_address(fip['ip_address']).version for option in dhcp_options: if option.get('ip_version', 4) == ip_version: update_opts.append(option) else: LOG.error('Requested to update port for port %s, ' 'however port lacks an IP address.', port_id) port_attrs = {'extra_dhcp_opts': update_opts} neutron.update_neutron_port(context, port_id, port_attrs) except openstack_exc.OpenStackCloudException: LOG.exception("Failed to update Neutron port %s.", port_id) raise exception.FailedToUpdateDHCPOptOnPort(port_id=port_id)
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 _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 test_get_neutron_client_with_token(self, mock_client_init, mock_session): token = 'test-token-123' sess = mock.Mock() sess.get_endpoint.return_value = 'fake-url' mock_session.return_value = sess expected = {'timeout': 30, 'retries': 2, 'insecure': False, 'ca_cert': 'test-file', 'token': token, 'endpoint_url': 'fake-url'} mock_client_init.return_value = None neutron.get_client(token=token) mock_client_init.assert_called_once_with(**expected)
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 power_on_node_if_needed(task): """Powers on node if it is powered off and has a Smart NIC port :param task: A TaskManager object :returns: the previous power state or None if no changes were made """ if not task.driver.network.need_power_on(task): return previous_power_state = task.driver.power.get_power_state(task) if previous_power_state == states.POWER_OFF: node_set_boot_device(task, boot_devices.BIOS, persistent=False) node_power_action(task, states.POWER_ON) # local import is necessary to avoid circular import from ironic.common import neutron host_id = None for port in task.ports: if neutron.is_smartnic_port(port): link_info = port.local_link_connection host_id = link_info['hostname'] break if host_id: LOG.debug('Waiting for host %(host)s agent to be down', {'host': host_id}) client = neutron.get_client(context=task.context) neutron.wait_for_host_agent(client, host_id, target_state='down') return previous_power_state
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 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 get_ip_addresses(self, task): """Get IP addresses for all ports/portgroups in `task`. :param task: a TaskManager instance. :returns: List of IP addresses associated with task's ports/portgroups. """ client = neutron.get_client() port_ip_addresses = self._get_ip_addresses(task, task.ports, client) portgroup_ip_addresses = self._get_ip_addresses( task, task.portgroups, client) return port_ip_addresses + portgroup_ip_addresses
def get_ip_addresses(self, task): """Get IP addresses for all ports/portgroups in `task`. :param task: a TaskManager instance. :returns: List of IP addresses associated with task's ports/portgroups. """ client = neutron.get_client(task.context.auth_token) port_ip_addresses = self._get_ip_addresses(task, task.ports, client) portgroup_ip_addresses = self._get_ip_addresses( task, task.portgroups, client) return port_ip_addresses + portgroup_ip_addresses
def test_get_neutron_client_with_token(self, mock_ctxt, mock_client_init, mock_session, mock_adapter, mock_auth, mock_sauth): mock_ctxt.return_value = ctxt = mock.Mock() ctxt.auth_token = 'test-token-123' mock_adapter.return_value = adapter = mock.Mock() adapter.get_endpoint.return_value = 'neutron_url' neutron.get_client(token='test-token-123') mock_ctxt.assert_called_once_with(auth_token='test-token-123') mock_client_init.assert_called_once_with( mock.ANY, # this is 'self' session=mock.sentinel.session, auth=mock.sentinel.sauth, retries=2, endpoint_override='neutron_url', global_request_id=ctxt.global_id) # testing handling of default url_timeout mock_session.assert_called_once_with('neutron', timeout=10) mock_adapter.assert_called_once_with('neutron', session=mock.sentinel.session, auth=mock.sentinel.auth) mock_sauth.assert_called_once_with(mock_ctxt.return_value, 'neutron_url', mock.sentinel.auth)
def create_cleaning_ports(self, task): """Create neutron ports for each port on task.node to boot the ramdisk. :param task: a TaskManager instance. :raises: InvalidParameterValue if the cleaning network is None :returns: a dictionary in the form {port.uuid: neutron_port['id']} """ if not CONF.neutron.cleaning_network_uuid: raise exception.InvalidParameterValue(_('Valid cleaning network ' 'UUID not provided')) neutron_client = neutron.get_client(task.context.auth_token) body = { 'port': { 'network_id': CONF.neutron.cleaning_network_uuid, 'admin_state_up': True, } } ports = {} for ironic_port in task.ports: body['port']['mac_address'] = ironic_port.address try: port = neutron_client.create_port(body) except neutron_client_exc.ConnectionFailed as e: self._rollback_cleaning_ports(task) msg = (_('Could not create cleaning port on network %(net)s ' 'from %(node)s. %(exc)s') % {'net': CONF.neutron.cleaning_network_uuid, 'node': task.node.uuid, 'exc': e}) LOG.exception(msg) raise exception.NodeCleaningFailure(msg) if not port.get('port') or not port['port'].get('id'): self._rollback_cleaning_ports(task) msg = (_('Failed to create cleaning ports for node ' '%(node)s') % {'node': task.node.uuid}) LOG.error(msg) raise exception.NodeCleaningFailure(msg) # Match return value of get_node_vif_ids() ports[ironic_port.uuid] = port['port']['id'] return ports
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 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('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 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() # 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): try: physnets = neutron.get_physnets_by_port_uuid(client, vif_id) except (exception.InvalidParameterValue, exception.NetworkError): # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # 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. Assume no physical network information exists in these # cases. pass 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: # 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: # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # 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 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 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.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 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. body = { 'port': { 'binding:vnic_type': neutron.VNIC_BAREMETAL, 'binding:host_id': node.uuid, '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 body['port']['binding:profile'] = binding_profile if client_id_opt: body['port']['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']}) body['port']['binding:host_id'] = link_info['hostname'] body['port']['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, body['port']['binding:host_id']) try: client.update_port(vif_id, body) if is_smart_nic: neutron.wait_for_port_status(client, vif_id, 'ACTIVE') 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 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 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() # 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): try: physnets = neutron.get_physnets_by_port_uuid(client, vif_id) except (exception.InvalidParameterValue, exception.NetworkError): # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # 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. Assume no physical network information exists in these # cases. pass 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: # 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: # TODO(mgoddard): Remove this except clause and handle errors # properly. We can do this once a strategy has been determined # for handling the tempest VIF tests in an environment that # may not support neutron. # 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)