Tidy up the ReST API

The ReST API needs 4 object types.  Create these as separate files.

Change-Id: I108fd6b8c81f4f94c5782b5eebe8c6aa998bec96
This commit is contained in:
Steven Dake 2014-11-21 11:31:05 -07:00 committed by Davanum Srinivas
parent 1dfe0d6200
commit 5036e84c5a
15 changed files with 989 additions and 43 deletions

View File

@ -0,0 +1,33 @@
# Copyright 2013 - Red Hat, 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 wsme import types as wtypes
Uri = wtypes.text
class Link(wtypes.Base):
"""A link representation."""
href = Uri
"The link URI."
target_name = wtypes.text
"Textual name of the target link."
@classmethod
def sample(cls):
return cls(href=('http://example.com:9777/v1'),
target_name='v1')

View File

@ -1,17 +1,54 @@
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License"); you may
# you may not use this file except in compliance with the License. # not use this file except in compliance with the License. You may obtain
# You may obtain a copy of the License at # a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# See the License for the specific language governing permissions and # License for the specific language governing permissions and limitations
# limitations under the License. # under the License.
from magnum.api.controllers import v1
import pecan
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from magnum.api.controllers import common_types
from magnum.api.controllers.v1 import root as v1_root
STATUS_KIND = wtypes.Enum(str, 'SUPPORTED', 'CURRENT', 'DEPRECATED')
class Version(wtypes.Base):
"""Version representation."""
id = wtypes.text
"The version identifier."
status = STATUS_KIND
"The status of the API (SUPPORTED, CURRENT or DEPRECATED)."
link = common_types.Link
"The link to the versioned API."
@classmethod
def sample(cls):
return cls(id='v1.0',
status='CURRENT',
link=common_types.Link(target_name='v1',
href='http://example.com:9511/v1'))
class RootController(object): class RootController(object):
v1 = v1.ContainerController()
v1 = v1_root.Controller()
@wsme_pecan.wsexpose([Version])
def index(self):
host_url = '%s/%s' % (pecan.request.host_url, 'v1')
Version(id='v1.0',
status='CURRENT',
link=common_types.Link(target_name='v1',
href=host_url))

View File

View File

