Esempio n. 1
0
class Controller(rest.RestController):
    """Version 1 API controller root."""

    nodes = node.NodesController()
    ports = port.PortsController()
    chassis = chassis.ChassisController()
    drivers = driver.DriversController()
    lookup = ramdisk.LookupController()
    heartbeat = ramdisk.HeartbeatController()

    @expose.expose(V1)
    def get(self):
        # NOTE: The reason why convert() it's being called for every
        #       request is because we need to get the host url from
        #       the request object to make the links.
        return V1.convert()

    def _check_version(self, version, headers=None):
        if headers is None:
            headers = {}
        # ensure that major version in the URL matches the header
        if version.major != BASE_VERSION:
            raise exc.HTTPNotAcceptable(
                _("Mutually exclusive versions requested. Version %(ver)s "
                  "requested but not supported by this service. The supported "
                  "version range is: [%(min)s, %(max)s].") % {
                      'ver': version,
                      'min': versions.MIN_VERSION_STRING,
                      'max': versions.MAX_VERSION_STRING
                  },
                headers=headers)
        # ensure the minor version is within the supported range
        if version < MIN_VER or version > MAX_VER:
            raise exc.HTTPNotAcceptable(
                _("Version %(ver)s was requested but the minor version is not "
                  "supported by this service. The supported version range is: "
                  "[%(min)s, %(max)s].") % {
                      'ver': version,
                      'min': versions.MIN_VERSION_STRING,
                      'max': versions.MAX_VERSION_STRING
                  },
                headers=headers)

    @pecan.expose()
    def _route(self, args):
        v = base.Version(pecan.request.headers, versions.MIN_VERSION_STRING,
                         versions.MAX_VERSION_STRING)

        # Always set the min and max headers
        pecan.response.headers[base.Version.min_string] = (
            versions.MIN_VERSION_STRING)
        pecan.response.headers[base.Version.max_string] = (
            versions.MAX_VERSION_STRING)

        # assert that requested version is supported
        self._check_version(v, pecan.response.headers)
        pecan.response.headers[base.Version.string] = str(v)
        pecan.request.version = v

        return super(Controller, self)._route(args)
Esempio n. 2
0
class Controller(rest.RestController):
    """Version 1 API controller root."""

    nodes = node.NodesController()
    ports = port.PortsController()
    chassis = chassis.ChassisController()

    @wsme_pecan.wsexpose(V1)
    def get(self):
        # NOTE: The reason why convert() it's being called for every
        #       request is because we need to get the host url from
        #       the request object to make the links.
        return V1.convert()
Esempio n. 3
0
class Controller(rest.RestController):
    """Version 1 API controller root."""

    nodes = node.NodesController()
    ports = port.PortsController()
    chassis = chassis.ChassisController()
    drivers = driver.DriversController()

    @wsme_pecan.wsexpose(V1)
    def get(self):
        # NOTE: The reason why convert() it's being called for every
        #       request is because we need to get the host url from
        #       the request object to make the links.
        return V1.convert()

    def _check_version(self, version):
        # ensure that major version in the URL matches the header
        if version.major != 1:
            raise exc.HTTPNotAcceptable(
                _("Mutually exclusive versions requested. Version %(ver)s "
                  "requested but not supported by this service.") %
                {'ver': version})
        # ensure the minor version is within the supported range
        if version.minor < MIN_VER or version.minor > MAX_VER:
            raise exc.HTTPNotAcceptable(
                _("Unsupported minor version requested. This API service "
                  "supports the following version range: "
                  "[%(min)s, %(max)s].") % {
                      'min': MIN_VER,
                      'max': MAX_VER
                  })

        version.set_min_max(MIN_VER, MAX_VER)

    @pecan.expose()
    def _route(self, args):
        v = base.Version(pecan.request.headers)
        # Always set the min and max headers
        # FIXME: these are not being sent if _check_version raises an exception
        pecan.response.headers[base.Version.min_string] = str(MIN_VER)
        pecan.response.headers[base.Version.max_string] = str(MAX_VER)

        # assert that requested version is supported
        self._check_version(v)
        pecan.response.headers[base.Version.string] = str(v)
        pecan.request.version = v

        return super(Controller, self)._route(args)
