diff options
| author | Dmitry Tantsur <dtantsur@protonmail.com> | 2019-11-19 18:19:31 +0100 |
|---|---|---|
| committer | Dmitry Tantsur <dtantsur@protonmail.com> | 2019-12-04 16:50:47 +0100 |
| commit | f1b2df908a0f13b81ecd36a1376e55ce14503b06 (patch) | |
| tree | 69e7571497f20862a160e185c27a156465475158 /ironic_python_agent/api/controllers | |
| parent | 6032643a04cfc6867ad925845ddd2b3f7d99b4e9 (diff) | |
| download | ironic-python-agent-f1b2df908a0f13b81ecd36a1376e55ce14503b06.tar.gz | |
Replace WSME and Pecan with Werkzeug
WSME is no longer maintained and Pecan is an overkill for our (purely
internal) API. This change rewrites the API in Werkzeug (the library
underneath Flask). I don't use Flask here since it's also an overkill
for API with 4 meaningful endpoints.
Change-Id: Ifed45f70869adf00e795202a53a2a53c9c57ef30
Diffstat (limited to 'ironic_python_agent/api/controllers')
| -rw-r--r-- | ironic_python_agent/api/controllers/__init__.py | 0 | ||||
| -rw-r--r-- | ironic_python_agent/api/controllers/root.py | 97 | ||||
| -rw-r--r-- | ironic_python_agent/api/controllers/v1/__init__.py | 118 | ||||
| -rw-r--r-- | ironic_python_agent/api/controllers/v1/base.py | 73 | ||||
| -rw-r--r-- | ironic_python_agent/api/controllers/v1/command.py | 126 | ||||
| -rw-r--r-- | ironic_python_agent/api/controllers/v1/link.py | 43 | ||||
| -rw-r--r-- | ironic_python_agent/api/controllers/v1/status.py | 55 |
7 files changed, 0 insertions, 512 deletions
diff --git a/ironic_python_agent/api/controllers/__init__.py b/ironic_python_agent/api/controllers/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/ironic_python_agent/api/controllers/__init__.py +++ /dev/null diff --git a/ironic_python_agent/api/controllers/root.py b/ironic_python_agent/api/controllers/root.py deleted file mode 100644 index c95e2186..00000000 --- a/ironic_python_agent/api/controllers/root.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2014 Rackspace, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from ironic_lib import metrics_utils -import pecan -from pecan import rest -from wsme import types as wtypes -import wsmeext.pecan as wsme_pecan - -from ironic_python_agent.api.controllers import v1 -from ironic_python_agent.api.controllers.v1 import base -from ironic_python_agent.api.controllers.v1 import link - - -class Version(base.APIBase): - """An API version representation.""" - - id = wtypes.text - "The ID of the version, also acts as the release number" - - links = [link.Link] - "A Link that point to a specific version of the API" - - @classmethod - def convert(self, id): - version = Version() - version.id = id - version.links = [link.Link.make_link('self', pecan.request.host_url, - id, '', bookmark=True)] - return version - - -class Root(base.APIBase): - - name = wtypes.text - "The name of the API" - - description = wtypes.text - "Some information about this API" - - versions = [Version] - "Links to all the versions available in this API" - - default_version = Version - "A link to the default version of the API" - - @classmethod - def convert(self): - root = Root() - root.name = 'OpenStack Ironic Python Agent API' - root.description = ('Ironic Python Agent is a provisioning agent for ' - 'OpenStack Ironic') - root.versions = [Version.convert('v1')] - root.default_version = Version.convert('v1') - return root - - -class RootController(rest.RestController): - - _versions = ['v1'] - "All supported API versions" - - _default_version = 'v1' - "The default API version" - - v1 = v1.Controller() - - @wsme_pecan.wsexpose(Root) - 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. - with metrics_utils.get_metrics_logger(__name__).timer('get'): - return Root.convert() - - @pecan.expose() - def _route(self, args): - """Overrides the default routing behavior. - - It redirects the request to the default version of the ironic API - if the version number is not specified in the url. - """ - - if args[0] and args[0] not in self._versions: - args = [self._default_version] + args - return super(RootController, self)._route(args) diff --git a/ironic_python_agent/api/controllers/v1/__init__.py b/ironic_python_agent/api/controllers/v1/__init__.py deleted file mode 100644 index 7b4fae9f..00000000 --- a/ironic_python_agent/api/controllers/v1/__init__.py +++ /dev/null @@ -1,118 +0,0 @@ -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Version 1 of the Ironic Python Agent API -""" - -import pecan -from pecan import rest -from wsme import types as wtypes -import wsmeext.pecan as wsme_pecan - -from ironic_python_agent.api.controllers.v1 import base -from ironic_python_agent.api.controllers.v1 import command -from ironic_python_agent.api.controllers.v1 import link -from ironic_python_agent.api.controllers.v1 import status - - -class MediaType(base.APIBase): - """A media type representation.""" - - base = wtypes.text - type = wtypes.text - - def __init__(self, base, type): - self.base = base - self.type = type - - -class V1(base.APIBase): - """The representation of the version 1 of the API.""" - - id = wtypes.text - "The ID of the version, also acts as the release number" - - media_types = [MediaType] - "An array of supported media types for this version" - - links = [link.Link] - "Links that point to a specific URL for this version and documentation" - - commands = [link.Link] - "Links to the command resource" - - status = [link.Link] - "Links to the status resource" - - @classmethod - def convert(self): - v1 = V1() - v1.id = "v1" - v1.links = [ - link.Link.make_link('self', - pecan.request.host_url, - 'v1', - '', - bookmark=True), - link.Link.make_link('describedby', - 'https://docs.openstack.org', - 'developer', - 'ironic-python-agent', - bookmark=True, - type='text/html') - ] - v1.commands = [ - link.Link.make_link('self', - pecan.request.host_url, - 'commands', - ''), - link.Link.make_link('bookmark', - pecan.request.host_url, - 'commands', - '', - bookmark=True) - ] - v1.status = [ - link.Link.make_link('self', - pecan.request.host_url, - 'status', - ''), - link.Link.make_link('bookmark', - pecan.request.host_url, - 'status', - '', - bookmark=True) - ] - v1.media_types = [MediaType('application/json', - ('application/vnd.openstack.' - 'ironic-python-agent.v1+json'))] - return v1 - - -class Controller(rest.RestController): - """Version 1 API controller root.""" - - commands = command.CommandController() - status = status.StatusController() - - @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() - - -__all__ = (Controller) diff --git a/ironic_python_agent/api/controllers/v1/base.py b/ironic_python_agent/api/controllers/v1/base.py deleted file mode 100644 index c3644efa..00000000 --- a/ironic_python_agent/api/controllers/v1/base.py +++ /dev/null @@ -1,73 +0,0 @@ -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from wsme import types as wtypes - - -class ExceptionType(wtypes.UserType): - basetype = wtypes.DictType - name = 'exception' - - def validate(self, value): - if not isinstance(value, BaseException): - raise ValueError('Value is not an exception') - return value - - def tobasetype(self, value): - """Turn an Exception into a dict.""" - return { - 'type': value.__class__.__name__, - 'code': getattr(value, 'status_code', 500), - 'message': str(value), - 'details': getattr(value, 'details', ''), - } - - frombasetype = tobasetype - - -exception_type = ExceptionType() - - -class MultiType(wtypes.UserType): - """A complex type that represents one or more types. - - Used for validating that a value is an instance of one of the types. - - :param types: Variable-length list of types. - - """ - - def __init__(self, *types): - self.types = types - - def __str__(self): - return ' | '.join(map(str, self.types)) - - def validate(self, value): - for t in self.types: - if t is wtypes.text and isinstance(value, wtypes.bytes): - value = value.decode() - if isinstance(value, t): - return value - else: - raise ValueError( - "Wrong type. Expected '{type}', got '{value}'".format( - type=self.types, value=type(value))) - - -json_type = MultiType(list, dict, int, wtypes.text) - - -class APIBase(wtypes.Base): - pass diff --git a/ironic_python_agent/api/controllers/v1/command.py b/ironic_python_agent/api/controllers/v1/command.py deleted file mode 100644 index b6971fa7..00000000 --- a/ironic_python_agent/api/controllers/v1/command.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2014 Rackspace, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from ironic_lib import metrics_utils -import pecan -from pecan import rest -from wsme import types -from wsmeext import pecan as wsme_pecan - -from ironic_python_agent.api.controllers.v1 import base - - -class CommandResult(base.APIBase): - """Object representing the result of a given command.""" - - id = types.text - command_name = types.text - command_params = types.DictType(types.text, base.json_type) - command_status = types.text - command_error = base.exception_type - command_result = types.DictType(types.text, base.json_type) - - @classmethod - def from_result(cls, result): - """Convert a BaseCommandResult object to a CommandResult object. - - :param result: a :class:`ironic_python_agent.extensions.base. - BaseCommandResult` object. - :returns: a :class:`ironic_python_agent.api.controllers.v1.command. - CommandResult` object. - """ - instance = cls() - for field in ('id', 'command_name', 'command_params', 'command_status', - 'command_error', 'command_result'): - setattr(instance, field, getattr(result, field)) - return instance - - -class CommandResultList(base.APIBase): - """An object representing a list of CommandResult objects.""" - commands = [CommandResult] - - @classmethod - def from_results(cls, results): - """Convert a list of BaseCommandResult objects to a CommandResultList. - - :param results: a list of :class:`ironic_python_agent.extensions.base. - BaseCommandResult` objects. - :returns: a :class:`ironic_python_agent.api.controllers.v1.command. - CommandResultList` object. - """ - instance = cls() - instance.commands = [CommandResult.from_result(result) - for result in results] - return instance - - -class Command(base.APIBase): - """A representation of a command.""" - name = types.wsattr(types.text, mandatory=True) - params = types.wsattr(base.MultiType(dict), mandatory=True) - - -class CommandController(rest.RestController): - """Controller for issuing commands and polling for command status.""" - - @wsme_pecan.wsexpose(CommandResultList) - def get_all(self): - """Get all command results.""" - with metrics_utils.get_metrics_logger(__name__).timer('get_all'): - agent = pecan.request.agent - results = agent.list_command_results() - return CommandResultList.from_results(results) - - @wsme_pecan.wsexpose(CommandResult, types.text, types.text) - def get_one(self, result_id, wait=None): - """Get a command result by ID. - - :param result_id: the ID of the result to get. - :param wait: if 'true', block until the command completes. - :returns: a :class:`ironic_python_agent.api.controller.v1.command. - CommandResult` object. - """ - with metrics_utils.get_metrics_logger(__name__).timer('get_one'): - agent = pecan.request.agent - result = agent.get_command_result(result_id) - - if wait and wait.lower() == 'true': - result.join() - - return CommandResult.from_result(result) - - @wsme_pecan.wsexpose(CommandResult, types.text, body=Command) - def post(self, wait=None, command=None): - """Post a command for the agent to run. - - :param wait: if 'true', block until the command completes. - :param command: the command to execute. If None, an InvalidCommandError - will be returned. - :returns: a :class:`ironic_python_agent.api.controller.v1.command. - CommandResult` object. - """ - with metrics_utils.get_metrics_logger(__name__).timer('post'): - # the POST body is always the last arg, - # so command must be a kwarg here - if command is None: - command = Command() - agent = pecan.request.agent - result = agent.execute_command(command.name, **command.params) - - if wait and wait.lower() == 'true': - result.join() - - return result diff --git a/ironic_python_agent/api/controllers/v1/link.py b/ironic_python_agent/api/controllers/v1/link.py deleted file mode 100644 index 7fa7f982..00000000 --- a/ironic_python_agent/api/controllers/v1/link.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2014 Rackspace, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from wsme import types as wtypes - -from ironic_python_agent.api.controllers.v1 import base - - -class Link(base.APIBase): - """A link representation.""" - - href = wtypes.text - "The url of a link." - - rel = wtypes.text - "The name of a link." - - type = wtypes.text - "Indicates the type of document/link." - - @classmethod - def make_link(cls, rel_name, url, resource, resource_args, - bookmark=False, type=wtypes.Unset): - template = '%s/%s' if bookmark else '%s/v1/%s' - # FIXME(lucasagomes): I'm getting a 404 when doing a GET on - # a nested resource that the URL ends with a '/'. - # https://groups.google.com/forum/#!topic/pecan-dev/QfSeviLg5qs - template += '%s' if resource_args.startswith('?') else '/%s' - - return Link(href=(template) % (url, resource, resource_args), - rel=rel_name, type=type) diff --git a/ironic_python_agent/api/controllers/v1/status.py b/ironic_python_agent/api/controllers/v1/status.py deleted file mode 100644 index b1ed83b8..00000000 --- a/ironic_python_agent/api/controllers/v1/status.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2014 Rackspace, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from ironic_lib import metrics_utils -import pecan -from pecan import rest -from wsme import types -from wsmeext import pecan as wsme_pecan - -from ironic_python_agent.api.controllers.v1 import base - - -class AgentStatus(base.APIBase): - """An object representing an agent instance's status.""" - - started_at = base.MultiType(float) - version = types.text - - @classmethod - def from_agent_status(cls, status): - """Convert an object representing agent status to an AgentStatus. - - :param status: An :class:`ironic_python_agent.agent. - IronicPythonAgentStatus` object. - :returns: An :class:`ironic_python_agent.api.controllers.v1.status. - AgentStatus` object. - """ - instance = cls() - for field in ('started_at', 'version'): - setattr(instance, field, getattr(status, field)) - return instance - - -class StatusController(rest.RestController): - """Controller for getting agent status.""" - - @wsme_pecan.wsexpose(AgentStatus) - def get_all(self): - """Get current status of the running agent.""" - with metrics_utils.get_metrics_logger(__name__).timer('get_all'): - agent = pecan.request.agent - status = agent.get_status() - return AgentStatus.from_agent_status(status) |
