Exemple #1
0
    def test_update_port_address_with_binding(self, mock_unp, mock_client):
        address = 'fe:54:00:77:07:d9'
        port_id = 'fake-port-id'

        mock_client.return_value.show_port.return_value = {
            'port': {
                'binding:host_id': 'host',
                'binding:profile': 'foo'
            }
        }

        calls = [
            mock.call(port_id, {'port': {
                'mac_address': address
            }}),
            mock.call(port_id, {
                'port': {
                    'binding:host_id': 'host',
                    'binding:profile': 'foo'
                }
            })
        ]

        neutron.update_port_address(port_id, address)
        mock_unp.assert_called_once_with(port_id, client=mock_client())
        mock_client.return_value.update_port.assert_has_calls(calls)
Exemple #2
0
    def test_update_port_address(self, mock_client):
        address = 'fe:54:00:77:07:d9'
        port_id = 'fake-port-id'
        expected = {'port': {'mac_address': address}}
        mock_client.return_value.show_port.return_value = {}

        neutron.update_port_address(port_id, address)
        mock_client.return_value.update_port.assert_called_once_with(port_id,
                                                                     expected)
Exemple #3
0
    def test_update_port_address(self, mock_client):
        address = 'fe:54:00:77:07:d9'
        port_id = 'fake-port-id'
        expected = {'port': {'mac_address': address}}
        mock_client.return_value.show_port.return_value = {}

        neutron.update_port_address(port_id, address)
        mock_client.return_value.update_port.assert_called_once_with(
            port_id, expected)
Exemple #4
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()
Exemple #5
0
    def test_update_port_address_without_binding(self, mock_unp, mock_client):
        address = 'fe:54:00:77:07:d9'
        port_id = 'fake-port-id'
        expected = {'port': {'mac_address': address}}
        mock_client.return_value.show_port.return_value = {
            'port': {'binding:profile': 'foo'}}

        neutron.update_port_address(port_id, address, context=self.context)
        self.assertFalse(mock_unp.called)
        mock_client.return_value.update_port.assert_any_call(port_id, expected)
Exemple #6
0
    def test_update_port_address_without_binding(self, mock_unp, mock_client):
        address = 'fe:54:00:77:07:d9'
        port_id = 'fake-port-id'
        expected = {'port': {'mac_address': address}}
        mock_client.return_value.show_port.return_value = {
            'port': {'binding:profile': 'foo'}}

        neutron.update_port_address(port_id, address)
        self.assertFalse(mock_unp.called)
        mock_client.return_value.update_port.assert_any_call(port_id, expected)
Exemple #7
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)
Exemple #8
0
    def test_update_port_address_with_binding(self, mock_unp, mock_client):
        address = 'fe:54:00:77:07:d9'
        port_id = 'fake-port-id'
        expected = {'port': {'mac_address': address,
                             'binding:host_id': 'host',
                             'binding:profile': 'foo'}}
        mock_client.return_value.show_port.return_value = {
            'port': {'binding:host_id': 'host',
                     'binding:profile': 'foo'}}

        neutron.update_port_address(port_id, address)
        mock_unp.assert_called_once_with(port_id, client=mock_client())
        mock_client.return_value.update_port.assert_any_call(port_id, expected)
Exemple #9
0
    def portgroup_changed(self, task, portgroup_obj):
        """Handle any actions required when a portgroup changes

        :param task: a TaskManager instance.
        :param portgroup_obj: a changed Portgroup object from the API before
            it is saved to database.
        :raises: FailedToUpdateDHCPOptOnPort, Conflict
        """

        context = task.context
        portgroup_uuid = portgroup_obj.uuid
        # NOTE(vsaienko) address is not mandatory field in portgroup.
        # Do not touch neutron port if we removed address on portgroup.
        if ('address' in portgroup_obj.obj_what_changed()
                and portgroup_obj.address):
            pg_vif = (portgroup_obj.internal_info.get(TENANT_VIF_KEY)
                      or portgroup_obj.extra.get('vif_port_id'))
            if pg_vif:
                neutron.update_port_address(pg_vif, portgroup_obj.address)

        if 'extra' in portgroup_obj.obj_what_changed():
            original_portgroup = objects.Portgroup.get_by_id(
                context, portgroup_obj.id)
            if (portgroup_obj.extra.get('vif_port_id')
                    and portgroup_obj.extra['vif_port_id'] !=
                    original_portgroup.extra.get('vif_port_id')):
                utils.warn_about_deprecated_extra_vif_port_id()

        if ('standalone_ports_supported' in portgroup_obj.obj_what_changed()):
            if not portgroup_obj.standalone_ports_supported:
                ports = [
                    p for p in task.ports if p.portgroup_id == portgroup_obj.id
                ]
                for p in ports:
                    vif = p.internal_info.get(TENANT_VIF_KEY,
                                              p.extra.get('vif_port_id'))
                    reason = []
                    if p.pxe_enabled:
                        reason.append("'pxe_enabled' is set to True")
                    if vif:
                        reason.append('VIF %s is attached to this port' % vif)

                    if reason:
                        msg = (_("standalone_ports_supported can not be set "
                                 "to False, because the port group %(pg_id)s "
                                 "contains port with %(reason)s") % {
                                     'pg_id': portgroup_uuid,
                                     'reason': ', '.join(reason)
                                 })
                        raise exception.Conflict(msg)
