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:
parent
1dfe0d6200
commit
5036e84c5a
33
magnum/api/controllers/common_types.py
Normal file
33
magnum/api/controllers/common_types.py
Normal 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')
|
@ -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))
|
||||||
|
0
magnum/api/controllers/v1/__init__.py
Normal file
0
magnum/api/controllers/v1/__init__.py
Normal file
198
magnum/api/controllers/v1/bay.py
Normal file
198
magnum/api/controllers/v1/bay.py
Normal 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'))
|
@ -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
|
|
0
magnum/api/controllers/v1/datamodel/__init__.py
Normal file
0
magnum/api/controllers/v1/datamodel/__init__.py
Normal file
108
magnum/api/controllers/v1/datamodel/types.py
Normal file
108
magnum/api/controllers/v1/datamodel/types.py
Normal 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)})
|
199
magnum/api/controllers/v1/pod.py
Normal file
199
magnum/api/controllers/v1/pod.py
Normal 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'))
|
72
magnum/api/controllers/v1/root.py
Normal file
72
magnum/api/controllers/v1/root.py
Normal 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)
|
199
magnum/api/controllers/v1/service.py
Normal file
199
magnum/api/controllers/v1/service.py
Normal 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'))
|
45
magnum/common/yamlutils.py
Normal file
45
magnum/common/yamlutils.py
Normal 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)
|
@ -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):
|
||||||
|
0
magnum/tests/common/__init__.py
Normal file
0
magnum/tests/common/__init__.py
Normal file
48
magnum/tests/common/test_yamlutils.py
Normal file
48
magnum/tests/common/test_yamlutils.py
Normal 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)
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user