Ejemplo n.º 1
0
    def post(self, port):
        """Create a new port.

        :param port: a port within the request body.
        :raises: NotAcceptable, HTTPNotFound, Conflict
        """
        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        context = api.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:port:create', cdict, cdict)

        pdict = port.as_dict()
        self._check_allowed_port_fields(pdict)

        if (port.is_smartnic
                and not types.locallinkconnectiontype.validate_for_smart_nic(
                    port.local_link_connection)):
            raise exception.Invalid("Smart NIC port must have port_id "
                                    "and hostname in local_link_connection")

        create_remotely = api.request.rpcapi.can_send_create_port()
        if (not create_remotely and pdict.get('portgroup_uuid')):
            # NOTE(mgoddard): In RPC API v1.41, port creation was moved to the
            # conductor service to facilitate validation of the physical
            # network field of ports in portgroups. During a rolling upgrade,
            # the RPCAPI will reject the create_port method, so we need to
            # create the port locally. If the port is a member of a portgroup,
            # we are unable to perform the validation and must reject the
            # request.
            raise exception.NotAcceptable()

        vif = api_utils.handle_post_port_like_extra_vif(pdict)

        if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)):
            rpc_pg = objects.Portgroup.get_by_uuid(context,
                                                   pdict['portgroup_uuid'])
            if not rpc_pg.standalone_ports_supported:
                msg = _("Port group %s doesn't support standalone ports. "
                        "This port cannot be created as a member of that "
                        "port group because either 'extra/vif_port_id' "
                        "was specified or 'pxe_enabled' was set to True.")
                raise exception.Conflict(msg % pdict['portgroup_uuid'])

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not pdict.get('uuid'):
            pdict['uuid'] = uuidutils.generate_uuid()

        rpc_port = objects.Port(context, **pdict)
        rpc_node = objects.Node.get_by_id(context, rpc_port.node_id)

        notify_extra = {
            'node_uuid': port.node_uuid,
            'portgroup_uuid': port.portgroup_uuid
        }
        notify.emit_start_notification(context, rpc_port, 'create',
                                       **notify_extra)
        with notify.handle_error_notification(context, rpc_port, 'create',
                                              **notify_extra):
            # NOTE(mgoddard): In RPC API v1.41, port creation was moved to the
            # conductor service to facilitate validation of the physical
            # network field of ports in portgroups. During a rolling upgrade,
            # the RPCAPI will reject the create_port method, so we need to
            # create the port locally.
            if create_remotely:
                topic = api.request.rpcapi.get_topic_for(rpc_node)
                new_port = api.request.rpcapi.create_port(
                    context, rpc_port, topic)
            else:
                rpc_port.create()
                new_port = rpc_port
        notify.emit_end_notification(context, new_port, 'create',
                                     **notify_extra)
        # Set the HTTP Location Header
        api.response.location = link.build_url('ports', new_port.uuid)
        return Port.convert_with_links(new_port)
Ejemplo n.º 2
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 = self._get_vif_id_by_port_like_obj(port_obj)
        if 'address' in port_obj.obj_what_changed():
            if vif:
                neutron.update_port_address(vif,
                                            port_obj.address,
                                            context=task.context)

        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 (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': DHCP_CLIENT_ID,
                        'opt_value': updated_client_id
                    }

                    api.provider.update_port_dhcp_opts(vif, [client_id_opt],
                                                       context=task.context)
                # 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)