@ -0,0 +1,198 @@
# 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 ast
import functools
import inspect
import uuid
from oslo.utils import strutils
from oslo.utils import timeutils
import pecan
from pecan import rest
import six
import wsme
from wsme import exc
from wsme import types as wtypes
from magnum.common import exception
from magnum.common import yamlutils
# NOTE(dims): We don't depend on oslo*i18n yet
_ = _LI = _LW = _LE = _LC = lambda x: x
state_kind = ["ok", "bays", "insufficient data"]
state_kind_enum = wtypes.Enum(str, *state_kind)
operation_kind = ('lt', 'le', 'eq', 'ne', 'ge', 'gt')
operation_kind_enum = wtypes.Enum(str, *operation_kind)
class _Base(wtypes.Base):
@classmethod
def from_db_model(cls, m):
return cls(**(m.as_dict()))
@classmethod
def from_db_and_links(cls, m, links):
return cls(links=links, **(m.as_dict()))
def as_dict(self, db_model):
valid_keys = inspect.getargspec(db_model.__init__)[0]
if 'self' in valid_keys:
valid_keys.remove('self')
return self.as_dict_from_keys(valid_keys)
def as_dict_from_keys(self, keys):
return dict((k, getattr(self, k))
for k in keys
if hasattr(self, k) and
getattr(self, k) != wsme.Unset)
class Query(_Base):
"""Query filter."""
# The data types supported by the query.
_supported_types = ['integer', 'float', 'string', 'boolean']
# Functions to convert the data field to the correct type.
_type_converters = {'integer': int,
'float': float,
'boolean': functools.partial(
strutils.bool_from_string, strict=True),
'string': six.text_type,
'datetime': timeutils.parse_isotime}
_op = None # provide a default
def get_op(self):
return self._op or 'eq'
def set_op(self, value):
self._op = value
field = wtypes.text
"The name of the field to test"
# op = wsme.wsattr(operation_kind, default='eq')
# this ^ doesn't seem to work.
op = wsme.wsproperty(operation_kind_enum, get_op, set_op)
"The comparison operator. Defaults to 'eq'."
value = wtypes.text
"The value to compare against the stored data"
type = wtypes.text
"The data type of value to compare against the stored data"
def __repr__(self):
# for logging calls
return '<Query %r %s %r %s>' % (self.field,
self.op,
self.value,
self.type)
@classmethod
def sample(cls):
return cls(field='resource_id',
op='eq',
value='bd9431c1-8d69-4ad3-803a-8d4a6b89fd36',
type='string'
)
def as_dict(self):
return self.as_dict_from_keys(['field', 'op', 'type', 'value'])
def _get_value_as_type(self, forced_type=None):
"""Convert metadata value to the specified data type.
"""
type = forced_type or self.type
try:
converted_value = self.value
if not type:
try:
converted_value = ast.literal_eval(self.value)
except (ValueError, SyntaxError):
# Unable to convert the metadata value automatically
# let it default to self.value
pass
else:
if type not in self._supported_types:
# Types must be explicitly declared so the
# correct type converter may be used. Subclasses
# of Query may define _supported_types and
# _type_converters to define their own types.
raise TypeError()
converted_value = self._type_converters[type](self.value)
except ValueError:
msg = (_('Unable to convert the value %(value)s'
' to the expected data type %(type)s.') %
{'value': self.value, 'type': type})
raise exc.ClientSideError(msg)
except TypeError:
msg = (_('The data type %(type)s is not supported. The supported'
' data type list is: %(supported)s') %
{'type': type, 'supported': self._supported_types})
raise exc.ClientSideError(msg)
except Exception:
msg = (_('Unexpected exception converting %(value)s to'
' the expected data type %(type)s.') %
{'value': self.value, 'type': type})
raise exc.ClientSideError(msg)
return converted_value
class BayController(rest.RestController):
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def get(self):
"""Retrieve a bay by UUID."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def put(self):
"""Create a new bay."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def delete(self):
"""Delete an existing bay."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
class Bay(_Base):
bay_id = wtypes.text
""" The ID of the bays."""
name = wsme.wsattr(wtypes.text, mandatory=True)
""" The name of the bay."""
desc = wsme.wsattr(wtypes.text, mandatory=True)
def __init__(self, **kwargs):
super(Bay, self).__init__(**kwargs)
@classmethod
def sample(cls):
return cls(id=str(uuid.uuid1(),
name="Docker",
desc='Docker Bays'))

View File

@ -23,7 +23,9 @@ import six
import wsme import wsme
from wsme import exc from wsme import exc
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from magnum.common import exception
from magnum.common import yamlutils
# NOTE(dims): We don't depend on oslo*i18n yet # NOTE(dims): We don't depend on oslo*i18n yet
@ -152,6 +154,32 @@ class Query(_Base):
return converted_value return converted_value
class ContainerController(rest.RestController):
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def get(self):
"""Retrieve a container by UUID."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def put(self):
"""Create a new container."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def delete(self):
"""Delete an existing container."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
class Container(_Base): class Container(_Base):
"""Controller Model.""" """Controller Model."""
@ -171,30 +199,3 @@ class Container(_Base):
return cls(id=str(uuid.uuid1(), return cls(id=str(uuid.uuid1(),
name="Docker", name="Docker",
desc='Docker Containers')) desc='Docker Containers'))
class ContainerController(rest.RestController):
@wsme_pecan.wsexpose([Container], [Query], int)
def get_all(self, q=None, limit=None):
# TODO(dims): Returns all the containers
pecan.response.status = 200
return
@wsme_pecan.wsexpose(Container, wtypes.text)
def get_one(self, container_id):
# TODO(dims): Returns specified container
pecan.response.status = 200
return
@wsme_pecan.wsexpose([Container], body=[Container])
def post(self, data):
# TODO(dims): Create a new container
pecan.response.status = 201
return
@wsme_pecan.wsexpose(None, status_code=204)
def delete(self):
# TODO(dims): DELETE this container
pecan.response.status = 204
return

