Merge "New API v2.0"
This commit is contained in:
commit
169bf1316c
@ -14,7 +14,7 @@
|
||||
|
||||
import routes
|
||||
from muranoapi.openstack.common import wsgi
|
||||
from muranoapi.api.v1 import environments
|
||||
from muranoapi.api.v1 import environments, services
|
||||
from muranoapi.api.v1 import sessions
|
||||
from muranoapi.api.v1 import active_directories
|
||||
from muranoapi.api.v1 import webservers
|
||||
@ -29,6 +29,43 @@ class API(wsgi.Router):
|
||||
return cls(routes.Mapper())
|
||||
|
||||
def __init__(self, mapper):
|
||||
services_resource = services.create_resource()
|
||||
mapper.connect('/environments/{environment_id}/services',
|
||||
controller=services_resource,
|
||||
action='get',
|
||||
conditions={'method': ['GET']}, path='')
|
||||
mapper.connect('/environments/{environment_id}/services/{path:.*?}',
|
||||
controller=services_resource,
|
||||
action='get',
|
||||
conditions={'method': ['GET']}, path='')
|
||||
|
||||
mapper.connect('/environments/{environment_id}/services',
|
||||
controller=services_resource,
|
||||
action='post',
|
||||
conditions={'method': ['POST']}, path='')
|
||||
mapper.connect('/environments/{environment_id}/services/{path:.*?}',
|
||||
controller=services_resource,
|
||||
action='post',
|
||||
conditions={'method': ['POST']}, path='')
|
||||
|
||||
mapper.connect('/environments/{environment_id}/services',
|
||||
controller=services_resource,
|
||||
action='put',
|
||||
conditions={'method': ['PUT']}, path='')
|
||||
mapper.connect('/environments/{environment_id}/services/{path:.*?}',
|
||||
controller=services_resource,
|
||||
action='put',
|
||||
conditions={'method': ['PUT']}, path='')
|
||||
|
||||
mapper.connect('/environments/{environment_id}/services',
|
||||
controller=services_resource,
|
||||
action='delete',
|
||||
conditions={'method': ['DELETE']}, path='')
|
||||
mapper.connect('/environments/{environment_id}/services/{path:.*?}',
|
||||
controller=services_resource,
|
||||
action='delete',
|
||||
conditions={'method': ['DELETE']}, path='')
|
||||
|
||||
environments_resource = environments.create_resource()
|
||||
mapper.connect('/environments',
|
||||
controller=environments_resource,
|
||||
|
25
muranoapi/api/v1/schemas.py
Normal file
25
muranoapi/api/v1/schemas.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2013 Mirantis, 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.
|
||||
|
||||
#TODO:write detailed schema
|
||||
ENV_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"name": {"type": "string"}
|
||||
},
|
||||
"required": ["id"]
|
||||
}
|
85
muranoapi/api/v1/services.py
Normal file
85
muranoapi/api/v1/services.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright (c) 2013 Mirantis, 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 functools import wraps
|
||||
|
||||
from webob.exc import HTTPNotFound
|
||||
|
||||
from muranoapi import utils
|
||||
from muranoapi.db.services.core_services import CoreServices
|
||||
from muranoapi.openstack.common import wsgi
|
||||
from muranoapi.openstack.common import log as logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def normalize_path(f):
|
||||
@wraps(f)
|
||||
def f_normalize_path(*args, **kwargs):
|
||||
if 'path' in kwargs:
|
||||
if kwargs['path']:
|
||||
kwargs['path'] = '/services/' + kwargs['path']
|
||||
else:
|
||||
kwargs['path'] = '/services'
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return f_normalize_path
|
||||
|
||||
|
||||
class Controller(object):
|
||||
@normalize_path
|
||||
def get(self, request, environment_id, path):
|
||||
log.debug(_('Services:Get <EnvId: {0}>'.format(environment_id)))
|
||||
|
||||
session_id = None
|
||||
if hasattr(request, 'context') and request.context.session:
|
||||
session_id = request.context.session
|
||||
|
||||
try:
|
||||
result = CoreServices.get_data(environment_id, path, session_id)
|
||||
except (KeyError, ValueError):
|
||||
raise HTTPNotFound
|
||||
return result
|
||||
|
||||
@utils.verify_session
|
||||
@normalize_path
|
||||
def post(self, request, environment_id, path, body):
|
||||
log.debug(_('Services:Post <EnvId: {0}, '
|
||||
'Body: {1}>'.format(environment_id, body)))
|
||||
|
||||
post_data = CoreServices.post_data
|
||||
session_id = request.context.session
|
||||
try:
|
||||
result = post_data(environment_id, session_id, body, path)
|
||||
except (KeyError, ValueError):
|
||||
raise HTTPNotFound
|
||||
return result
|
||||
|
||||
@utils.verify_session
|
||||
@normalize_path
|
||||
def put(self, request, environment_id, path, body):
|
||||
log.debug(_('Services:Put <EnvId: {0}, '
|
||||
'Body: {1}>'.format(environment_id, body)))
|
||||
|
||||
put_data = CoreServices.put_data
|
||||
session_id = request.context.session
|
||||
|
||||
try:
|
||||
result = put_data(environment_id, session_id, body, path)
|
||||
except (KeyError, ValueError):
|
||||
raise HTTPNotFound
|
||||
return result
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(Controller())
|
@ -13,6 +13,8 @@
|
||||
# under the License.
|
||||
|
||||
import eventlet
|
||||
from jsonschema import validate
|
||||
from muranoapi.common.uuidutils import generate_uuid
|
||||
import types
|
||||
from collections import deque
|
||||
from functools import wraps
|
||||
@ -95,6 +97,17 @@ class TraverseHelper(object):
|
||||
node.append(value)
|
||||
|
||||
|
||||
def auto_id(value):
|
||||
if isinstance(value, types.DictionaryType):
|
||||
value['id'] = generate_uuid()
|
||||
for k, v in value.iteritems():
|
||||
value[k] = auto_id(v)
|
||||
if isinstance(value, types.ListType):
|
||||
for item in value:
|
||||
auto_id(item)
|
||||
return value
|
||||
|
||||
|
||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2):
|
||||
"""Retry calling the decorated function using an exponential backoff.
|
||||
|
||||
@ -151,3 +164,14 @@ def handle(f):
|
||||
log.exception(e)
|
||||
|
||||
return f_handle
|
||||
|
||||
|
||||
def validate_body(schema):
|
||||
def deco_validate_body(f):
|
||||
@wraps(f)
|
||||
def f_validate_body(*args, **kwargs):
|
||||
if 'body' in kwargs:
|
||||
validate(kwargs['body'], schema)
|
||||
return f(*args, **kwargs)
|
||||
return f_validate_body
|
||||
return deco_validate_body
|
||||
|
69
muranoapi/db/services/core_services.py
Normal file
69
muranoapi/db/services/core_services.py
Normal file
@ -0,0 +1,69 @@
|
||||
# Copyright (c) 2013 Mirantis, 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 muranoapi.common.utils import TraverseHelper, auto_id
|
||||
from muranoapi.db.services.environments import EnvironmentServices
|
||||
from muranoapi.openstack.common import timeutils
|
||||
|
||||
|
||||
class CoreServices(object):
|
||||
@staticmethod
|
||||
def get_data(environment_id, path, session_id=None):
|
||||
get_description = EnvironmentServices.get_environment_description
|
||||
|
||||
env_description = get_description(environment_id, session_id)
|
||||
|
||||
if not 'services' in env_description:
|
||||
return []
|
||||
|
||||
result = TraverseHelper.get(path, env_description)
|
||||
return result if result else []
|
||||
|
||||
@staticmethod
|
||||
def post_data(environment_id, session_id, data, path):
|
||||
get_description = EnvironmentServices.get_environment_description
|
||||
save_description = EnvironmentServices.save_environment_description
|
||||
|
||||
env_description = get_description(environment_id, session_id)
|
||||
if not 'services' in env_description:
|
||||
env_description['services'] = []
|
||||
|
||||
data = auto_id(data)
|
||||
|
||||
if path == '/services':
|
||||
data['created'] = str(timeutils.utcnow())
|
||||
data['updated'] = str(timeutils.utcnow())
|
||||
|
||||
for idx, unit in enumerate(data['units']):
|
||||
unit['name'] = data['name'] + '_instance_' + str(idx)
|
||||
|
||||
TraverseHelper.insert(path, data, env_description)
|
||||
save_description(session_id, env_description)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def put_data(environment_id, session_id, data, path):
|
||||
get_description = EnvironmentServices.get_environment_description
|
||||
save_description = EnvironmentServices.save_environment_description
|
||||
|
||||
env_description = get_description(environment_id, session_id)
|
||||
|
||||
TraverseHelper.update(path, data, env_description)
|
||||
if path == '/services':
|
||||
data['updated'] = str(timeutils.utcnow())
|
||||
|
||||
save_description(session_id, env_description)
|
||||
|
||||
return data
|
@ -16,6 +16,8 @@ from collections import namedtuple
|
||||
from amqplib.client_0_8 import Message
|
||||
import anyjson
|
||||
import eventlet
|
||||
from jsonschema import validate
|
||||
from muranoapi.api.v1.schemas import ENV_SCHEMA
|
||||
from muranoapi.common import config
|
||||
from muranoapi.db.models import Session, Environment
|
||||
from muranoapi.db.session import get_session
|
||||
@ -174,5 +176,6 @@ class EnvironmentServices(object):
|
||||
unit = get_session()
|
||||
session = unit.query(Session).get(session_id)
|
||||
|
||||
validate(environment, ENV_SCHEMA)
|
||||
session.description = environment
|
||||
session.save(unit)
|
||||
|
@ -11,7 +11,7 @@
|
||||
# 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 unittest
|
||||
import unittest2 as unittest
|
||||
from muranoapi.common.utils import TraverseHelper
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ class TraverseHelperTests(unittest.TestCase):
|
||||
def test_simple_root_get(self):
|
||||
source = {"attr": True}
|
||||
value = TraverseHelper.get('/', source)
|
||||
self.assertEqual(value, {"attr": True})
|
||||
self.assertDictEqual(value, {"attr": True})
|
||||
|
||||
def test_simple_attribute_get(self):
|
||||
source = {"attr": True}
|
||||
@ -61,10 +61,31 @@ class TraverseHelperTests(unittest.TestCase):
|
||||
source = {"attr": [1, 2, 3]}
|
||||
TraverseHelper.insert('/attr', 4, source)
|
||||
value = TraverseHelper.get('/attr', source)
|
||||
self.assertEqual(value, [1, 2, 3, 4])
|
||||
self.assertListEqual(value, [1, 2, 3, 4])
|
||||
|
||||
def test_adding_item_to_list(self):
|
||||
source = {"obj": {"attr": [1, 2, 3]}}
|
||||
TraverseHelper.insert('/obj/attr', 4, source)
|
||||
value = TraverseHelper.get('/obj/attr', source)
|
||||
self.assertEqual(value, [1, 2, 3, 4])
|
||||
self.assertListEqual(value, [1, 2, 3, 4])
|
||||
|
||||
@unittest.skip
|
||||
def test_simple_attribute_remove(self):
|
||||
source = {"attr1": False, "attr2": True}
|
||||
TraverseHelper.remove('/attr1', source)
|
||||
value = TraverseHelper.get('/', source)
|
||||
self.assertEqual(value, {"attr2": True})
|
||||
|
||||
@unittest.skip
|
||||
def test_nested_attribute_remove_from_object(self):
|
||||
source = {"obj": {"attr1": False, "attr2": True}}
|
||||
TraverseHelper.remove('/obj/attr1', source)
|
||||
value = TraverseHelper.get('/obj', source)
|
||||
self.assertDictEqual(value, {"attr2": True})
|
||||
|
||||
@unittest.skip
|
||||
def test_nested_attribute_remove_from_list(self):
|
||||
source = {"obj": [{"id": 'id1'}, {"id": 'id2'}]}
|
||||
TraverseHelper.remove('/obj/id1', source)
|
||||
value = TraverseHelper.get('/', source)
|
||||
self.assertListEqual(value, [{"id": 'id2'}])
|
||||
|
38
muranoapi/tests/common/utils_tests.py
Normal file
38
muranoapi/tests/common/utils_tests.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2013 Mirantis, 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 unittest2 as unittest
|
||||
from muranoapi.common.utils import auto_id
|
||||
|
||||
|
||||
class AutoIdTests(unittest.TestCase):
|
||||
def test_simple_dict(self):
|
||||
source = {"attr": True}
|
||||
value = auto_id(source)
|
||||
self.assertIn('id', value)
|
||||
|
||||
def test_nested_lists(self):
|
||||
source = {"attr": True, "obj": {"attr": False}}
|
||||
value = auto_id(source)
|
||||
self.assertIn('id', value)
|
||||
|
||||
def test_list_with_ints(self):
|
||||
source = [0, 1, 2, 3]
|
||||
value = auto_id(source)
|
||||
self.assertListEqual(value, source)
|
||||
|
||||
def test_list_with_dicts(self):
|
||||
source = [{"attr": True}, {"attr": False}]
|
||||
value = auto_id(source)
|
||||
for item in value:
|
||||
self.assertIn('id', item)
|
@ -18,8 +18,8 @@ from mock import MagicMock
|
||||
import muranoapi.api.v1.router as router
|
||||
|
||||
|
||||
def my_mock(link, controller, action, conditions):
|
||||
return [link, controller, action, conditions]
|
||||
def my_mock(link, controller, action, conditions, path=''):
|
||||
return [link, controller, action, conditions, path]
|
||||
|
||||
|
||||
def func_mock():
|
||||
|
Loading…
Reference in New Issue
Block a user