Esempio n. 4
0
class NodesController(rest.RestController):
    """REST controller for Nodes."""

    states = NodeStatesController()
    """Expose the state controller action as a sub-element of nodes"""

    vendor_passthru = NodeVendorPassthruController()
    """A resource used for vendors to expose a custom functionality in
    the API"""

    ports = port.PortsController()
    """Expose ports as a sub-element of nodes"""

    management = NodeManagementController()
    """Expose management as a sub-element of nodes"""

    maintenance = NodeMaintenanceController()
    """Expose maintenance as a sub-element of nodes"""

    # Set the flag to indicate that the requests to this resource are
    # coming from a top-level resource
    ports.from_nodes = True

    from_chassis = False
    """A flag to indicate if the requests to this controller are coming
    from the top-level resource Chassis"""

    _custom_actions = {
        'detail': ['GET'],
        'validate': ['GET'],
    }

    invalid_sort_key_list = ['properties', 'driver_info', 'extra',
                             'instance_info', 'driver_internal_info',
                             'clean_step']

    def _get_nodes_collection(self, chassis_uuid, instance_uuid, associated,
                              maintenance, provision_state, marker, limit,
                              sort_key, sort_dir, resource_url=None,
                              fields=None):
        if self.from_chassis and not chassis_uuid:
            raise exception.MissingParameterValue(
                _("Chassis id not specified."))

        limit = api_utils.validate_limit(limit)
        sort_dir = api_utils.validate_sort_dir(sort_dir)

        marker_obj = None
        if marker:
            marker_obj = objects.Node.get_by_uuid(pecan.request.context,
                                                  marker)

        if sort_key in self.invalid_sort_key_list:
            raise exception.InvalidParameterValue(
                _("The sort_key value %(key)s is an invalid field for "
                  "sorting") % {'key': sort_key})

        if instance_uuid:
            nodes = self._get_nodes_by_instance(instance_uuid)
        else:
            filters = {}
            if chassis_uuid:
                filters['chassis_uuid'] = chassis_uuid
            if associated is not None:
                filters['associated'] = associated
            if maintenance is not None:
                filters['maintenance'] = maintenance
            if provision_state:
                filters['provision_state'] = provision_state

            nodes = objects.Node.list(pecan.request.context, limit, marker_obj,
                                      sort_key=sort_key, sort_dir=sort_dir,
                                      filters=filters)

        parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
        if associated:
            parameters['associated'] = associated
        if maintenance:
            parameters['maintenance'] = maintenance
        return NodeCollection.convert_with_links(nodes, limit,
                                                 url=resource_url,
                                                 fields=fields,
                                                 **parameters)

    def _get_nodes_by_instance(self, instance_uuid):
        """Retrieve a node by its instance uuid.

        It returns a list with the node, or an empty list if no node is found.
        """
        try:
            node = objects.Node.get_by_instance_uuid(pecan.request.context,
                                                     instance_uuid)
            return [node]
        except exception.InstanceNotFound:
            return []

    @expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
                   types.boolean, wtypes.text, types.uuid, int, wtypes.text,
                   wtypes.text, types.listtype)
    def get_all(self, chassis_uuid=None, instance_uuid=None, associated=None,
                maintenance=None, provision_state=None, marker=None,
                limit=None, sort_key='id', sort_dir='asc', fields=None):
        """Retrieve a list of nodes.

        :param chassis_uuid: Optional UUID of a chassis, to get only nodes for
                           that chassis.
        :param instance_uuid: Optional UUID of an instance, to find the node
                              associated with that instance.
        :param associated: Optional boolean whether to return a list of
                           associated or unassociated nodes. May be combined
                           with other parameters.
        :param maintenance: Optional boolean value that indicates whether
                            to get nodes in maintenance mode ("True"), or not
                            in maintenance mode ("False").
        :param provision_state: Optional string value to get only nodes in
                                that provision state.
        :param marker: pagination marker for large data sets.
        :param limit: maximum number of resources to return in a single result.
        :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.
        """
        api_utils.check_allow_specify_fields(fields)
        api_utils.check_for_invalid_state_and_allow_filter(provision_state)
        if fields is None:
            fields = _DEFAULT_RETURN_FIELDS
        return self._get_nodes_collection(chassis_uuid, instance_uuid,
                                          associated, maintenance,
                                          provision_state, marker,
                                          limit, sort_key, sort_dir,
                                          fields=fields)

    @expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
                   types.boolean, wtypes.text, types.uuid, int, wtypes.text,
                   wtypes.text)
    def detail(self, chassis_uuid=None, instance_uuid=None, associated=None,
               maintenance=None, provision_state=None, marker=None,
               limit=None, sort_key='id', sort_dir='asc'):
        """Retrieve a list of nodes with detail.

        :param chassis_uuid: Optional UUID of a chassis, to get only nodes for
                           that chassis.
        :param instance_uuid: Optional UUID of an instance, to find the node
                              associated with that instance.
        :param associated: Optional boolean whether to return a list of
                           associated or unassociated nodes. May be combined
                           with other parameters.
        :param maintenance: Optional boolean value that indicates whether
                            to get nodes in maintenance mode ("True"), or not
                            in maintenance mode ("False").
        :param provision_state: Optional string value to get only nodes in
                                that provision state.
        :param marker: pagination marker for large data sets.
        :param limit: maximum number of resources to return in a single result.
        :param sort_key: column to sort results by. Default: id.
        :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
        """
        api_utils.check_for_invalid_state_and_allow_filter(provision_state)
        # /detail should only work against collections
        parent = pecan.request.path.split('/')[:-1][-1]
        if parent != "nodes":
            raise exception.HTTPNotFound

        resource_url = '/'.join(['nodes', 'detail'])
        return self._get_nodes_collection(chassis_uuid, instance_uuid,
                                          associated, maintenance,
                                          provision_state, marker,
                                          limit, sort_key, sort_dir,
                                          resource_url)

    @expose.expose(wtypes.text, types.uuid_or_name, types.uuid)
    def validate(self, node=None, node_uuid=None):
        """Validate the driver interfaces, using the node's UUID or name.

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

        :param node: UUID or name of a node.
        :param node_uuid: UUID of a node.
        """
        if node:
            # We're invoking this interface using positional notation, or
            # explicitly using 'node'.  Try and determine which one.
            if (not api_utils.allow_node_logical_names() and
                not uuidutils.is_uuid_like(node)):
                raise exception.NotAcceptable()

        rpc_node = api_utils.get_rpc_node(node_uuid or node)

        topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        return pecan.request.rpcapi.validate_driver_interfaces(
            pecan.request.context, rpc_node.uuid, topic)

    @expose.expose(Node, types.uuid_or_name, types.listtype)
    def get_one(self, node_ident, fields=None):
        """Retrieve information about the given node.

        :param node_ident: UUID or logical name of a node.
        :param fields: Optional, a list with a specified set of fields
            of the resource to be returned.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        api_utils.check_allow_specify_fields(fields)

        rpc_node = api_utils.get_rpc_node(node_ident)
        return Node.convert_with_links(rpc_node, fields=fields)

    @expose.expose(Node, body=Node, status_code=201)
    def post(self, node):
        """Create a new node.

        :param node: a node within the request body.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        # NOTE(deva): get_topic_for checks if node.driver is in the hash ring
        #             and raises NoValidHost if it is not.
        #             We need to ensure that node has a UUID before it can
        #             be mapped onto the hash ring.
        if not node.uuid:
            node.uuid = uuidutils.generate_uuid()

        try:
            pecan.request.rpcapi.get_topic_for(node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = 400
            raise e

        # Verify that if we're creating a new node with a 'name' set
        # that it is a valid name
        if node.name:
            if not api_utils.allow_node_logical_names():
                raise exception.NotAcceptable()
            if not api_utils.is_valid_node_name(node.name):
                msg = _("Cannot create node with invalid name %(name)s")
                raise wsme.exc.ClientSideError(msg % {'name': node.name},
                                               status_code=400)

        node.provision_state = api_utils.initial_node_provision_state()

        new_node = objects.Node(pecan.request.context,
                                **node.as_dict())
        new_node.create()
        # Set the HTTP Location Header
        pecan.response.location = link.build_url('nodes', new_node.uuid)
        return Node.convert_with_links(new_node)

    @wsme.validate(types.uuid, [NodePatchType])
    @expose.expose(Node, types.uuid_or_name, body=[NodePatchType])
    def patch(self, node_ident, patch):
        """Update an existing node.

        :param node_ident: UUID or logical name of a node.
        :param patch: a json PATCH document to apply to this node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = api_utils.get_rpc_node(node_ident)

        # Check if node is transitioning state, although nodes in some states
        # can be updated.
        if ((rpc_node.maintenance or
                rpc_node.provision_state == ir_states.CLEANING) and
                patch == [{'op': 'remove', 'path': '/instance_uuid'}]):
            # Allow node.instance_uuid removal during cleaning, but not other
            # operations. Also allow it during maintenance, to break
            # association with Nova in case of serious problems.
            # TODO(JoshNang) remove node.instance_uuid when removing
            # instance_info and stop removing node.instance_uuid in the Nova
            # Ironic driver. Bug: 1436568
            LOG.debug('Removing instance uuid %(instance)s from node %(node)s',
                      {'instance': rpc_node.instance_uuid,
                       'node': rpc_node.uuid})
        elif ((rpc_node.target_power_state or rpc_node.target_provision_state)
                and rpc_node.provision_state not in
                ir_states.UPDATE_ALLOWED_STATES):
            msg = _("Node %s can not be updated while a state transition "
                    "is in progress.")
            raise wsme.exc.ClientSideError(msg % node_ident, status_code=409)

        # Verify that if we're patching 'name' that it is a valid
        name = api_utils.get_patch_value(patch, '/name')
        if name:
            if not api_utils.allow_node_logical_names():
                raise exception.NotAcceptable()
            if not api_utils.is_valid_node_name(name):
                msg = _("Node %(node)s: Cannot change name to invalid "
                        "name '%(name)s'")
                raise wsme.exc.ClientSideError(msg % {'node': node_ident,
                                                      'name': name},
                                               status_code=400)

        try:
            node_dict = rpc_node.as_dict()
            # NOTE(lucasagomes):
            # 1) Remove chassis_id because it's an internal value and
            #    not present in the API object
            # 2) Add chassis_uuid
            node_dict['chassis_uuid'] = node_dict.pop('chassis_id', None)
            node = Node(**api_utils.apply_jsonpatch(node_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

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

        # NOTE(deva): we calculate the rpc topic here in case node.driver
        #             has changed, so that update is sent to the
        #             new conductor, not the old one which may fail to
        #             load the new driver.
        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = 400
            raise e

        # NOTE(lucasagomes): If it's changing the driver and the console
        # is enabled we prevent updating it because the new driver will
        # not be able to stop a console started by the previous one.
        delta = rpc_node.obj_what_changed()
        if 'driver' in delta and rpc_node.console_enabled:
            raise wsme.exc.ClientSideError(
                _("Node %s can not update the driver while the console is "
                  "enabled. Please stop the console first.") % node_ident,
                status_code=409)

        new_node = pecan.request.rpcapi.update_node(
            pecan.request.context, rpc_node, topic)

        return Node.convert_with_links(new_node)

    @expose.expose(None, types.uuid_or_name, status_code=204)
    def delete(self, node_ident):
        """Delete a node.

        :param node_ident: UUID or logical name of a node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = api_utils.get_rpc_node(node_ident)

        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            e.code = 400
            raise e

        pecan.request.rpcapi.destroy_node(pecan.request.context,
                                          rpc_node.uuid, topic)
Esempio n. 5
0
class NodesController(rest.RestController):
    """REST controller for Nodes."""

    states = NodeStatesController()
    "Expose the state controller action as a sub-element of nodes"

    vendor_passthru = NodeVendorPassthruController()
    "A resource used for vendors to expose a custom functionality in the API"

    ports = port.PortsController()
    "Expose ports as a sub-element of nodes"

    management = NodeManagementController()
    "Expose management as a sub-element of nodes"

    # Set the flag to indicate that the requests to this resource are
    # coming from a top-level resource
    ports.from_nodes = True

    from_chassis = False
    """A flag to indicate if the requests to this controller are coming
    from the top-level resource Chassis"""

    _custom_actions = {
        'detail': ['GET'],
        'validate': ['GET'],
    }

    def _get_nodes_collection(self,
                              chassis_uuid,
                              instance_uuid,
                              associated,
                              maintenance,
                              marker,
                              limit,
                              sort_key,
                              sort_dir,
                              expand=False,
                              resource_url=None):
        if self.from_chassis and not chassis_uuid:
            raise exception.MissingParameterValue(
                _("Chassis id not specified."))

        limit = api_utils.validate_limit(limit)
        sort_dir = api_utils.validate_sort_dir(sort_dir)

        marker_obj = None
        if marker:
            marker_obj = objects.Node.get_by_uuid(pecan.request.context,
                                                  marker)
        if instance_uuid:
            nodes = self._get_nodes_by_instance(instance_uuid)
        else:
            filters = {}
            if chassis_uuid:
                filters['chassis_uuid'] = chassis_uuid
            if associated is not None:
                filters['associated'] = associated
            if maintenance is not None:
                filters['maintenance'] = maintenance

            nodes = objects.Node.list(pecan.request.context,
                                      limit,
                                      marker_obj,
                                      sort_key=sort_key,
                                      sort_dir=sort_dir,
                                      filters=filters)

        parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
        if associated:
            parameters['associated'] = associated
        if maintenance:
            parameters['maintenance'] = maintenance
        return NodeCollection.convert_with_links(nodes,
                                                 limit,
                                                 url=resource_url,
                                                 expand=expand,
                                                 **parameters)

    def _get_nodes_by_instance(self, instance_uuid):
        """Retrieve a node by its instance uuid.

        It returns a list with the node, or an empty list if no node is found.
        """
        try:
            node = objects.Node.get_by_instance_uuid(pecan.request.context,
                                                     instance_uuid)
            return [node]
        except exception.InstanceNotFound:
            return []

    @wsme_pecan.wsexpose(NodeCollection, types.uuid, types.uuid, types.boolean,
                         types.boolean, types.uuid, int, wtypes.text,
                         wtypes.text)
    def get_all(self,
                chassis_uuid=None,
                instance_uuid=None,
                associated=None,
                maintenance=None,
                marker=None,
                limit=None,
                sort_key='id',
                sort_dir='asc'):
        """Retrieve a list of nodes.

        :param chassis_uuid: Optional UUID of a chassis, to get only nodes for
                           that chassis.
        :param instance_uuid: Optional UUID of an instance, to find the node
                              associated with that instance.
        :param associated: Optional boolean whether to return a list of
                           associated or unassociated nodes. May be combined
                           with other parameters.
        :param maintenance: Optional boolean value that indicates whether
                            to get nodes in maintenance mode ("True"), or not
                            in maintenance mode ("False").
        :param marker: pagination marker for large data sets.
        :param limit: maximum number of resources to return in a single result.
        :param sort_key: column to sort results by. Default: id.
        :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
        """
        return self._get_nodes_collection(chassis_uuid, instance_uuid,
                                          associated, maintenance, marker,
                                          limit, sort_key, sort_dir)

    @wsme_pecan.wsexpose(NodeCollection, types.uuid, types.uuid, types.boolean,
                         types.boolean, types.uuid, int, wtypes.text,
                         wtypes.text)
    def detail(self,
               chassis_uuid=None,
               instance_uuid=None,
               associated=None,
               maintenance=None,
               marker=None,
               limit=None,
               sort_key='id',
               sort_dir='asc'):
        """Retrieve a list of nodes with detail.

        :param chassis_uuid: Optional UUID of a chassis, to get only nodes for
                           that chassis.
        :param instance_uuid: Optional UUID of an instance, to find the node
                              associated with that instance.
        :param associated: Optional boolean whether to return a list of
                           associated or unassociated nodes. May be combined
                           with other parameters.
        :param maintenance: Optional boolean value that indicates whether
                            to get nodes in maintenance mode ("True"), or not
                            in maintenance mode ("False").
        :param marker: pagination marker for large data sets.
        :param limit: maximum number of resources to return in a single result.
        :param sort_key: column to sort results by. Default: id.
        :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
        """
        # /detail should only work agaist collections
        parent = pecan.request.path.split('/')[:-1][-1]
        if parent != "nodes":
            raise exception.HTTPNotFound

        expand = True
        resource_url = '/'.join(['nodes', 'detail'])
        return self._get_nodes_collection(chassis_uuid, instance_uuid,
                                          associated, maintenance, marker,
                                          limit, sort_key, sort_dir, expand,
                                          resource_url)

    @wsme_pecan.wsexpose(wtypes.text, types.uuid)
    def validate(self, node_uuid):
        """Validate the driver interfaces.

        :param node_uuid: UUID of a node.
        """
        # check if node exists
        rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)
        topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        return pecan.request.rpcapi.validate_driver_interfaces(
            pecan.request.context, rpc_node.uuid, topic)

    @wsme_pecan.wsexpose(Node, types.uuid)
    def get_one(self, node_uuid):
        """Retrieve information about the given node.

        :param node_uuid: UUID of a node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)
        return Node.convert_with_links(rpc_node)

    @wsme_pecan.wsexpose(Node, body=Node, status_code=201)
    def post(self, node):
        """Create a new node.

        :param node: a node within the request body.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        # NOTE(deva): get_topic_for checks if node.driver is in the hash ring
        #             and raises NoValidHost if it is not.
        #             We need to ensure that node has a UUID before it can
        #             be mapped onto the hash ring.
        if not node.uuid:
            node.uuid = utils.generate_uuid()

        try:
            pecan.request.rpcapi.get_topic_for(node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = 400
            raise e

        new_node = objects.Node(context=pecan.request.context,
                                **node.as_dict())
        new_node.create()
        # Set the HTTP Location Header
        pecan.response.location = link.build_url('nodes', new_node.uuid)
        return Node.convert_with_links(new_node)

    @wsme.validate(types.uuid, [NodePatchType])
    @wsme_pecan.wsexpose(Node, types.uuid, body=[NodePatchType])
    def patch(self, node_uuid, patch):
        """Update an existing node.

        :param node_uuid: UUID of a node.
        :param patch: a json PATCH document to apply to this node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)

        # Check if node is transitioning state
        if rpc_node['target_power_state'] or \
             rpc_node['target_provision_state']:
            msg = _("Node %s can not be updated while a state transition "
                    "is in progress.")
            raise wsme.exc.ClientSideError(msg % node_uuid, status_code=409)

        try:
            node_dict = rpc_node.as_dict()
            # NOTE(lucasagomes):
            # 1) Remove chassis_id because it's an internal value and
            #    not present in the API object
            # 2) Add chassis_uuid
            node_dict['chassis_uuid'] = node_dict.pop('chassis_id', None)
            node = Node(**api_utils.apply_jsonpatch(node_dict, patch))
        except api_utils.JSONPATCH_EXCEPTIONS as e:
            raise exception.PatchError(patch=patch, reason=e)

        # Update only the fields that have changed
        for field in objects.Node.fields:
            try:
                patch_val = getattr(node, field)
            except AttributeError:
                # Ignore fields that aren't exposed in the API
                continue
            if rpc_node[field] != patch_val:
                rpc_node[field] = patch_val

        # NOTE(deva): we calculate the rpc topic here in case node.driver
        #             has changed, so that update is sent to the
        #             new conductor, not the old one which may fail to
        #             load the new driver.
        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            # NOTE(deva): convert from 404 to 400 because client can see
            #             list of available drivers and shouldn't request
            #             one that doesn't exist.
            e.code = 400
            raise e

        new_node = pecan.request.rpcapi.update_node(pecan.request.context,
                                                    rpc_node, topic)

        return Node.convert_with_links(new_node)

    @wsme_pecan.wsexpose(None, types.uuid, status_code=204)
    def delete(self, node_uuid):
        """Delete a node.

        :param node_uuid: UUID of a node.
        """
        if self.from_chassis:
            raise exception.OperationNotPermitted

        rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)
        try:
            topic = pecan.request.rpcapi.get_topic_for(rpc_node)
        except exception.NoValidHost as e:
            e.code = 400
            raise e

        pecan.request.rpcapi.destroy_node(pecan.request.context, node_uuid,
                                          topic)
Esempio n. 6
0
class Controller(rest.RestController):
    """Version 1 API controller root."""

    nodes = node.NodesController()
    ports = port.PortsController()
    portgroups = portgroup.PortgroupsController()
    chassis = chassis.ChassisController()
    drivers = driver.DriversController()
    volume = volume.VolumeController()
    lookup = ramdisk.LookupController()
    heartbeat = ramdisk.HeartbeatController()
    conductors = conductor.ConductorsController()
    allocations = allocation.AllocationsController()
    events = event.EventsController()
    deploy_templates = deploy_template.DeployTemplatesController()

    @expose.expose(V1)
    def get(self):
        # NOTE: The reason why convert() it's being called for every
        #       request is because we need to get the host url from
        #       the request object to make the links.
        return V1.convert()

    def _check_version(self, version, headers=None):
        if headers is None:
            headers = {}
        # ensure that major version in the URL matches the header
        if version.major != BASE_VERSION:
            raise exc.HTTPNotAcceptable(
                _("Mutually exclusive versions requested. Version %(ver)s "
                  "requested but not supported by this service. The supported "
                  "version range is: [%(min)s, %(max)s].") % {
                      'ver': version,
                      'min': versions.min_version_string(),
                      'max': versions.max_version_string()
                  },
                headers=headers)
        # ensure the minor version is within the supported range
        if version < min_version() or version > max_version():
            raise exc.HTTPNotAcceptable(
                _("Version %(ver)s was requested but the minor version is not "
                  "supported by this service. The supported version range is: "
                  "[%(min)s, %(max)s].") % {
                      'ver': version,
                      'min': versions.min_version_string(),
                      'max': versions.max_version_string()
                  },
                headers=headers)

    @pecan.expose()
    def _route(self, args, request=None):
        v = base.Version(api.request.headers, versions.min_version_string(),
                         versions.max_version_string())

        # Always set the min and max headers
        api.response.headers[base.Version.min_string] = (
            versions.min_version_string())
        api.response.headers[base.Version.max_string] = (
            versions.max_version_string())

        # assert that requested version is supported
        self._check_version(v, api.response.headers)
        api.response.headers[base.Version.string] = str(v)
        api.request.version = v

        return super(Controller, self)._route(args, request)
Esempio n. 7
0
class Controller(object):
    """Version 1 API controller root."""

    _subcontroller_map = {
        'nodes': node.NodesController(),
        'ports': port.PortsController(),
        'portgroups': portgroup.PortgroupsController(),
        'chassis': chassis.ChassisController(),
        'drivers': driver.DriversController(),
        'volume': volume.VolumeController(),
        'lookup': ramdisk.LookupController(),
        'heartbeat': ramdisk.HeartbeatController(),
        'conductors': conductor.ConductorsController(),
        'allocations': allocation.AllocationsController(),
        'events': event.EventsController(),
        'deploy_templates': deploy_template.DeployTemplatesController()
    }

    @method.expose()
    def index(self):
        # NOTE: The reason why v1() it's being called for every
        #       request is because we need to get the host url from
        #       the request object to make the links.
        self._add_version_attributes()
        if api.request.method != "GET":
            pecan.abort(http_client.METHOD_NOT_ALLOWED)

        return v1()

    def _check_version(self, version, headers=None):
        if headers is None:
            headers = {}
        # ensure that major version in the URL matches the header
        if version.major != BASE_VERSION:
            raise exc.HTTPNotAcceptable(
                _("Mutually exclusive versions requested. Version %(ver)s "
                  "requested but not supported by this service. The supported "
                  "version range is: [%(min)s, %(max)s].") % {
                      'ver': version,
                      'min': versions.min_version_string(),
                      'max': versions.max_version_string()
                  },
                headers=headers)
        # ensure the minor version is within the supported range
        if version < min_version() or version > max_version():
            raise exc.HTTPNotAcceptable(
                _("Version %(ver)s was requested but the minor version is not "
                  "supported by this service. The supported version range is: "
                  "[%(min)s, %(max)s].") % {
                      'ver': version,
                      'min': versions.min_version_string(),
                      'max': versions.max_version_string()
                  },
                headers=headers)

    def _add_version_attributes(self):
        v = base.Version(api.request.headers, versions.min_version_string(),
                         versions.max_version_string())

        # Always set the min and max headers
        api.response.headers[base.Version.min_string] = (
            versions.min_version_string())
        api.response.headers[base.Version.max_string] = (
            versions.max_version_string())

        # assert that requested version is supported
        self._check_version(v, api.response.headers)
        api.response.headers[base.Version.string] = str(v)
        api.request.version = v

    @pecan.expose()
    def _lookup(self, primary_key, *remainder):
        self._add_version_attributes()

        controller = self._subcontroller_map.get(primary_key)
        if not controller:
            pecan.abort(http_client.NOT_FOUND)

        return controller, remainder