Merge "New API v2.0"

This commit is contained in:
Jenkins 2013-07-12 10:14:08 +00:00 committed by Gerrit Code Review
commit 169bf1316c
9 changed files with 309 additions and 7 deletions

View File

@ -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,

View 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"]
}

View 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())

View File

@ -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

View 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

View File

@ -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)

View File

@ -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'}])

View 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)

View File

@ -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():