Ejemplo n.º 3
0
def validate_port_physnet(task, port_obj):
    """Validate the consistency of physical networks of ports in a portgroup.

    Validate the consistency of a port's physical network with other ports in
    the same portgroup.  All ports in a portgroup should have the same value
    (which may be None) for their physical_network field.

    During creation or update of a port in a portgroup we apply the
    following validation criteria:

    - If the portgroup has existing ports with different physical networks, we
      raise PortgroupPhysnetInconsistent. This shouldn't ever happen.
    - If the port has a physical network that is inconsistent with other
      ports in the portgroup, we raise exception.Conflict.

    If a port's physical network is None, this indicates that ironic's VIF
    attachment mapping algorithm should operate in a legacy (physical
    network unaware) mode for this port or portgroup. This allows existing
    ironic nodes to continue to function after an upgrade to a release
    including physical network support.

    :param task: a TaskManager instance
    :param port_obj: a port object to be validated.
    :raises: Conflict if the port is a member of a portgroup which is on a
             different physical network.
    :raises: PortgroupPhysnetInconsistent if the port's portgroup has
             ports which are not all assigned the same physical network.
    """
    if 'portgroup_id' not in port_obj or not port_obj.portgroup_id:
        return

    delta = port_obj.obj_what_changed()
    # We can skip this step if the port's portgroup membership or physical
    # network assignment is not being changed (during creation these will
    # appear changed).
    if not (delta & {'portgroup_id', 'physical_network'}):
        return

    # Determine the current physical network of the portgroup.
    pg_ports = network.get_ports_by_portgroup_id(task, port_obj.portgroup_id)
    port_obj_id = port_obj.id if 'id' in port_obj else None
    pg_physnets = {
        port.physical_network
        for port in pg_ports if port.id != port_obj_id
    }

    if not pg_physnets:
        return

    # Sanity check that all existing ports in the group have the same
    # physical network (should never happen).
    if len(pg_physnets) > 1:
        portgroup = network.get_portgroup_by_id(task, port_obj.portgroup_id)
        raise exception.PortgroupPhysnetInconsistent(
            portgroup=portgroup.uuid, physical_networks=", ".join(pg_physnets))

    # Check that the port has the same physical network as any existing
    # member ports.
    pg_physnet = pg_physnets.pop()
    port_physnet = (port_obj.physical_network
                    if 'physical_network' in port_obj else None)
    if port_physnet != pg_physnet:
        portgroup = network.get_portgroup_by_id(task, port_obj.portgroup_id)
        msg = _("Port with physical network %(physnet)s cannot become a "
                "member of port group %(portgroup)s which has ports in "
                "physical network %(pg_physnet)s.")
        raise exception.Conflict(
            msg % {
                'portgroup': portgroup.uuid,
                'physnet': port_physnet,
                'pg_physnet': pg_physnet
            })
Ejemplo n.º 4
0
    def post(self, port):
        """Create a new port.

        :param port: a port within the request body.
        :raises: NotAcceptable, HTTPNotFound, Conflict
        """
        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        context = api.request.context
        api_utils.check_policy('baremetal:port:create')

        # NOTE(lucasagomes): Create the node_id attribute on-the-fly
        #                    to satisfy the api -> rpc object
        #                    conversion.
        node = api_utils.replace_node_uuid_with_id(port)

        self._check_allowed_port_fields(port)

        portgroup = None
        if port.get('portgroup_uuid'):
            try:
                portgroup = objects.Portgroup.get(api.request.context,
                                                  port.pop('portgroup_uuid'))
                if portgroup.node_id != node.id:
                    raise exception.BadRequest(_('Port can not be added to a '
                                                 'portgroup belonging to a '
                                                 'different node.'))
                # NOTE(lucasagomes): Create the portgroup_id attribute
                #                    on-the-fly to satisfy the api ->
                #                    rpc object conversion.
                port['portgroup_id'] = portgroup.id
            except exception.PortgroupNotFound as e:
                # Change error code because 404 (NotFound) is inappropriate
                # response for a POST request to create a Port
                e.code = http_client.BAD_REQUEST  # BadRequest
                raise e

        if port.get('is_smartnic'):
            try:
                api_utils.LOCAL_LINK_SMART_NIC_VALIDATOR(
                    'local_link_connection',
                    port.get('local_link_connection'))
            except exception.Invalid:
                raise exception.Invalid(
                    "Smart NIC port must have port_id "
                    "and hostname in local_link_connection")

        physical_network = port.get('physical_network')
        if physical_network is not None and not physical_network:
            raise exception.Invalid('A non-empty value is required when '
                                    'setting physical_network')

        vif = api_utils.handle_post_port_like_extra_vif(port)

        if (portgroup and (port.get('pxe_enabled') or vif)):
            if not portgroup.standalone_ports_supported:
                msg = _("Port group %s doesn't support standalone ports. "
                        "This port cannot be created as a member of that "
                        "port group because either 'extra/vif_port_id' "
                        "was specified or 'pxe_enabled' was set to True.")
                raise exception.Conflict(
                    msg % portgroup.uuid)

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not port.get('uuid'):
            port['uuid'] = uuidutils.generate_uuid()

        rpc_port = objects.Port(context, **port)

        notify_extra = {
            'node_uuid': node.uuid,
            'portgroup_uuid': portgroup and portgroup.uuid or None
        }
        notify.emit_start_notification(context, rpc_port, 'create',
                                       **notify_extra)
        with notify.handle_error_notification(context, rpc_port, 'create',
                                              **notify_extra):
            topic = api.request.rpcapi.get_topic_for(node)
            new_port = api.request.rpcapi.create_port(context, rpc_port,
                                                      topic)
        notify.emit_end_notification(context, new_port, 'create',
                                     **notify_extra)
        # Set the HTTP Location Header
        api.response.location = link.build_url('ports', new_port.uuid)
        return convert_with_links(new_port)
