Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
    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)
Beispiel #4
0
    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)
Beispiel #5
0
    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)
Beispiel #6
0
    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)
Beispiel #7
0
 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')
Beispiel #8
0
 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)
Beispiel #9
0
    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)
Beispiel #10
0
    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)
Beispiel #11
0
    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)
Beispiel #12
0
    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)
Beispiel #13
0
    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)
Beispiel #14
0
    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)
Beispiel #15
0
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
Beispiel #16
0
    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)
Beispiel #17
0
    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)
Beispiel #18
0
 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)
Beispiel #19
0
 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)
Beispiel #20
0
    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)
Beispiel #21
0
    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)
Beispiel #22
0
    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)
Beispiel #23
0
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
Beispiel #24
0
    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)
Beispiel #25
0
    def vif_attach(self, task, vif_info):
        """Attach a virtual network interface to a node

        :param task: A TaskManager instance.
        :param vif_info: a dictionary of information about a VIF.
             It must have an 'id' key, whose value is a unique
             identifier for that VIF.
        :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts
        """
        vif_id = vif_info['id']
        # Sort ports by pxe_enabled to ensure we always bind pxe_enabled ports
        # first
        sorted_ports = sorted(task.ports, key=lambda p: p.pxe_enabled,
                              reverse=True)
        free_ports = []
        # Check all ports to ensure this VIF isn't already attached
        for port in sorted_ports:
            port_id = port.internal_info.get(TENANT_VIF_KEY,
                                             port.extra.get('vif_port_id'))
            if port_id is None:
                free_ports.append(port)
            elif port_id == vif_id:
                raise exception.VifAlreadyAttached(
                    vif=vif_id, port_uuid=port.uuid)

        if not free_ports:
            raise exception.NoFreePhysicalPorts(vif=vif_id)

        # Get first free port
        port = free_ports.pop(0)

        # Check if the requested vif_id is a neutron port. If it is
        # then attempt to update the port's MAC address.
        try:
            client = neutron.get_client(task.context.auth_token)
            client.show_port(vif_id)
        except neutron_exceptions.NeutronClientException:
            # NOTE(sambetts): If a client error occurs this is because either
            # neutron doesn't exist because we're running in standalone
            # environment or we can't find a matching neutron port which means
            # a user might be requesting a non-neutron port. So skip trying to
            # update the neutron port MAC address in these cases.
            pass
        else:
            try:
                neutron.update_port_address(vif_id, port.address)
            except exception.FailedToUpdateMacOnPort:
                raise exception.NetworkError(_(
                    "Unable to attach VIF %(vif)s because Ironic can not "
                    "update Neutron port %(port)s MAC address to match "
                    "physical MAC address %(mac)s") % {
                        'vif': vif_id, 'port': vif_id, 'mac': port.address})

        int_info = port.internal_info
        int_info[TENANT_VIF_KEY] = vif_id
        port.internal_info = int_info
        port.save()
Beispiel #26
0
    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)
Beispiel #27
0
    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
Beispiel #28
0
    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
Beispiel #29
0
    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)
Beispiel #30
0
    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
Beispiel #31
0
    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)
Beispiel #32
0
    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)
Beispiel #33
0
    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)
Beispiel #34
0
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)
Beispiel #35
0
    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)
Beispiel #36
0
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)
Beispiel #37
0
    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)
Beispiel #38
0
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)
Beispiel #39
0
    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)
Beispiel #40
0
    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)
Beispiel #41
0
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)
Beispiel #42
0
    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)
Beispiel #43
0
    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)