Exemple #10
0
    def portgroup_changed(self, task, portgroup_obj):
        """Handle any actions required when a portgroup changes

        :param task: a TaskManager instance.
        :param portgroup_obj: a changed Portgroup object from the API before
            it is saved to database.
        :raises: FailedToUpdateDHCPOptOnPort, Conflict
        """

        context = task.context
        portgroup_uuid = portgroup_obj.uuid
        # NOTE(vsaienko) address is not mandatory field in portgroup.
        # Do not touch neutron port if we removed address on portgroup.
        if ('address' in portgroup_obj.obj_what_changed() and
                portgroup_obj.address):
            pg_vif = (portgroup_obj.internal_info.get(TENANT_VIF_KEY) or
                      portgroup_obj.extra.get('vif_port_id'))
            if pg_vif:
                neutron.update_port_address(pg_vif, portgroup_obj.address)

        if 'extra' in portgroup_obj.obj_what_changed():
            original_portgroup = objects.Portgroup.get_by_id(context,
                                                             portgroup_obj.id)
            if (portgroup_obj.extra.get('vif_port_id') and
                    portgroup_obj.extra['vif_port_id'] !=
                    original_portgroup.extra.get('vif_port_id')):
                utils.warn_about_deprecated_extra_vif_port_id()

        if ('standalone_ports_supported' in
                portgroup_obj.obj_what_changed()):
            if not portgroup_obj.standalone_ports_supported:
                ports = [p for p in task.ports if
                         p.portgroup_id == portgroup_obj.id]
                for p in ports:
                    vif = p.internal_info.get(
                        TENANT_VIF_KEY, p.extra.get('vif_port_id'))
                    reason = []
                    if p.pxe_enabled:
                        reason.append("'pxe_enabled' is set to True")
                    if vif:
                        reason.append('VIF %s is attached to this port' % vif)

                    if reason:
                        msg = (_("standalone_ports_supported can not be set "
                                 "to False, because the port group %(pg_id)s "
                                 "contains port with %(reason)s") % {
                               'pg_id': portgroup_uuid,
                               'reason': ', '.join(reason)})
                        raise exception.Conflict(msg)