Ejemplo n.º 5
0
    def post(self, port):
        """Create a new port.

        :param port: a port within the request body.
        :raises: NotAcceptable, HTTPNotFound, Conflict
        """
        context = pecan.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:port:create', cdict, cdict)

        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        pdict = port.as_dict()
        self._check_allowed_port_fields(pdict)

        extra = pdict.get('extra')
        vif = extra.get('vif_port_id') if extra else None
        if vif:
            common_utils.warn_about_deprecated_extra_vif_port_id()

        if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)):
            rpc_pg = objects.Portgroup.get_by_uuid(context,
                                                   pdict['portgroup_uuid'])
            if not rpc_pg.standalone_ports_supported:
                msg = _("Port group %s doesn't support standalone ports. "
                        "This port cannot be created as a member of that "
                        "port group because either 'extra/vif_port_id' "
                        "was specified or 'pxe_enabled' was set to True.")
                raise exception.Conflict(msg % pdict['portgroup_uuid'])

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not pdict.get('uuid'):
            pdict['uuid'] = uuidutils.generate_uuid()

        rpc_port = objects.Port(context, **pdict)
        rpc_node = objects.Node.get_by_id(context, rpc_port.node_id)

        notify_extra = {
            'node_uuid': port.node_uuid,
            'portgroup_uuid': port.portgroup_uuid
        }
        notify.emit_start_notification(context, rpc_port, 'create',
                                       **notify_extra)
        with notify.handle_error_notification(context, rpc_port, 'create',
                                              **notify_extra):
            # TODO(mgoddard): In RPC API v1.41, port creation was moved to the
            # conductor service to facilitate validation of the physical
            # network field of ports in portgroups. Further consideration is
            # required determine how best to support rolling upgrades from a
            # release in which ports are created by the API service to one in
            # which they are created by the conductor service, while ensuring
            # that all required validation is performed.
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
            new_port = pecan.request.rpcapi.create_port(
                context, rpc_port, topic)
        notify.emit_end_notification(context, new_port, 'create',
                                     **notify_extra)
        # Set the HTTP Location Header
        pecan.response.location = link.build_url('ports', new_port.uuid)
        return Port.convert_with_links(new_port)
