Example #1
0
 def _set_portgroup_uuid(self, value):
     if value and self._portgroup_uuid != value:
         if not api_utils.allow_portgroups_subcontrollers():
             self._portgroup_uuid = wtypes.Unset
             return
         try:
             portgroup = objects.Portgroup.get(pecan.request.context, value)
             if portgroup.node_id != self.node_id:
                 raise exception.BadRequest(_('Port can not be added to a '
                                              'portgroup belonging to a '
                                              'different node.'))
             self._portgroup_uuid = portgroup.uuid
             # NOTE(lucasagomes): Create the portgroup_id attribute
             #                    on-the-fly to satisfy the api ->
             #                    rpc object conversion.
             self.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
     elif value == wtypes.Unset:
         self._portgroup_uuid = wtypes.Unset
     elif value is None and api_utils.allow_portgroups_subcontrollers():
         # This is to output portgroup_uuid field if API version allows this
         self._portgroup_uuid = None
Example #2
0
 def _set_portgroup_uuid(self, value):
     if value and self._portgroup_uuid != value:
         if not api_utils.allow_portgroups_subcontrollers():
             self._portgroup_uuid = wtypes.Unset
             return
         try:
             portgroup = objects.Portgroup.get(pecan.request.context, value)
             if portgroup.node_id != self.node_id:
                 raise exception.BadRequest(
                     _('Port can not be added to a '
                       'portgroup belonging to a '
                       'different node.'))
             self._portgroup_uuid = portgroup.uuid
             # NOTE(lucasagomes): Create the portgroup_id attribute
             #                    on-the-fly to satisfy the api ->
             #                    rpc object conversion.
             self.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
     elif value == wtypes.Unset:
         self._portgroup_uuid = wtypes.Unset
     elif value is None and api_utils.allow_portgroups_subcontrollers():
         # This is to output portgroup_uuid field if API version allows this
         self._portgroup_uuid = None
Example #3
0
    def get_all(self, node=None, node_uuid=None, address=None, marker=None,
                limit=None, sort_key='id', sort_dir='asc', fields=None,
                portgroup=None):
        """Retrieve a list of ports.

        Note that the 'node_uuid' interface is deprecated in favour
        of the 'node' interface

        :param node: UUID or name of a node, to get only ports for that
                           node.
        :param node_uuid: UUID of a node, to get only ports for that
                           node.
        :param address: MAC address of a port, to get the port which has
                        this MAC address.
        :param marker: pagination marker for large data sets.
        :param limit: maximum number of resources to return in a single result.
                      This value cannot be larger than the value of max_limit
                      in the [api] section of the ironic configuration, or only
                      max_limit resources will be returned.
        :param sort_key: column to sort results by. Default: id.
        :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
        :param fields: Optional, a list with a specified set of fields
            of the resource to be returned.
        :param portgroup: UUID or name of a portgroup, to get only ports
                                   for that portgroup.
        :raises: NotAcceptable, HTTPNotFound
        """
        cdict = pecan.request.context.to_dict()
        policy.authorize('baremetal:port:get', cdict, cdict)

        api_utils.check_allow_specify_fields(fields)
        if fields:
            if (not api_utils.allow_port_advanced_net_fields() and
                    set(fields).intersection(self.advanced_net_fields)):
                raise exception.NotAcceptable()
            if ('portgroup_uuid' in fields and not
                    api_utils.allow_portgroups_subcontrollers()):
                    raise exception.NotAcceptable()

        if portgroup and not api_utils.allow_portgroups_subcontrollers():
            raise exception.NotAcceptable()

        if fields is None:
            fields = _DEFAULT_RETURN_FIELDS

        if not node_uuid and node:
            # We're invoking this interface using positional notation, or
            # explicitly using 'node'.  Try and determine which one.
            # Make sure only one interface, node or node_uuid is used
            if (not api_utils.allow_node_logical_names() and
                not uuidutils.is_uuid_like(node)):
                raise exception.NotAcceptable()

        return self._get_ports_collection(node_uuid or node, address,
                                          portgroup, marker, limit, sort_key,
                                          sort_dir, fields=fields)