Exemple #11
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
        """
        global update_port_address_deprecation
        if not update_port_address_deprecation:
            LOG.warning('update_port_address via DHCP provider is '
                        'deprecated. The node.network_interface '
                        'port_changed() should be used instead.')
            update_port_address_deprecation = True

        neutron.update_port_address(port_id, address, token)
Exemple #12
0
    def portgroup_changed(self, task, portgroup_obj):
        """Handle any actions required when a portgroup changes

        :param task: a TaskManager instance.
        :param portgroup_obj: a changed Portgroup object from the API before
            it is saved to database.
        :raises: FailedToUpdateDHCPOptOnPort, Conflict
        """

        portgroup_uuid = portgroup_obj.uuid
        # NOTE(vsaienko) address is not mandatory field in portgroup.
        # Do not touch neutron port if we removed address on portgroup.
        if ('address' in portgroup_obj.obj_what_changed()
                and portgroup_obj.address):
            pg_vif = self._get_vif_id_by_port_like_obj(portgroup_obj)
            if pg_vif:
                neutron.update_port_address(pg_vif,
                                            portgroup_obj.address,
                                            context=task.context)

        if ('standalone_ports_supported' in portgroup_obj.obj_what_changed()):
            if not portgroup_obj.standalone_ports_supported:
                ports = [
                    p for p in task.ports if p.portgroup_id == portgroup_obj.id
                ]
                for p in ports:
                    vif = self._get_vif_id_by_port_like_obj(p)
                    reason = []
                    if p.pxe_enabled:
                        reason.append("'pxe_enabled' is set to True")
                    if vif:
                        reason.append('VIF %s is attached to this port' % vif)

                    if reason:
                        msg = (_("standalone_ports_supported can not be set "
                                 "to False, because the port group %(pg_id)s "
                                 "contains port with %(reason)s") % {
                                     'pg_id': portgroup_uuid,
                                     'reason': ', '.join(reason)
                                 })
                        raise exception.Conflict(msg)
Exemple #13
0
    def portgroup_changed(self, task, portgroup_obj):
        """Handle any actions required when a portgroup changes

        :param task: a TaskManager instance.
        :param portgroup_obj: a changed Portgroup object from the API before
            it is saved to database.
        :raises: FailedToUpdateDHCPOptOnPort, Conflict
        """

        context = task.context
        portgroup_uuid = portgroup_obj.uuid
        if 'address' in portgroup_obj.obj_what_changed():
            pg_vif = portgroup_obj.extra.get('vif_port_id')
            if pg_vif:
                neutron.update_port_address(pg_vif,
                                            portgroup_obj.address,
                                            token=context.auth_token)

        if ('standalone_ports_supported' in
                portgroup_obj.obj_what_changed()):
            if not portgroup_obj.standalone_ports_supported:
                ports = [p for p in task.ports if
                         p.portgroup_id == portgroup_obj.id]
                for p in ports:
                    vif = p.internal_info.get(
                        TENANT_VIF_KEY, p.extra.get('vif_port_id'))
                    reason = []
                    if p.pxe_enabled:
                        reason.append("'pxe_enabled' is set to True")
                    if vif:
                        reason.append('VIF %s is attached to this port' % vif)

                    if reason:
                        msg = (_("standalone_ports_supported can not be set "
                                 "to False, because the port group %(pg_id)s "
                                 "contains port with %(reason)s") % {
                               'pg_id': portgroup_uuid,
                               'reason': ', '.join(reason)})
                        raise exception.Conflict(msg)
Exemple #14
0
    def portgroup_changed(self, task, portgroup_obj):
        """Handle any actions required when a portgroup changes

        :param task: a TaskManager instance.
        :param portgroup_obj: a changed Portgroup object from the API before
            it is saved to database.
        :raises: FailedToUpdateDHCPOptOnPort, Conflict
        """

        portgroup_uuid = portgroup_obj.uuid
        # NOTE(vsaienko) address is not mandatory field in portgroup.
        # Do not touch neutron port if we removed address on portgroup.
        if ('address' in portgroup_obj.obj_what_changed()
                and portgroup_obj.address):
            pg_vif = self._get_vif_id_by_port_like_obj(portgroup_obj)
            if pg_vif:
                neutron.update_port_address(pg_vif, portgroup_obj.address,
                                            context=task.context)

        if ('standalone_ports_supported' in
                portgroup_obj.obj_what_changed()):
            if not portgroup_obj.standalone_ports_supported:
                ports = [p for p in task.ports if
                         p.portgroup_id == portgroup_obj.id]
                for p in ports:
                    vif = self._get_vif_id_by_port_like_obj(p)
                    reason = []
                    if p.pxe_enabled:
                        reason.append("'pxe_enabled' is set to True")
                    if vif:
                        reason.append('VIF %s is attached to this port' % vif)

                    if reason:
                        msg = (_("standalone_ports_supported can not be set "
                                 "to False, because the port group %(pg_id)s "
                                 "contains port with %(reason)s") % {
                               'pg_id': portgroup_uuid,
                               'reason': ', '.join(reason)})
                        raise exception.Conflict(msg)
Exemple #15
0
    def port_changed(self, task, port_obj):
        """Handle any actions required when a port changes

        :param task: a TaskManager instance.
        :param port_obj: a changed Port object from the API before it is saved
            to database.
        :raises: FailedToUpdateDHCPOptOnPort, Conflict
        """
        context = task.context
        node = task.node
        port_uuid = port_obj.uuid
        portgroup_obj = None
        if port_obj.portgroup_id:
            portgroup_obj = [
                pg for pg in task.portgroups if pg.id == port_obj.portgroup_id
            ][0]
        vif = (port_obj.internal_info.get(TENANT_VIF_KEY)
               or port_obj.extra.get('vif_port_id'))
        if 'address' in port_obj.obj_what_changed():
            if vif:
                neutron.update_port_address(vif, port_obj.address)

        if 'extra' in port_obj.obj_what_changed():
            original_port = objects.Port.get_by_id(context, port_obj.id)
            updated_client_id = port_obj.extra.get('client-id')
            if (port_obj.extra.get('vif_port_id')
                    and (port_obj.extra['vif_port_id'] !=
                         original_port.extra.get('vif_port_id'))):
                utils.warn_about_deprecated_extra_vif_port_id()
            if (original_port.extra.get('client-id') != updated_client_id):
                # DHCP Option with opt_value=None will remove it
                # from the neutron port
                if vif:
                    api = dhcp_factory.DHCPFactory()
                    client_id_opt = {
                        'opt_name': 'client-id',
                        'opt_value': updated_client_id
                    }

                    api.provider.update_port_dhcp_opts(vif, [client_id_opt])
                # Log warning if there is no VIF and an instance
                # is associated with the node.
                elif node.instance_uuid:
                    LOG.warning(
                        _LW("No VIF found for instance %(instance)s "
                            "port %(port)s when attempting to update port "
                            "client-id."), {
                                'port': port_uuid,
                                'instance': node.instance_uuid
                            })

        if portgroup_obj and ((set(port_obj.obj_what_changed())
                               & {'pxe_enabled', 'portgroup_id'}) or vif):
            if not portgroup_obj.standalone_ports_supported:
                reason = []
                if port_obj.pxe_enabled:
                    reason.append("'pxe_enabled' was set to True")
                if vif:
                    reason.append('VIF %s is attached to the port' % vif)

                if reason:
                    msg = (_("Port group %(portgroup)s doesn't support "
                             "standalone ports. This port %(port)s cannot be "
                             " a member of that port group because of: "
                             "%(reason)s") % {
                                 "reason": ', '.join(reason),
                                 "portgroup": portgroup_obj.uuid,
                                 "port": port_uuid
                             })
                    raise exception.Conflict(msg)
Exemple #16
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)
Exemple #17
0
    def port_changed(self, task, port_obj):
        """Handle any actions required when a port changes

        :param task: a TaskManager instance.
        :param port_obj: a changed Port object from the API before it is saved
            to database.
        :raises: FailedToUpdateDHCPOptOnPort, Conflict
        """
        context = task.context
        node = task.node
        port_uuid = port_obj.uuid
        portgroup_obj = None
        if port_obj.portgroup_id:
            portgroup_obj = [pg for pg in task.portgroups if
                             pg.id == port_obj.portgroup_id][0]
        vif = (port_obj.internal_info.get(TENANT_VIF_KEY) or
               port_obj.extra.get('vif_port_id'))
        if 'address' in port_obj.obj_what_changed():
            if vif:
                neutron.update_port_address(vif, port_obj.address)

        if 'extra' in port_obj.obj_what_changed():
            original_port = objects.Port.get_by_id(context, port_obj.id)
            updated_client_id = port_obj.extra.get('client-id')
            if (port_obj.extra.get('vif_port_id') and
                    (port_obj.extra['vif_port_id'] !=
                     original_port.extra.get('vif_port_id'))):
                utils.warn_about_deprecated_extra_vif_port_id()
            if (original_port.extra.get('client-id') !=
                updated_client_id):
                # DHCP Option with opt_value=None will remove it
                # from the neutron port
                if vif:
                    api = dhcp_factory.DHCPFactory()
                    client_id_opt = {'opt_name': 'client-id',
                                     'opt_value': updated_client_id}

                    api.provider.update_port_dhcp_opts(
                        vif, [client_id_opt])
                # Log warning if there is no VIF and an instance
                # is associated with the node.
                elif node.instance_uuid:
                    LOG.warning(
                        "No VIF found for instance %(instance)s "
                        "port %(port)s when attempting to update port "
                        "client-id.",
                        {'port': port_uuid,
                         'instance': node.instance_uuid})

        if portgroup_obj and ((set(port_obj.obj_what_changed()) &
                              {'pxe_enabled', 'portgroup_id'}) or vif):
            if not portgroup_obj.standalone_ports_supported:
                reason = []
                if port_obj.pxe_enabled:
                    reason.append("'pxe_enabled' was set to True")
                if vif:
                    reason.append('VIF %s is attached to the port' % vif)

                if reason:
                    msg = (_("Port group %(portgroup)s doesn't support "
                             "standalone ports. This port %(port)s cannot be "
                             " a member of that port group because of: "
                             "%(reason)s") % {"reason": ', '.join(reason),
                                              "portgroup": portgroup_obj.uuid,
                                              "port": port_uuid})
                    raise exception.Conflict(msg)
Exemple #18
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)
Exemple #19
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)
Exemple #20
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)