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