View File

@ -0,0 +1,108 @@
# Copyright 2013 - Red Hat, 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.
import string
import wsme
from wsme import types as wtypes
from magnum.api.controllers import common_types
from magnum.openstack.common._i18n import _
class Base(wtypes.Base):
"""Base class for all API types."""
uri = common_types.Uri
"URI to the resource."
uuid = wtypes.text
"Unique Identifier of the resource"
def get_name(self):
return self.__name
def set_name(self, value):
allowed_chars = string.letters + string.digits + '-_'
for ch in value:
if ch not in allowed_chars:
raise ValueError(_('Names must only contain a-z,A-Z,0-9,-,_'))
self.__name = value
name = wtypes.wsproperty(str, get_name, set_name, mandatory=True)
"Name of the resource."
type = wtypes.text
"The resource type."
description = wtypes.text
"Textual description of the resource."
tags = [wtypes.text]
"Tags for the resource."
project_id = wtypes.text
"The project that this resource belongs in."
user_id = wtypes.text
"The user that owns this resource."
def __init__(self, **kwds):
self.__name = wsme.Unset
super(Base, self).__init__(**kwds)
@classmethod
def from_db_model(cls, m, host_url):
json = m.as_dict()
json['type'] = m.__tablename__
json['uri'] = '%s/v1/%s/%s' % (host_url, m.__resource__, m.uuid)
del json['id']
return cls(**(json))
def as_dict(self, db_model):
valid_keys = (attr for attr in db_model.__dict__.keys()
if attr[:2] != '__' and attr != 'as_dict')
return self.as_dict_from_keys(valid_keys)
def as_dict_from_keys(self, keys):
return dict((k, getattr(self, k))
for k in keys
if hasattr(self, k) and
getattr(self, k) != wsme.Unset)
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 wsme.types.text and isinstance(value, wsme.types.bytes):
value = value.decode()
if isinstance(value, t):
return value
else:
raise ValueError(
_("Wrong type. Expected '%(type)s', got '%(value)s'")
% {'type': self.types, 'value': type(value)})

View File

