97 changed files with 922 additions and 7174 deletions
@ -0,0 +1,4 @@
|
||||
[gerrit] |
||||
host=review.openstack.org |
||||
port=29418 |
||||
project=openstack/iotronic.git |
@ -0,0 +1,17 @@
|
||||
If you would like to contribute to the development of OpenStack, you must |
||||
follow the steps in this page: |
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html |
||||
|
||||
If you already have a good understanding of how the system works and your |
||||
OpenStack accounts are set up, you can skip to the development workflow |
||||
section of this documentation to learn how changes to OpenStack should be |
||||
submitted for review via the Gerrit tool: |
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow |
||||
|
||||
Pull requests submitted through GitHub will be ignored. |
||||
|
||||
Bugs should be filed on Launchpad, not GitHub: |
||||
|
||||
https://bugs.launchpad.net/iotronic |
@ -0,0 +1,4 @@
|
||||
iotronic Style Commandments |
||||
=============================================== |
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ |
@ -0,0 +1,6 @@
|
||||
include AUTHORS |
||||
include ChangeLog |
||||
exclude .gitignore |
||||
exclude .gitreview |
||||
|
||||
global-exclude *.pyc |
@ -0,0 +1,19 @@
|
||||
=============================== |
||||
iotronic |
||||
=============================== |
||||
|
||||
IoTronic is an Internet of Things resource management service for OpenStack clouds. |
||||
|
||||
Please feel here a long description which must be at least 3 lines wrapped on |
||||
80 cols, so that distribution package maintainers can use it in their packages. |
||||
Note that this is a hard requirement. |
||||
|
||||
* Free software: Apache license |
||||
* Documentation: http://docs.openstack.org/developer/iotronic |
||||
* Source: http://git.openstack.org/cgit/openstack/iotronic |
||||
* Bugs: http://bugs.launchpad.net/iotronic |
||||
|
||||
Features |
||||
-------- |
||||
|
||||
* TODO |
@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*- |
||||
# 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. |
||||
|
||||
import os |
||||
import sys |
||||
|
||||
sys.path.insert(0, os.path.abspath('../..')) |
||||
# -- General configuration ---------------------------------------------------- |
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be |
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |
||||
extensions = [ |
||||
'sphinx.ext.autodoc', |
||||
#'sphinx.ext.intersphinx', |
||||
'oslosphinx' |
||||
] |
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy |
||||
# text edit cycles. |
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable |
||||
|
||||
# The suffix of source filenames. |
||||
source_suffix = '.rst' |
||||
|
||||
# The master toctree document. |
||||
master_doc = 'index' |
||||
|
||||
# General information about the project. |
||||
project = u'iotronic' |
||||
copyright = u'2013, OpenStack Foundation' |
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text. |
||||
add_function_parentheses = True |
||||
|
||||
# If true, the current module name will be prepended to all description |
||||
# unit titles (such as .. function::). |
||||
add_module_names = True |
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use. |
||||
pygments_style = 'sphinx' |
||||
|
||||
# -- Options for HTML output -------------------------------------------------- |
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with |
||||
# Sphinx are currently 'default' and 'sphinxdoc'. |
||||
# html_theme_path = ["."] |
||||
# html_theme = '_theme' |
||||
# html_static_path = ['static'] |
||||
|
||||
# Output file base name for HTML help builder. |
||||
htmlhelp_basename = '%sdoc' % project |
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples |
||||
# (source start file, target name, title, author, documentclass |
||||
# [howto/manual]). |
||||
latex_documents = [ |
||||
('index', |
||||
'%s.tex' % project, |
||||
u'%s Documentation' % project, |
||||
u'OpenStack Foundation', 'manual'), |
||||
] |
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library. |
||||
#intersphinx_mapping = {'http://docs.python.org/': None} |
@ -0,0 +1,4 @@
|
||||
============ |
||||
Contributing |
||||
============ |
||||
.. include:: ../../CONTRIBUTING.rst |
@ -0,0 +1,25 @@
|
||||
.. iotronic documentation master file, created by |
||||
sphinx-quickstart on Tue Jul 9 22:26:36 2013. |
||||
You can adapt this file completely to your liking, but it should at least |
||||
contain the root `toctree` directive. |
||||
|
||||
Welcome to iotronic's documentation! |
||||
======================================================== |
||||
|
||||
Contents: |
||||
|
||||
.. toctree:: |
||||
:maxdepth: 2 |
||||
|
||||
readme |
||||
installation |
||||
usage |
||||
contributing |
||||
|
||||
Indices and tables |
||||
================== |
||||
|
||||
* :ref:`genindex` |
||||
* :ref:`modindex` |
||||
* :ref:`search` |
||||
|
@ -0,0 +1,12 @@
|
||||
============ |
||||
Installation |
||||
============ |
||||
|
||||
At the command line:: |
||||
|
||||
$ pip install iotronic |
||||
|
||||
Or, if you have virtualenvwrapper installed:: |
||||
|
||||
$ mkvirtualenv iotronic |
||||
$ pip install iotronic |
@ -0,0 +1,7 @@
|
||||
======== |
||||
Usage |
||||
======== |
||||
|
||||
To use iotronic in a project:: |
||||
|
||||
import iotronic |
@ -1,207 +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 Iotronic API |
||||
""" |
||||
|
||||
import pecan |
||||
from pecan import rest |
||||
from webob import exc |
||||
from wsme import types as wtypes |
||||
|
||||
|
||||
from iotronic.api.controllers import base |
||||
from iotronic.api.controllers import link |
||||
#from iotronic.api.controllers.v1 import chassis |
||||
#from iotronic.api.controllers.v1 import driver |
||||
from iotronic.api.controllers.v1 import node |
||||
from iotronic.api.controllers.v1 import board |
||||
#from iotronic.api.controllers.v1 import port |
||||
from iotronic.api import expose |
||||
from iotronic.common.i18n import _ |
||||
|
||||
|
||||
|
||||
BASE_VERSION = 1 |
||||
|
||||
# NOTE(deva): v1.0 is reserved to indicate Juno's API, but is not presently |
||||
# supported by the API service. All changes between Juno and the |
||||
# point where we added microversioning are considered backwards- |
||||
# compatible, but are not specifically discoverable at this time. |
||||
# |
||||
# The v1.1 version indicates this "initial" version as being |
||||
# different from Juno (v1.0), and includes the following changes: |
||||
# |
||||
# 827db7fe: Add Node.maintenance_reason |
||||
# 68eed82b: Add API endpoint to set/unset the node maintenance mode |
||||
# bc973889: Add sync and async support for passthru methods |
||||
# e03f443b: Vendor endpoints to support different HTTP methods |
||||
# e69e5309: Make vendor methods discoverable via the Iotronic API |
||||
# edf532db: Add logic to store the config drive passed by Nova |
||||
|
||||
# v1.1: API at the point in time when microversioning support was added |
||||
MIN_VER_STR = '1.0' |
||||
|
||||
# v1.2: Renamed NOSTATE ("None") to AVAILABLE ("available") |
||||
# v1.3: Add node.driver_internal_info |
||||
# v1.4: Add MANAGEABLE state |
||||
# v1.5: Add logical node names |
||||
# v1.6: Add INSPECT* states |
||||
MAX_VER_STR = '1.0' |
||||
|
||||
|
||||
MIN_VER = base.Version({base.Version.string: MIN_VER_STR}, |
||||
MIN_VER_STR, MAX_VER_STR) |
||||
MAX_VER = base.Version({base.Version.string: MAX_VER_STR}, |
||||
MIN_VER_STR, MAX_VER_STR) |
||||
|
||||
|
||||
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""" |
||||
|
||||
#chassis = [link.Link] |
||||
"""Links to the chassis resource""" |
||||
|
||||
nodes = [link.Link] |
||||
"""Links to the nodes resource""" |
||||
|
||||
#ports = [link.Link] |
||||
"""Links to the ports resource""" |
||||
|
||||
#drivers = [link.Link] |
||||
"""Links to the drivers resource""" |
||||
|
||||
@staticmethod |
||||
def convert(): |
||||
v1 = V1() |
||||
v1.id = "v1" |
||||
|
||||
v1.links = [link.Link.make_link('self', pecan.request.host_url, |
||||
'v1', '', bookmark=True), |
||||
link.Link.make_link('describedby', |
||||
'http://docs.openstack.org', |
||||
'developer/iotronic/dev', |
||||
'api-spec-v1.html', |
||||
bookmark=True, type='text/html') |
||||
] |
||||
|
||||
v1.media_types = [MediaType('application/json', |
||||
'application/vnd.openstack.iotronic.v1+json')] |
||||
''' |
||||
v1.chassis = [link.Link.make_link('self', pecan.request.host_url, |
||||
'chassis', ''), |
||||
link.Link.make_link('bookmark', |
||||
pecan.request.host_url, |
||||
'chassis', '', |
||||
bookmark=True) |
||||
] |
||||
''' |
||||
v1.nodes = [link.Link.make_link('self', pecan.request.host_url, |
||||
'nodes', ''), |
||||
link.Link.make_link('bookmark', |
||||
pecan.request.host_url, |
||||
'nodes', '', |
||||
bookmark=True) |
||||
] |
||||
''' |
||||
v1.ports = [link.Link.make_link('self', pecan.request.host_url, |
||||
'ports', ''), |
||||
link.Link.make_link('bookmark', |
||||
pecan.request.host_url, |
||||
'ports', '', |
||||
bookmark=True) |
||||
] |
||||
v1.drivers = [link.Link.make_link('self', pecan.request.host_url, |
||||
'drivers', ''), |
||||
link.Link.make_link('bookmark', |
||||
pecan.request.host_url, |
||||
'drivers', '', |
||||
bookmark=True) |
||||
] |
||||
''' |
||||
return v1 |
||||
|
||||
|
||||
class Controller(rest.RestController): |
||||
"""Version 1 API controller root.""" |
||||
|
||||
nodes = node.NodesController() |
||||
#ports = port.PortsController() |
||||
#chassis = chassis.ChassisController() |
||||
#drivers = driver.DriversController() |
||||
boards= board.BoardsController() |
||||
|
||||
@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': MIN_VER_STR, 'max': MAX_VER_STR}, 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': MIN_VER_STR, |
||||
'max': MAX_VER_STR}, headers=headers) |
||||
|
||||
@pecan.expose() |
||||
def _route(self, args): |
||||
v = base.Version(pecan.request.headers, MIN_VER_STR, MAX_VER_STR) |
||||
|
||||
# Always set the min and max headers |
||||
pecan.response.headers[base.Version.min_string] = MIN_VER_STR |
||||
pecan.response.headers[base.Version.max_string] = MAX_VER_STR |
||||
|
||||
# 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) |
||||
|
||||
|
||||
__all__ = (Controller) |
@ -1,270 +0,0 @@
|
||||
# Copyright 2013 Red Hat, 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. |
||||
|
||||
import datetime |
||||
|
||||
import pecan |
||||
from pecan import rest |
||||
import wsme |
||||
from wsme import types as wtypes |
||||
|
||||
from iotronic.api.controllers import base |
||||
from iotronic.api.controllers import link |
||||
from iotronic.api.controllers.v1 import collection |
||||
from iotronic.api.controllers.v1 import node |
||||
from iotronic.api.controllers.v1 import types |
||||
from iotronic.api.controllers.v1 import utils as api_utils |
||||
from iotronic.api import expose |
||||
from iotronic.common import exception |
||||
from iotronic.common.i18n import _ |
||||
from iotronic import objects |
||||
|
||||
|
||||
class ChassisPatchType(types.JsonPatchType): |
||||
pass |
||||
|
||||
|
||||
class Chassis(base.APIBase): |
||||
"""API representation of a chassis. |
||||
|
||||
This class enforces type checking and value constraints, and converts |
||||
between the internal object model and the API representation of |
||||
a chassis. |
||||
""" |
||||
|
||||
uuid = types.uuid |
||||
"""The UUID of the chassis""" |
||||
|
||||
description = wtypes.text |
||||
"""The description of the chassis""" |
||||
|
||||
extra = {wtypes.text: types.jsontype} |
||||
"""The metadata of the chassis""" |
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True) |
||||
"""A list containing a self link and associated chassis links""" |
||||
|
||||
nodes = wsme.wsattr([link.Link], readonly=True) |
||||
"""Links to the collection of nodes contained in this chassis""" |
||||
|
||||
def __init__(self, **kwargs): |
||||
self.fields = [] |
||||
for field in objects.Chassis.fields: |
||||
# Skip fields we do not expose. |
||||
if not hasattr(self, field): |
||||
continue |
||||
self.fields.append(field) |
||||
setattr(self, field, kwargs.get(field, wtypes.Unset)) |
||||
|
||||
@staticmethod |
||||
def _convert_with_links(chassis, url, expand=True): |
||||
if not expand: |
||||
chassis.unset_fields_except(['uuid', 'description']) |
||||
else: |
||||
chassis.nodes = [link.Link.make_link('self', |
||||
url, |
||||
'chassis', |
||||
chassis.uuid + "/nodes"), |
||||
link.Link.make_link('bookmark', |
||||
url, |
||||
'chassis', |
||||
chassis.uuid + "/nodes", |
||||
bookmark=True) |
||||
] |
||||
chassis.links = [link.Link.make_link('self', |
||||
url, |
||||
'chassis', chassis.uuid), |
||||
link.Link.make_link('bookmark', |
||||
url, |
||||
'chassis', chassis.uuid, |
||||
bookmark=True) |
||||
] |
||||
return chassis |
||||
|
||||
@classmethod |
||||
def convert_with_links(cls, rpc_chassis, expand=True): |
||||
chassis = Chassis(**rpc_chassis.as_dict()) |
||||
return cls._convert_with_links(chassis, pecan.request.host_url, |
||||
expand) |
||||
|
||||
@classmethod |
||||
def sample(cls, expand=True): |
||||
time = datetime.datetime(2000, 1, 1, 12, 0, 0) |
||||
sample = cls(uuid='eaaca217-e7d8-47b4-bb41-3f99f20eed89', extra={}, |
||||
description='Sample chassis', created_at=time, |
||||
updated_at=time) |
||||
return cls._convert_with_links(sample, 'http://localhost:6385', |
||||
expand) |
||||
|
||||
|
||||
class ChassisCollection(collection.Collection): |
||||
"""API representation of a collection of chassis.""" |
||||
|
||||
chassis = [Chassis] |
||||
"""A list containing chassis objects""" |
||||
|
||||
def __init__(self, **kwargs): |
||||
self._type = 'chassis' |
||||
|
||||
@staticmethod |
||||
def convert_with_links(chassis, limit, url=None, expand=False, **kwargs): |
||||
collection = ChassisCollection() |
||||
collection.chassis = [Chassis.convert_with_links(ch, expand) |
||||
for ch in chassis] |
||||
url = url or None |
||||
collection.next = collection.get_next(limit, url=url, **kwargs) |
||||
return collection |
||||
|
||||
@classmethod |
||||
def sample(cls, expand=True): |
||||
sample = cls() |
||||
sample.chassis = [Chassis.sample(expand=False)] |
||||
return sample |
||||
|
||||
|
||||
class ChassisController(rest.RestController): |
||||
"""REST controller for Chassis.""" |
||||
|
||||
nodes = node.NodesController() |
||||
"""Expose nodes as a sub-element of chassis""" |
||||
|
||||
# Set the flag to indicate that the requests to this resource are |
||||
# coming from a top-level resource |
||||
nodes.from_chassis = True |
||||
|
||||
_custom_actions = { |
||||
'detail': ['GET'], |
||||
} |
||||
|
||||
invalid_sort_key_list = ['extra'] |
||||
|
||||
def _get_chassis_collection(self, marker, limit, sort_key, sort_dir, |
||||
expand=False, resource_url=None): |
||||
limit = api_utils.validate_limit(limit) |
||||
sort_dir = api_utils.validate_sort_dir(sort_dir) |
||||
marker_obj = None |
||||
if marker: |
||||
marker_obj = objects.Chassis.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}) |
||||
|
||||
chassis = objects.Chassis.list(pecan.request.context, limit, |
||||
marker_obj, sort_key=sort_key, |
||||
sort_dir=sort_dir) |
||||
return ChassisCollection.convert_with_links(chassis, limit, |
||||
url=resource_url, |
||||
expand=expand, |
||||
sort_key=sort_key, |
||||
sort_dir=sort_dir) |
||||
|
||||
@expose.expose(ChassisCollection, types.uuid, |
||||
int, wtypes.text, wtypes.text) |
||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): |
||||
"""Retrieve a list of chassis. |
||||
|
||||
: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_chassis_collection(marker, limit, sort_key, sort_dir) |
||||
|
||||
@expose.expose(ChassisCollection, types.uuid, int, |
||||
wtypes.text, wtypes.text) |
||||
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): |
||||
"""Retrieve a list of chassis with detail. |
||||
|
||||
: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 against collections |
||||
parent = pecan.request.path.split('/')[:-1][-1] |
||||
if parent != "chassis": |
||||
raise exception.HTTPNotFound |
||||
|
||||
expand = True |
||||
resource_url = '/'.join(['chassis', 'detail']) |
||||
return self._get_chassis_collection(marker, limit, sort_key, sort_dir, |
||||
expand, resource_url) |
||||
|
||||
@expose.expose(Chassis, types.uuid) |
||||
def get_one(self, chassis_uuid): |
||||
"""Retrieve information about the given chassis. |
||||
|
||||
:param chassis_uuid: UUID of a chassis. |
||||
""" |
||||
rpc_chassis = objects.Chassis.get_by_uuid(pecan.request.context, |
||||
chassis_uuid) |
||||
return Chassis.convert_with_links(rpc_chassis) |
||||
|
||||
@expose.expose(Chassis, body=Chassis, status_code=201) |
||||
def post(self, chassis): |
||||
"""Create a new chassis. |
||||
|
||||
:param chassis: a chassis within the request body. |
||||
""" |
||||
new_chassis = objects.Chassis(pecan.request.context, |
||||
**chassis.as_dict()) |
||||
new_chassis.create() |
||||
# Set the HTTP Location Header |
||||
pecan.response.location = link.build_url('chassis', new_chassis.uuid) |
||||
return Chassis.convert_with_links(new_chassis) |
||||
|
||||
@wsme.validate(types.uuid, [ChassisPatchType]) |
||||
@expose.expose(Chassis, types.uuid, body=[ChassisPatchType]) |
||||
def patch(self, chassis_uuid, patch): |
||||
"""Update an existing chassis. |
||||
|
||||
:param chassis_uuid: UUID of a chassis. |
||||
:param patch: a json PATCH document to apply to this chassis. |
||||
""" |
||||
rpc_chassis = objects.Chassis.get_by_uuid(pecan.request.context, |
||||
chassis_uuid) |
||||
try: |
||||
chassis = Chassis(**api_utils.apply_jsonpatch( |
||||
rpc_chassis.as_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.Chassis.fields: |
||||
try: |
||||
patch_val = getattr(chassis, field) |
||||
except AttributeError: |
||||
# Ignore fields that aren't exposed in the API |
||||
continue |
||||
if patch_val == wtypes.Unset: |
||||
patch_val = None |
||||
if rpc_chassis[field] != patch_val: |
||||
rpc_chassis[field] = patch_val |
||||
|
||||
rpc_chassis.save() |
||||
return Chassis.convert_with_links(rpc_chassis) |
||||
|
||||
@expose.expose(None, types.uuid, status_code=204) |
||||
def delete(self, chassis_uuid): |
||||
"""Delete a chassis. |
||||
|
||||
:param chassis_uuid: UUID of a chassis. |
||||
""" |
||||
rpc_chassis = objects.Chassis.get_by_uuid(pecan.request.context, |
||||
chassis_uuid) |
||||
rpc_chassis.destroy() |
@ -1,48 +0,0 @@
|
||||
# Copyright 2013 Red Hat, 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. |
||||
|
||||
import pecan |
||||
from wsme import types as wtypes |
||||
|
||||
from iotronic.api.controllers import base |
||||
from iotronic.api.controllers import link |
||||
|
||||
|
||||
class Collection(base.APIBase): |
||||
|
||||
next = wtypes.text |
||||
"""A link to retrieve the next subset of the collection""" |
||||
|
||||
@property |
||||
def collection(self): |
||||
return getattr(self, self._type) |
||||
|
||||
def has_next(self, limit): |
||||
"""Return whether collection has more items.""" |
||||
return len(self.collection) and len(self.collection) == limit |
||||
|
||||
def get_next(self, limit, url=None, **kwargs): |
||||
"""Return a link to the next subset of the collection.""" |
||||
if not self.has_next(limit): |
||||
return wtypes.Unset |
||||
|
||||
resource_url = url or self._type |
||||
q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs]) |
||||
next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % { |
||||
'args': q_args, 'limit': limit, |
||||
'marker': self.collection[-1].uuid} |
||||
|
||||
return link.Link.make_link('next', pecan.request.host_url, |
||||
resource_url, next_args).href |
@ -1,210 +0,0 @@
|
||||
# Copyright 2013 Red Hat, 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. |
||||
|
||||
import pecan |
||||
from pecan import rest |
||||
import wsme |
||||
from wsme import types as wtypes |
||||
|
||||
from iotronic.api.controllers import base |
||||
from iotronic.api.controllers import link |
||||
from iotronic.api import expose |
||||
from iotronic.common import exception |
||||
from iotronic.common.i18n import _ |
||||
|
||||
|
||||
# Property information for drivers: |
||||
# key = driver name; |
||||
# value = dictionary of properties of that driver: |
||||
# key = property name. |
||||
# value = description of the property. |
||||
# NOTE(rloo). This is cached for the lifetime of the API service. If one or |
||||
# more conductor services are restarted with new driver versions, the API |
||||
# service should be restarted. |
||||
_DRIVER_PROPERTIES = {} |
||||
|
||||
# Vendor information for drivers: |
||||
# key = driver name; |
||||
# value = dictionary of vendor methods of that driver: |
||||
# key = method name. |
||||
# value = dictionary with the metadata of that method. |
||||
# NOTE(lucasagomes). This is cached for the lifetime of the API |
||||
# service. If one or more conductor services are restarted with new driver |
||||
# versions, the API service should be restarted. |
||||
_VENDOR_METHODS = {} |
||||
|
||||
|
||||
class Driver(base.APIBase): |
||||
"""API representation of a driver.""" |
||||
|
||||
name = wtypes.text |
||||
"""The name of the driver""" |
||||
|
||||
hosts = [wtypes.text] |
||||
"""A list of active conductors that support this driver""" |
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True) |
||||
"""A list containing self and bookmark links""" |
||||
|
||||
@staticmethod |
||||
def convert_with_links(name, hosts): |
||||
driver = Driver() |
||||
driver.name = name |
||||
driver.hosts = hosts |
||||
driver.links = [ |
||||
link.Link.make_link('self', |
||||
pecan.request.host_url, |
||||
'drivers', name), |
||||
link.Link.make_link('bookmark', |
||||
pecan.request.host_url, |
||||
'drivers', name, |
||||
bookmark=True) |
||||
] |
||||
return driver |
||||
|
||||
@classmethod |
||||
def sample(cls): |
||||
sample = cls(name="sample-driver", |
||||
hosts=["fake-host"]) |
||||
return sample |
||||
|
||||
|
||||
class DriverList(base.APIBase): |
||||
"""API representation of a list of drivers.""" |
||||
|
||||
drivers = [Driver] |
||||
"""A list containing drivers objects""" |
||||
|
||||
@staticmethod |
||||
def convert_with_links(drivers): |
||||
collection = DriverList() |
||||
collection.drivers = [ |
||||
Driver.convert_with_links(dname, list(drivers[dname])) |
||||
for dname in drivers] |
||||
return collection |
||||
|
||||
@classmethod |
||||
def sample(cls): |
||||
sample = cls() |
||||
sample.drivers = [Driver.sample()] |
||||
return sample |
||||
|
||||
|
||||
class DriverPassthruController(rest.RestController): |
||||
"""REST controller for driver passthru. |
||||
|
||||
This controller allow vendors to expose cross-node functionality in the |
||||
Iotronic API. Iotronic will merely relay the message from here to the specified |
||||
driver, no introspection will be made in the message body. |
||||
""" |
||||
|
||||
_custom_actions = { |
||||
'methods': ['GET'] |
||||
} |
||||
|
||||
@expose.expose(wtypes.text, wtypes.text) |
||||
def methods(self, driver_name): |
||||
"""Retrieve information about vendor methods of the given driver. |
||||
|
||||
:param driver_name: name of the driver. |
||||
:returns: dictionary with <vendor method name>:<method metadata> |
||||
entries. |
||||
:raises: DriverNotFound if the driver name is invalid or the |
||||
driver cannot be loaded. |
||||
""" |
||||
if driver_name not in _VENDOR_METHODS: |
||||
topic = pecan.request.rpcapi.get_topic_for_driver(driver_name) |
||||
ret = pecan.request.rpcapi.get_driver_vendor_passthru_methods( |
||||
pecan.request.context, driver_name, topic=topic) |
||||
_VENDOR_METHODS[driver_name] = ret |
||||
|
||||
return _VENDOR_METHODS[driver_name] |
||||
|
||||
@expose.expose(wtypes.text, wtypes.text, wtypes.text, |
||||
body=wtypes.text) |
||||
def _default(self, driver_name, method, data=None): |
||||
"""Call a driver API extension. |
||||
|
||||
:param driver_name: name of the driver to call. |
||||
:param method: name of the method, to be passed to the vendor |
||||
implementation. |
||||
:param data: body of data to supply to the specified method. |
||||
""" |
||||
if not method: |
||||
raise wsme.exc.ClientSideError(_("Method not specified")) |
||||
|
||||
if data is None: |
||||
data = {} |
||||
|
||||
http_method = pecan.request.method.upper() |
||||
topic = pecan.request.rpcapi.get_topic_for_driver(driver_name) |
||||
ret, is_async = pecan.request.rpcapi.driver_vendor_passthru( |
||||
pecan.request.context, driver_name, method, |
||||
http_method, data, topic=topic) |
||||
status_code = 202 if is_async else 200 |
||||
return wsme.api.Response(ret, status_code=status_code) |
||||
|
||||
|
||||
class DriversController(rest.RestController): |
||||
"""REST controller for Drivers.""" |
||||
|
||||
vendor_passthru = DriverPassthruController() |
||||
|
||||
_custom_actions = { |
||||
'properties': ['GET'], |
||||
} |
||||
|
||||
@expose.expose(DriverList) |
||||
def get_all(self): |
||||
"""Retrieve a list of drivers.""" |
||||
# FIXME(deva): formatting of the auto-generated REST API docs |
||||
# will break from a single-line doc string. |
||||
# This is a result of a bug in sphinxcontrib-pecanwsme |
||||
# https://github.com/dreamhost/sphinxcontrib-pecanwsme/issues/8 |
||||
driver_list = pecan.request.dbapi.get_active_driver_dict() |
||||
return DriverList.convert_with_links(driver_list) |
||||
|
||||
@expose.expose(Driver, wtypes.text) |
||||
def get_one(self, driver_name): |
||||
"""Retrieve a single driver.""" |
||||
# NOTE(russell_h): There is no way to make this more efficient than |
||||
# retrieving a list of drivers using the current sqlalchemy schema, but |
||||
# this path must be exposed for Pecan to route any paths we might |
||||
# choose to expose below it. |
||||
|
||||
driver_dict = pecan.request.dbapi.get_active_driver_dict() |
||||
for name, hosts in driver_dict.items(): |
||||
if name == driver_name: |
||||
return Driver.convert_with_links(name, list(hosts)) |
||||
|
||||
raise exception.DriverNotFound(driver_name=driver_name) |
||||
|
||||
@expose.expose(wtypes.text, wtypes.text) |
||||
def properties(self, driver_name): |
||||
"""Retrieve property information of the given driver. |
||||
|
||||
:param driver_name: name of the driver. |
||||
:returns: dictionary with <property name>:<property description> |
||||
entries. |
||||
:raises: DriverNotFound (HTTP 404) if the driver name is invalid or |
||||
the driver cannot be loaded. |
||||
""" |
||||
if driver_name not in _DRIVER_PROPERTIES: |
||||
topic = pecan.request.rpcapi.get_topic_for_driver(driver_name) |
||||
properties = pecan.request.rpcapi.get_driver_properties( |
||||
pecan.request.context, driver_name, topic=topic) |
||||
_DRIVER_PROPERTIES[driver_name] = properties |
||||
|
||||
return _DRIVER_PROPERTIES[driver_name] |
File diff suppressed because it is too large
Load Diff
@ -1,396 +0,0 @@
|
||||
# Copyright 2013 UnitedStack 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. |
||||
|
||||
import datetime |
||||
|
||||
from oslo_utils import uuidutils |
||||
import pecan |
||||
from pecan import rest |
||||
import wsme |
||||
from wsme import types as wtypes |
||||
|
||||
from iotronic.api.controllers import base |
||||
from iotronic.api.controllers import link |
||||
from iotronic.api.controllers.v1 import collection |
||||
from iotronic.api.controllers.v1 import types |
||||
from iotronic.api.controllers.v1 import utils as api_utils |
||||
from iotronic.api import expose |
||||
from iotronic.common import exception |
||||
from iotronic.common.i18n import _ |
||||
from iotronic import objects |
||||
|
||||
|
||||
class PortPatchType(types.JsonPatchType): |
||||
|
||||
@staticmethod |
||||
def mandatory_attrs(): |
||||
return ['/address', '/node_uuid'] |
||||
|
||||
|
||||
class Port(base.APIBase): |
||||
"""API representation of a port. |
||||
|
||||
This class enforces type checking and value constraints, and converts |
||||
between the internal object model and the API representation of a port. |
||||
""" |
||||
|
||||
_node_uuid = None |
||||
|
||||
def _get_node_uuid(self): |
||||
return self._node_uuid |
||||
|
||||
def _set_node_uuid(self, value): |
||||
if value and self._node_uuid != value: |
||||
try: |
||||
# FIXME(comstud): One should only allow UUID here, but |
||||
# there seems to be a bug in that tests are passing an |
||||
# ID. See bug #1301046 for more details. |
||||
node = objects.Node.get(pecan.request.context, value) |
||||
self._node_uuid = node.uuid |
||||
# NOTE(lucasagomes): Create the node_id attribute on-the-fly |
||||
# to satisfy the api -> rpc object |
||||
# conversion. |
||||
self.node_id = node.id |
||||
except exception.NodeNotFound as e: |
||||
# Change error code because 404 (NotFound) is inappropriate |
||||
# response for a POST request to create a Port |
||||
e.code = 400 # BadRequest |
||||
raise e |
||||
elif value == wtypes.Unset: |
||||
self._node_uuid = wtypes.Unset |
||||
|
||||
uuid = types.uuid |
||||
"""Unique UUID for this port""" |
||||
|
||||
address = wsme.wsattr(types.macaddress, mandatory=True) |
||||
"""MAC Address for this port""" |
||||
|
||||
extra = {wtypes.text: types.jsontype} |
||||
"""This port's meta data""" |
||||
|
||||
node_uuid = wsme.wsproperty(types.uuid, _get_node_uuid, _set_node_uuid, |
||||
mandatory=True) |
||||
"""The UUID of the node this port belongs to""" |
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True) |
||||
"""A list containing a self link and associated port links""" |
||||
|
||||
def __init__(self, **kwargs): |
||||
self.fields = [] |
||||
fields = list(objects.Port.fields) |
||||
# NOTE(lucasagomes): node_uuid is not part of objects.Port.fields |
||||
# because it's an API-only attribute |
||||
fields.append('node_uuid') |
||||
for field in fields: |
||||
# Skip fields we do not expose. |
||||
if not hasattr(self, field): |
||||
continue |
||||
self.fields.append(field) |
||||
setattr(self, field, kwargs.get(field, wtypes.Unset)) |
||||
|
||||
# NOTE(lucasagomes): node_id is an attribute created on-the-fly |
||||
# by _set_node_uuid(), it needs to be present in the fields so |
||||
# that as_dict() will contain node_id field when converting it |
||||
# before saving it in the database. |
||||
self.fields.append('node_id') |
||||
setattr(self, 'node_uuid', kwargs.get('node_id', wtypes.Unset)) |
||||
|
||||
@staticmethod |
||||
def _convert_with_links(port, url, expand=True): |
||||
if not expand: |
||||
port.unset_fields_except(['uuid', 'address']) |
||||
|
||||
# never expose the node_id attribute |
||||
port.node_id = wtypes.Unset |
||||
|
||||
port.links = [link.Link.make_link('self', url, |
||||
'ports', port.uuid), |
||||
link.Link.make_link('bookmark', url, |
||||
'ports', port.uuid, |
||||
bookmark=True) |
||||
] |
||||
return port |
||||
|
||||