Example #4
0
    def post(self, port):
        """Create a new port.

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

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

        pdict = port.as_dict()
        if (not api_utils.allow_port_advanced_net_fields()
                and set(pdict).intersection(self.advanced_net_fields)):
            raise exception.NotAcceptable()
        if (not api_utils.allow_portgroups_subcontrollers()
                and 'portgroup_uuid' in pdict):
            raise exception.NotAcceptable()

        new_port = objects.Port(pecan.request.context, **pdict)

        new_port.create()
        # Set the HTTP Location Header
        pecan.response.location = link.build_url('ports', new_port.uuid)
        return Port.convert_with_links(new_port)
Example #5
0
    def detail(self,
               node=None,
               node_uuid=None,
               address=None,
               marker=None,
               limit=None,
               sort_key='id',
               sort_dir='asc',
               portgroup=None):
        """Retrieve a list of ports with detail.

        Note that the 'node_uuid' interface is deprecated in favour
        of the 'node' interface

        :param node: UUID or name of a node, to get only ports for that
                     node.
        :param node_uuid: UUID of a node, to get only ports for that
                          node.
        :param address: MAC address of a port, to get the port which has
                        this MAC address.
        :param portgroup: UUID or name of a portgroup, to get only ports
                           for that portgroup.
        :param marker: pagination marker for large data sets.
        :param limit: maximum number of resources to return in a single result.
                      This value cannot be larger than the value of max_limit
                      in the [api] section of the ironic configuration, or only
                      max_limit resources will be returned.
        :param sort_key: column to sort results by. Default: id.
        :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
        :raises: NotAcceptable, HTTPNotFound
        """
        owner = api_utils.check_port_list_policy()

        self._check_allowed_port_fields([sort_key])
        if portgroup and not api_utils.allow_portgroups_subcontrollers():
            raise exception.NotAcceptable()

        if not node_uuid and node:
            # We're invoking this interface using positional notation, or
            # explicitly using 'node'.  Try and determine which one.
            # Make sure only one interface, node or node_uuid is used
            if (not api_utils.allow_node_logical_names()
                    and not uuidutils.is_uuid_like(node)):
                raise exception.NotAcceptable()

        # NOTE(lucasagomes): /detail should only work against collections
        parent = api.request.path.split('/')[:-1][-1]
        if parent != "ports":
            raise exception.HTTPNotFound()

        resource_url = '/'.join(['ports', 'detail'])
        return self._get_ports_collection(node_uuid or node,
                                          address,
                                          portgroup,
                                          marker,
                                          limit,
                                          sort_key,
                                          sort_dir,
                                          resource_url,
                                          owner=owner)
Example #6
0
    def _check_allowed_port_fields(self, fields):
        """Check if fetching a particular field of a port is allowed.

        Check if the required version is being requested for fields
        that are only allowed to be fetched in a particular API version.

        :param fields: list or set of fields to check
        :raises: NotAcceptable if a field is not allowed
        """
        if fields is None:
            return
        if (not api_utils.allow_port_advanced_net_fields()
                and set(fields).intersection(self.advanced_net_fields)):
            raise exception.NotAcceptable()
        if ('portgroup_uuid' in fields
                and not api_utils.allow_portgroups_subcontrollers()):
            raise exception.NotAcceptable()
        if ('physical_network' in fields
                and not api_utils.allow_port_physical_network()):
            raise exception.NotAcceptable()
        if ('is_smartnic' in fields
                and not api_utils.allow_port_is_smartnic()):
            raise exception.NotAcceptable()
        if ('local_link_connection/network_type' in fields
                and not api_utils.allow_local_link_connection_network_type()):
            raise exception.NotAcceptable()
        if (isinstance(fields, dict)
                and fields.get('local_link_connection') is not None):
            if (not api_utils.allow_local_link_connection_network_type()
                    and 'network_type' in fields['local_link_connection']):
                raise exception.NotAcceptable()
Example #7
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()
        if (not api_utils.allow_port_advanced_net_fields()
                and set(pdict).intersection(self.advanced_net_fields)):
            raise exception.NotAcceptable()
        if (not api_utils.allow_portgroups_subcontrollers()
                and 'portgroup_uuid' in pdict):
            raise exception.NotAcceptable()

        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()

        new_port = objects.Port(context, **pdict)

        notify.emit_start_notification(context,
                                       new_port,
                                       'create',
                                       node_uuid=port.node_uuid)
        with notify.handle_error_notification(context,
                                              new_port,
                                              'create',
                                              node_uuid=port.node_uuid):
            new_port.create()
        notify.emit_end_notification(context,
                                     new_port,
                                     'create',
                                     node_uuid=port.node_uuid)
        # Set the HTTP Location Header
        pecan.response.location = link.build_url('ports', new_port.uuid)
        return Port.convert_with_links(new_port)
Example #8
0
def hide_fields_in_newer_versions(obj):
    # if requested version is < 1.18, hide internal_info field
    if not api_utils.allow_port_internal_info():
        obj.internal_info = wsme.Unset
    # if requested version is < 1.19, hide local_link_connection and
    # pxe_enabled fields
    if not api_utils.allow_port_advanced_net_fields():
        obj.pxe_enabled = wsme.Unset
        obj.local_link_connection = wsme.Unset
    # if requested version is < 1.24, hide portgroup_uuid field
    if not api_utils.allow_portgroups_subcontrollers():
        obj.portgroup_uuid = wsme.Unset
Example #9
0
def hide_fields_in_newer_versions(obj):
    # if requested version is < 1.18, hide internal_info field
    if not api_utils.allow_port_internal_info():
        obj.internal_info = wsme.Unset
    # if requested version is < 1.19, hide local_link_connection and
    # pxe_enabled fields
    if not api_utils.allow_port_advanced_net_fields():
        obj.pxe_enabled = wsme.Unset
        obj.local_link_connection = wsme.Unset
    # if requested version is < 1.24, hide portgroup_uuid field
    if not api_utils.allow_portgroups_subcontrollers():
        obj.portgroup_uuid = wsme.Unset
Example #10
0
 def _lookup(self, ident, subres, *remainder):
     if not api_utils.allow_portgroups():
         pecan.abort(http_client.NOT_FOUND)
     try:
         ident = types.uuid_or_name.validate(ident)
     except exception.InvalidUuidOrName as e:
         pecan.abort(http_client.BAD_REQUEST, e.args[0])
     subcontroller = self._subcontroller_map.get(subres)
     if subcontroller:
         if api_utils.allow_portgroups_subcontrollers():
             return subcontroller(
                 portgroup_ident=ident,
                 node_ident=self.parent_node_ident), remainder
         pecan.abort(http_client.NOT_FOUND)
Example #11
0
 def _lookup(self, ident, subres, *remainder):
     if not api_utils.allow_portgroups():
         pecan.abort(http_client.NOT_FOUND)
     try:
         ident = types.uuid_or_name.validate(ident)
     except exception.InvalidUuidOrName as e:
         pecan.abort(http_client.BAD_REQUEST, e.args[0])
     subcontroller = self._subcontroller_map.get(subres)
     if subcontroller:
         if api_utils.allow_portgroups_subcontrollers():
             return subcontroller(
                 portgroup_ident=ident,
                 node_ident=self.parent_node_ident), remainder
         pecan.abort(http_client.NOT_FOUND)
Example #12
0
    def detail(self, node=None, node_uuid=None, address=None, marker=None,
               limit=None, sort_key='id', sort_dir='asc', portgroup=None):
        """Retrieve a list of ports with detail.

        Note that the 'node_uuid' interface is deprecated in favour
        of the 'node' interface

        :param node: UUID or name of a node, to get only ports for that
                     node.
        :param node_uuid: UUID of a node, to get only ports for that
                          node.
        :param address: MAC address of a port, to get the port which has
                        this MAC address.
        :param portgroup: UUID or name of a portgroup, to get only ports
                           for that portgroup.
        :param marker: pagination marker for large data sets.
        :param limit: maximum number of resources to return in a single result.
                      This value cannot be larger than the value of max_limit
                      in the [api] section of the ironic configuration, or only
                      max_limit resources will be returned.
        :param sort_key: column to sort results by. Default: id.
        :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
        :raises: NotAcceptable, HTTPNotFound
        """
        cdict = pecan.request.context.to_policy_values()
        policy.authorize('baremetal:port:get', cdict, cdict)

        self._check_allowed_port_fields([sort_key])
        if portgroup and not api_utils.allow_portgroups_subcontrollers():
            raise exception.NotAcceptable()

        if not node_uuid and node:
            # We're invoking this interface using positional notation, or
            # explicitly using 'node'.  Try and determine which one.
            # Make sure only one interface, node or node_uuid is used
            if (not api_utils.allow_node_logical_names() and
                not uuidutils.is_uuid_like(node)):
                raise exception.NotAcceptable()

        # NOTE(lucasagomes): /detail should only work against collections
        parent = pecan.request.path.split('/')[:-1][-1]
        if parent != "ports":
            raise exception.HTTPNotFound()

        resource_url = '/'.join(['ports', 'detail'])
        return self._get_ports_collection(node_uuid or node, address,
                                          portgroup, marker, limit, sort_key,
                                          sort_dir, resource_url)
Example #13
0
 def _lookup(self, ident, *remainder):
     if not api_utils.allow_portgroups():
         pecan.abort(http_client.NOT_FOUND)
     try:
         ident = args.uuid_or_name('portgroup', ident)
     except exception.InvalidParameterValue as e:
         pecan.abort(http_client.BAD_REQUEST, e.args[0])
     if not remainder:
         return
     subcontroller = self._subcontroller_map.get(remainder[0])
     if subcontroller:
         if api_utils.allow_portgroups_subcontrollers():
             return subcontroller(
                 portgroup_ident=ident,
                 node_ident=self.parent_node_ident), remainder[1:]
         pecan.abort(http_client.NOT_FOUND)
Example #14
0
    def _check_allowed_port_fields(self, fields):
        """Check if fetching a particular field of a port is allowed.

        Check if the required version is being requested for fields
        that are only allowed to be fetched in a particular API version.

        :param fields: list or set of fields to check
        :raises: NotAcceptable if a field is not allowed
        """
        if fields is None:
            return
        if (not api_utils.allow_port_advanced_net_fields() and
                set(fields).intersection(self.advanced_net_fields)):
            raise exception.NotAcceptable()
        if ('portgroup_uuid' in fields and not
                api_utils.allow_portgroups_subcontrollers()):
            raise exception.NotAcceptable()
Example #15
0
    def _check_allowed_port_fields(self, fields):
        """Check if fetching a particular field of a port is allowed.

        Check if the required version is being requested for fields
        that are only allowed to be fetched in a particular API version.

        :param fields: list or set of fields to check
        :raises: NotAcceptable if a field is not allowed
        """
        if fields is None:
            return
        if (not api_utils.allow_port_advanced_net_fields()
                and set(fields).intersection(self.advanced_net_fields)):
            raise exception.NotAcceptable()
        if ('portgroup_uuid' in fields
                and not api_utils.allow_portgroups_subcontrollers()):
            raise exception.NotAcceptable()
Example #16
0
def hide_fields_in_newer_versions(port):
    # if requested version is < 1.18, hide internal_info field
    if not api_utils.allow_port_internal_info():
        port.pop('internal_info', None)
    # if requested version is < 1.19, hide local_link_connection and
    # pxe_enabled fields
    if not api_utils.allow_port_advanced_net_fields():
        port.pop('pxe_enabled', None)
        port.pop('local_link_connection', None)
    # if requested version is < 1.24, hide portgroup_uuid field
    if not api_utils.allow_portgroups_subcontrollers():
        port.pop('portgroup_uuid', None)
    # if requested version is < 1.34, hide physical_network field.
    if not api_utils.allow_port_physical_network():
        port.pop('physical_network', None)
    # if requested version is < 1.53, hide is_smartnic field.
    if not api_utils.allow_port_is_smartnic():
        port.pop('is_smartnic', None)
Example #17
0
def hide_fields_in_newer_versions(obj):
    # if requested version is < 1.18, hide internal_info field
    if not api_utils.allow_port_internal_info():
        obj.internal_info = atypes.Unset
    # if requested version is < 1.19, hide local_link_connection and
    # pxe_enabled fields
    if not api_utils.allow_port_advanced_net_fields():
        obj.pxe_enabled = atypes.Unset
        obj.local_link_connection = atypes.Unset
    # if requested version is < 1.24, hide portgroup_uuid field
    if not api_utils.allow_portgroups_subcontrollers():
        obj.portgroup_uuid = atypes.Unset
    # if requested version is < 1.34, hide physical_network field.
    if not api_utils.allow_port_physical_network():
        obj.physical_network = atypes.Unset
    # if requested version is < 1.53, hide is_smartnic field.
    if not api_utils.allow_port_is_smartnic():
        obj.is_smartnic = atypes.Unset
Example #18
0
    def post(self, port):
        """Create a new port.

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

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

        pdict = port.as_dict()
        if (not api_utils.allow_port_advanced_net_fields() and
                set(pdict).intersection(self.advanced_net_fields)):
            raise exception.NotAcceptable()
        if (not api_utils.allow_portgroups_subcontrollers() and
            'portgroup_uuid' in pdict):
            raise exception.NotAcceptable()

        extra = pdict.get('extra')
        vif = extra.get('vif_port_id') if extra else None
        if (pdict.get('portgroup_uuid') and
                (pdict.get('pxe_enabled') or vif)):
            rpc_pg = objects.Portgroup.get_by_uuid(pecan.request.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'])

        new_port = objects.Port(pecan.request.context,
                                **pdict)

        new_port.create()
        # Set the HTTP Location Header
        pecan.response.location = link.build_url('ports', new_port.uuid)
        return Port.convert_with_links(new_port)
Example #19
0
 def test_allow_portgroups_subcontrollers(self, mock_request):
     mock_request.version.minor = 24
     self.assertTrue(utils.allow_portgroups_subcontrollers())
     mock_request.version.minor = 23
     self.assertFalse(utils.allow_portgroups_subcontrollers())
Example #20
0
 def test_allow_portgroups_subcontrollers(self, mock_request):
     mock_request.version.minor = 24
     self.assertTrue(utils.allow_portgroups_subcontrollers())
     mock_request.version.minor = 23
     self.assertFalse(utils.allow_portgroups_subcontrollers())
Example #21
0
    def patch(self, port_uuid, patch):
        """Update an existing port.

        :param port_uuid: UUID of a port.
        :param patch: a json PATCH document to apply to this port.
        :raises: NotAcceptable, HTTPNotFound
        """
        context = pecan.request.context
        cdict = context.to_policy_values()
        policy.authorize('baremetal:port:update', cdict, cdict)

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

        fields_to_check = set()
        for field in self.advanced_net_fields + ['portgroup_uuid']:
            field_path = '/%s' % field
            if (api_utils.get_patch_values(patch, field_path)
                    or api_utils.is_path_removed(patch, field_path)):
                fields_to_check.add(field)
        if (fields_to_check.intersection(self.advanced_net_fields)
                and not api_utils.allow_port_advanced_net_fields()):
            raise exception.NotAcceptable()
        if ('portgroup_uuid' in fields_to_check
                and not api_utils.allow_portgroups_subcontrollers()):
            raise exception.NotAcceptable()

        rpc_port = objects.Port.get_by_uuid(context, port_uuid)
        try:
            port_dict = rpc_port.as_dict()
            # NOTE(lucasagomes):
            # 1) Remove node_id because it's an internal value and
            #    not present in the API object
            # 2) Add node_uuid
            port_dict['node_uuid'] = port_dict.pop('node_id', None)
            # NOTE(vsaienko):
            # 1) Remove portgroup_id because it's an internal value and
            #    not present in the API object
            # 2) Add portgroup_uuid
            port_dict['portgroup_uuid'] = port_dict.pop('portgroup_id', None)
            port = Port(**api_utils.apply_jsonpatch(port_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        if api_utils.is_path_removed(patch, '/portgroup_uuid'):
            rpc_port.portgroup_id = None

        # Update only the fields that have changed
        for field in objects.Port.fields:
            try:
                patch_val = getattr(port, field)
            except AttributeError:
                # Ignore fields that aren't exposed in the API
                continue
            if patch_val == wtypes.Unset:
                patch_val = None
            if rpc_port[field] != patch_val:
                rpc_port[field] = patch_val

        rpc_node = objects.Node.get_by_id(context, rpc_port.node_id)
        notify.emit_start_notification(context,
                                       rpc_port,
                                       'update',
                                       node_uuid=rpc_node.uuid)
        with notify.handle_error_notification(context,
                                              rpc_port,
                                              'update',
                                              node_uuid=rpc_node.uuid):
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
            new_port = pecan.request.rpcapi.update_port(
                context, rpc_port, topic)

        api_port = Port.convert_with_links(new_port)
        notify.emit_end_notification(context,
                                     new_port,
                                     'update',
                                     node_uuid=api_port.node_uuid)

        return api_port
Example #22
0
    def patch(self, port_uuid, patch):
        """Update an existing port.

        :param port_uuid: UUID of a port.
        :param patch: a json PATCH document to apply to this port.
        :raises: NotAcceptable, HTTPNotFound
        """
        cdict = pecan.request.context.to_dict()
        policy.authorize('baremetal:port:update', cdict, cdict)

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

        fields_to_check = set()
        for field in self.advanced_net_fields + ['portgroup_uuid']:
            field_path = '/%s' % field
            if (api_utils.get_patch_values(patch, field_path) or
                    api_utils.is_path_removed(patch, field_path)):
                fields_to_check.add(field)
        if (fields_to_check.intersection(self.advanced_net_fields) and
                not api_utils.allow_port_advanced_net_fields()):
            raise exception.NotAcceptable()
        if ('portgroup_uuid' in fields_to_check and
                not api_utils.allow_portgroups_subcontrollers()):
            raise exception.NotAcceptable()

        rpc_port = objects.Port.get_by_uuid(pecan.request.context, port_uuid)
        try:
            port_dict = rpc_port.as_dict()
            # NOTE(lucasagomes):
            # 1) Remove node_id because it's an internal value and
            #    not present in the API object
            # 2) Add node_uuid
            port_dict['node_uuid'] = port_dict.pop('node_id', None)
            # NOTE(vsaienko):
            # 1) Remove portgroup_id because it's an internal value and
            #    not present in the API object
            # 2) Add portgroup_uuid
            port_dict['portgroup_uuid'] = port_dict.pop('portgroup_id', None)
            port = Port(**api_utils.apply_jsonpatch(port_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        if api_utils.is_path_removed(patch, '/portgroup_uuid'):
            rpc_port.portgroup_id = None

        # Update only the fields that have changed
        for field in objects.Port.fields:
            try:
                patch_val = getattr(port, field)
            except AttributeError:
                # Ignore fields that aren't exposed in the API
                continue
            if patch_val == wtypes.Unset:
                patch_val = None
            if rpc_port[field] != patch_val:
                rpc_port[field] = patch_val

        rpc_node = objects.Node.get_by_id(pecan.request.context,
                                          rpc_port.node_id)
        topic = pecan.request.rpcapi.get_topic_for(rpc_node)

        new_port = pecan.request.rpcapi.update_port(
            pecan.request.context, rpc_port, topic)

        return Port.convert_with_links(new_port)