Ejemplo n.º 6
0
    def post(self, port):
        """Create a new port.

        :param port: a port within the request body.
        :raises: NotAcceptable, HTTPNotFound, Conflict
        """
        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        # NOTE(lucasagomes): Create the node_id attribute on-the-fly
        #                    to satisfy the api -> rpc object
        #                    conversion.
        # NOTE(TheJulia): The get of the node *does* check if the node
        # can be accessed. We need to be able to get the node regardless
        # in order to perform the actual policy check.
        raise_node_not_found = False
        node = None
        owner = None
        lessee = None
        node_uuid = port.get('node_uuid')
        try:
            node = api_utils.replace_node_uuid_with_id(port)
            owner = node.owner
            lessee = node.lessee
        except exception.NotFound:
            raise_node_not_found = True

        # While the rule is for the port, the base object that controls access
        # is the node.
        api_utils.check_owner_policy('node',
                                     'baremetal:port:create',
                                     owner,
                                     lessee=lessee,
                                     conceal_node=False)
        if raise_node_not_found:
            # Delayed raise of NodeNotFound because we want to check
            # the access policy first.
            raise exception.NodeNotFound(node=node_uuid,
                                         code=http_client.BAD_REQUEST)

        context = api.request.context

        self._check_allowed_port_fields(port)

        portgroup = None
        if port.get('portgroup_uuid'):
            try:
                portgroup = objects.Portgroup.get(api.request.context,
                                                  port.pop('portgroup_uuid'))
                if portgroup.node_id != node.id:
                    raise exception.BadRequest(
                        _('Port can not be added to a '
                          'portgroup belonging to a '
                          'different node.'))
                # NOTE(lucasagomes): Create the portgroup_id attribute
                #                    on-the-fly to satisfy the api ->
                #                    rpc object conversion.
                port['portgroup_id'] = portgroup.id
            except exception.PortgroupNotFound as e:
                # Change error code because 404 (NotFound) is inappropriate
                # response for a POST request to create a Port
                e.code = http_client.BAD_REQUEST  # BadRequest
                raise e

        if port.get('is_smartnic'):
            try:
                api_utils.LOCAL_LINK_SMART_NIC_VALIDATOR(
                    'local_link_connection', port.get('local_link_connection'))
            except exception.Invalid:
                raise exception.Invalid(
                    "Smart NIC port must have port_id "
                    "and hostname in local_link_connection")

        physical_network = port.get('physical_network')
        if physical_network is not None and not physical_network:
            raise exception.Invalid('A non-empty value is required when '
                                    'setting physical_network')

        if (portgroup and (port.get('pxe_enabled'))):
            if not portgroup.standalone_ports_supported:
                msg = _("Port group %s doesn't support standalone ports. "
                        "This port cannot be created as a member of that "
                        "portgroup as the port's 'pxe_enabled' field was "
                        "set to True.")
                raise exception.Conflict(msg % portgroup.uuid)

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not port.get('uuid'):
            port['uuid'] = uuidutils.generate_uuid()

        rpc_port = objects.Port(context, **port)

        notify_extra = {
            'node_uuid': node.uuid,
            'portgroup_uuid': portgroup and portgroup.uuid or None
        }
        notify.emit_start_notification(context, rpc_port, 'create',
                                       **notify_extra)
        with notify.handle_error_notification(context, rpc_port, 'create',
                                              **notify_extra):
            topic = api.request.rpcapi.get_topic_for(node)
            new_port = api.request.rpcapi.create_port(context, rpc_port, topic)
        notify.emit_end_notification(context, new_port, 'create',
                                     **notify_extra)
        # Set the HTTP Location Header
        api.response.location = link.build_url('ports', new_port.uuid)
        return convert_with_links(new_port)
Ejemplo n.º 7
0
    def post(self, port):
        """Create a new port.

        :param port: a port within the request body.
        :raises: NotAcceptable, HTTPNotFound, Conflict
        """
        if self.parent_node_ident or self.parent_portgroup_ident:
            raise exception.OperationNotPermitted()

        context = api.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:port:create', cdict, cdict)

        pdict = port.as_dict()
        self._check_allowed_port_fields(pdict)

        if (port.is_smartnic
                and not types.locallinkconnectiontype.validate_for_smart_nic(
                    port.local_link_connection)):
            raise exception.Invalid("Smart NIC port must have port_id "
                                    "and hostname in local_link_connection")

        physical_network = pdict.get('physical_network')
        if physical_network is not None and not physical_network:
            raise exception.Invalid('A non-empty value is required when '
                                    'setting physical_network')

        vif = api_utils.handle_post_port_like_extra_vif(pdict)

        if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)):
            rpc_pg = objects.Portgroup.get_by_uuid(context,
                                                   pdict['portgroup_uuid'])
            if not rpc_pg.standalone_ports_supported:
                msg = _("Port group %s doesn't support standalone ports. "
                        "This port cannot be created as a member of that "
                        "port group because either 'extra/vif_port_id' "
                        "was specified or 'pxe_enabled' was set to True.")
                raise exception.Conflict(msg % pdict['portgroup_uuid'])

        # NOTE(yuriyz): UUID is mandatory for notifications payload
        if not pdict.get('uuid'):
            pdict['uuid'] = uuidutils.generate_uuid()

        rpc_port = objects.Port(context, **pdict)
        rpc_node = objects.Node.get_by_id(context, rpc_port.node_id)

        notify_extra = {
            'node_uuid': port.node_uuid,
            'portgroup_uuid': port.portgroup_uuid
        }
        notify.emit_start_notification(context, rpc_port, 'create',
                                       **notify_extra)
        with notify.handle_error_notification(context, rpc_port, 'create',
                                              **notify_extra):
            topic = api.request.rpcapi.get_topic_for(rpc_node)
            new_port = api.request.rpcapi.create_port(context, rpc_port, topic)
        notify.emit_end_notification(context, new_port, 'create',
                                     **notify_extra)
        # Set the HTTP Location Header
        api.response.location = link.build_url('ports', new_port.uuid)
        return Port.convert_with_links(new_port)