@ -0,0 +1,199 @@
# 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 ast
import functools
import inspect
import uuid
from oslo.utils import strutils
from oslo.utils import timeutils
import pecan
from pecan import rest
import six
import wsme
from wsme import exc
from wsme import types as wtypes
from magnum.common import exception
from magnum.common import yamlutils
# NOTE(dims): We don't depend on oslo*i18n yet
_ = _LI = _LW = _LE = _LC = lambda x: x
state_kind = ["ok", "pods", "insufficient data"]
state_kind_enum = wtypes.Enum(str, *state_kind)
operation_kind = ('lt', 'le', 'eq', 'ne', 'ge', 'gt')
operation_kind_enum = wtypes.Enum(str, *operation_kind)
class _Base(wtypes.Base):
@classmethod
def from_db_model(cls, m):
return cls(**(m.as_dict()))
@classmethod
def from_db_and_links(cls, m, links):
return cls(links=links, **(m.as_dict()))
def as_dict(self, db_model):
valid_keys = inspect.getargspec(db_model.__init__)[0]
if 'self' in valid_keys:
valid_keys.remove('self')
return self.as_dict_from_keys(valid_keys)
def as_dict_from_keys(self, keys):
return dict((k, getattr(self, k))
for k in keys
if hasattr(self, k) and
getattr(self, k) != wsme.Unset)
class Query(_Base):
"""Query filter."""
# The data types supported by the query.
_supported_types = ['integer', 'float', 'string', 'boolean']
# Functions to convert the data field to the correct type.
_type_converters = {'integer': int,
'float': float,
'boolean': functools.partial(
strutils.bool_from_string, strict=True),
'string': six.text_type,
'datetime': timeutils.parse_isotime}
_op = None # provide a default
def get_op(self):
return self._op or 'eq'
def set_op(self, value):
self._op = value
field = wtypes.text
"The name of the field to test"
# op = wsme.wsattr(operation_kind, default='eq')
# this ^ doesn't seem to work.
op = wsme.wsproperty(operation_kind_enum, get_op, set_op)
"The comparison operator. Defaults to 'eq'."
value = wtypes.text
"The value to compare against the stored data"
type = wtypes.text
"The data type of value to compare against the stored data"
def __repr__(self):
# for logging calls
return '<Query %r %s %r %s>' % (self.field,
self.op,
self.value,
self.type)
@classmethod
def sample(cls):
return cls(field='resource_id',
op='eq',
value='bd9431c1-8d69-4ad3-803a-8d4a6b89fd36',
type='string'
)
def as_dict(self):
return self.as_dict_from_keys(['field', 'op', 'type', 'value'])
def _get_value_as_type(self, forced_type=None):
"""Convert metadata value to the specified data type.
"""
type = forced_type or self.type
try:
converted_value = self.value
if not type:
try:
converted_value = ast.literal_eval(self.value)
except (ValueError, SyntaxError):
# Unable to convert the metadata value automatically
# let it default to self.value
pass
else:
if type not in self._supported_types:
# Types must be explicitly declared so the
# correct type converter may be used. Subclasses
# of Query may define _supported_types and
# _type_converters to define their own types.
raise TypeError()
converted_value = self._type_converters[type](self.value)
except ValueError:
msg = (_('Unable to convert the value %(value)s'
' to the expected data type %(type)s.') %
{'value': self.value, 'type': type})
raise exc.ClientSideError(msg)
except TypeError:
msg = (_('The data type %(type)s is not supported. The supported'
' data type list is: %(supported)s') %
{'type': type, 'supported': self._supported_types})
raise exc.ClientSideError(msg)
except Exception:
msg = (_('Unexpected exception converting %(value)s to'
' the expected data type %(type)s.') %
{'value': self.value, 'type': type})
raise exc.ClientSideError(msg)
return converted_value
class PodController(rest.RestController):
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def get(self):
"""Retrieve a pod by UUID."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def put(self):
"""Create a new pod."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def delete(self):
"""Delete an existing pod."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
class Pod(_Base):
pod_id = wtypes.text
""" The ID of the pods."""
name = wsme.wsattr(wtypes.text, mandatory=True)
""" The name of the pod."""
desc = wsme.wsattr(wtypes.text, mandatory=True)
def __init__(self, **kwargs):
super(Pod, self).__init__(**kwargs)
@classmethod
def sample(cls):
return cls(id=str(uuid.uuid1(),
name="Docker",
desc='Docker Pods'))

View File

@ -0,0 +1,72 @@
# 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
import wsmeext.pecan as wsme_pecan
from magnum.api.controllers import common_types
from magnum.api.controllers.v1 import bay
from magnum.api.controllers.v1 import container
from magnum.api.controllers.v1.datamodel import types as api_types
from magnum.api.controllers.v1 import pod
from magnum.api.controllers.v1 import service
from magnum.common import exception
from magnum import version
class Platform(api_types.Base):
bays_uri = common_types.Uri
"URI to Bays"
pods_uri = common_types.Uri
"URI to Pods"
services_uri = common_types.Uri
"URI to Services"
containers_uri = common_types.Uri
"URI to Services"
@classmethod
def sample(cls):
return cls(uri='http://example.com/v1',
name='magnum',
type='platform',
description='magnum native implementation',
bays_uri='http://example.com:9511/v1/bays',
pods_uri='http://example.com:9511/v1/pods',
services_uri='http://example.com:9511/v1/services',
containers_uri='http://example.com:9511/v1/containers')
class Controller(object):
"""Version 1 API Controller Root."""
bays = bay.BayController()
pods = pod.PodController()
services = service.ServiceController()
containers = container.ContainerController()
@exception.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(Platform)
def index(self):
host_url = '%s/%s' % (pecan.request.host_url, 'v1')
return Platform(uri=host_url,
name='magnum',
type='platform',
description='magnum native implementation',
implementation_version=version.version_string(),
bays_uri='%s/bays' % host_url,
pods_uri='%s/pods' % host_url,
services_uri='%s/services' % host_url,
containers_uri='%s/containers' % host_url)

View File

@ -0,0 +1,199 @@
# 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 ast
import functools
import inspect
import uuid
from oslo.utils import strutils
from oslo.utils import timeutils
import pecan
from pecan import rest
import six
import wsme
from wsme import exc
from wsme import types as wtypes
from magnum.common import exception
from magnum.common import yamlutils
# NOTE(dims): We don't depend on oslo*i18n yet
_ = _LI = _LW = _LE = _LC = lambda x: x
state_kind = ["ok", "services", "insufficient data"]
state_kind_enum = wtypes.Enum(str, *state_kind)
operation_kind = ('lt', 'le', 'eq', 'ne', 'ge', 'gt')
operation_kind_enum = wtypes.Enum(str, *operation_kind)
class _Base(wtypes.Base):
@classmethod
def from_db_model(cls, m):
return cls(**(m.as_dict()))
@classmethod
def from_db_and_links(cls, m, links):
return cls(links=links, **(m.as_dict()))
def as_dict(self, db_model):
valid_keys = inspect.getargspec(db_model.__init__)[0]
if 'self' in valid_keys:
valid_keys.remove('self')
return self.as_dict_from_keys(valid_keys)
def as_dict_from_keys(self, keys):
return dict((k, getattr(self, k))
for k in keys
if hasattr(self, k) and
getattr(self, k) != wsme.Unset)
class Query(_Base):
"""Query filter."""
# The data types supported by the query.
_supported_types = ['integer', 'float', 'string', 'boolean']
# Functions to convert the data field to the correct type.
_type_converters = {'integer': int,
'float': float,
'boolean': functools.partial(
strutils.bool_from_string, strict=True),
'string': six.text_type,
'datetime': timeutils.parse_isotime}
_op = None # provide a default
def get_op(self):
return self._op or 'eq'
def set_op(self, value):
self._op = value
field = wtypes.text
"The name of the field to test"
# op = wsme.wsattr(operation_kind, default='eq')
# this ^ doesn't seem to work.
op = wsme.wsproperty(operation_kind_enum, get_op, set_op)
"The comparison operator. Defaults to 'eq'."
value = wtypes.text
"The value to compare against the stored data"
type = wtypes.text
"The data type of value to compare against the stored data"
def __repr__(self):
# for logging calls
return '<Query %r %s %r %s>' % (self.field,
self.op,
self.value,
self.type)
@classmethod
def sample(cls):
return cls(field='resource_id',
op='eq',
value='bd9431c1-8d69-4ad3-803a-8d4a6b89fd36',
type='string'
)
def as_dict(self):
return self.as_dict_from_keys(['field', 'op', 'type', 'value'])
def _get_value_as_type(self, forced_type=None):
"""Convert metadata value to the specified data type.
"""
type = forced_type or self.type
try:
converted_value = self.value
if not type:
try:
converted_value = ast.literal_eval(self.value)
except (ValueError, SyntaxError):
# Unable to convert the metadata value automatically
# let it default to self.value
pass
else:
if type not in self._supported_types:
# Types must be explicitly declared so the
# correct type converter may be used. Subclasses
# of Query may define _supported_types and
# _type_converters to define their own types.
raise TypeError()
converted_value = self._type_converters[type](self.value)
except ValueError:
msg = (_('Unable to convert the value %(value)s'
' to the expected data type %(type)s.') %
{'value': self.value, 'type': type})
raise exc.ClientSideError(msg)
except TypeError:
msg = (_('The data type %(type)s is not supported. The supported'
' data type list is: %(supported)s') %
{'type': type, 'supported': self._supported_types})
raise exc.ClientSideError(msg)
except Exception:
msg = (_('Unexpected exception converting %(value)s to'
' the expected data type %(type)s.') %
{'value': self.value, 'type': type})
raise exc.ClientSideError(msg)
return converted_value
class ServiceController(rest.RestController):
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def get(self):
"""Retrieve a service by UUID."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def put(self):
"""Create a new service."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
@exception.wrap_pecan_controller_exception
@pecan.expose(content_type='application/x-yaml')
def delete(self):
"""Delete an existing service."""
res_yaml = yamlutils.dump({'dummy_data'})
pecan.response.status = 200
return res_yaml
class Service(_Base):
service_id = wtypes.text
""" The ID of the services."""
name = wsme.wsattr(wtypes.text, mandatory=True)
""" The name of the service."""
desc = wsme.wsattr(wtypes.text, mandatory=True)
def __init__(self, **kwargs):
super(Service, self).__init__(**kwargs)
@classmethod
def sample(cls):
return cls(id=str(uuid.uuid1(),
name="Docker",
desc='Docker Services'))

View File

@ -0,0 +1,45 @@
# 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 yaml
if hasattr(yaml, 'CSafeLoader'):
yaml_loader = yaml.CSafeLoader
else:
yaml_loader = yaml.SafeLoader
if hasattr(yaml, 'CSafeDumper'):
yaml_dumper = yaml.CSafeDumper
else:
yaml_dumper = yaml.SafeDumper
def load(s):
try:
yml_dict = yaml.load(s, yaml_loader)
except yaml.YAMLError as exc:
msg = 'An error occurred during YAML parsing.'
if hasattr(exc, 'problem_mark'):
msg += ' Error position: (%s:%s)' % (exc.problem_mark.line + 1,
exc.problem_mark.column + 1)
raise ValueError(msg)
if not isinstance(yml_dict, dict) and not isinstance(yml_dict, list):
raise ValueError('The source is not a YAML mapping or list.')
if isinstance(yml_dict, dict) and len(yml_dict) < 1:
raise ValueError('Could not find any element in your YAML mapping.')
return yml_dict
def dump(s):
return yaml.dump(s, Dumper=yaml_dumper)

View File

@ -15,7 +15,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from oslo.config import cfg
from oslotest import base from oslotest import base
import testscenarios
class BaseTestCase(testscenarios.WithScenarios, base.BaseTestCase):
"""Test base class."""
def setUp(self):
super(BaseTestCase, self).setUp()
self.addCleanup(cfg.CONF.reset)
class TestCase(base.BaseTestCase): class TestCase(base.BaseTestCase):

View File

View File

@ -0,0 +1,48 @@
# 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 mock
import yaml
from magnum.common import yamlutils
from magnum.tests import base
class TestYamlUtils(base.BaseTestCase):
def setUp(self):
super(TestYamlUtils, self).setUp()
def test_load_yaml(self):
yml_dict = yamlutils.load('a: x\nb: y\n')
self.assertEqual(yml_dict, {'a': 'x', 'b': 'y'})
def test_load_empty_yaml(self):
self.assertRaises(ValueError, yamlutils.load, '{}')
def test_load_empty_list(self):
yml_dict = yamlutils.load('[]')
self.assertEqual(yml_dict, [])
def test_load_invalid_yaml_syntax(self):
self.assertRaises(ValueError, yamlutils.load, "}invalid: y'm'l3!")
def test_load_invalid_yaml_type(self):
self.assertRaises(ValueError, yamlutils.load, 'invalid yaml type')
@mock.patch('magnum.common.yamlutils.yaml.dump')
def test_dump_yaml(self, dump):
if hasattr(yaml, 'CSafeDumper'):
yaml_dumper = yaml.CSafeDumper
else:
yaml_dumper = yaml.SafeDumper
yamlutils.dump('version: 1')
dump.assert_called_with('version: 1', Dumper=yaml_dumper)

View File

@ -15,10 +15,6 @@ from magnum import tests
class TestRootController(tests.FunctionalTest): class TestRootController(tests.FunctionalTest):
def test_get_all(self):
response = self.app.get('/v1/containers')
assert response.status_int == 200
def test_get_not_found(self): def test_get_not_found(self):
response = self.app.get('/a/bogus/url', expect_errors=True) response = self.app.get('/a/bogus/url', expect_errors=True)
assert response.status_int == 404 assert response.status_int == 404