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 import routes
from muranoapi.openstack.common import wsgi 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 sessions
from muranoapi.api.v1 import active_directories from muranoapi.api.v1 import active_directories
from muranoapi.api.v1 import webservers from muranoapi.api.v1 import webservers
@ -29,6 +29,43 @@ class API(wsgi.Router):
return cls(routes.Mapper()) return cls(routes.Mapper())
def __init__(self, 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() environments_resource = environments.create_resource()
mapper.connect('/environments', mapper.connect('/environments',
controller=environments_resource, 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. # under the License.
import eventlet import eventlet
from jsonschema import validate
from muranoapi.common.uuidutils import generate_uuid
import types import types
from collections import deque from collections import deque
from functools import wraps from functools import wraps
@ -95,6 +97,17 @@ class TraverseHelper(object):
node.append(value) 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): def retry(ExceptionToCheck, tries=4, delay=3, backoff=2):
"""Retry calling the decorated function using an exponential backoff. """Retry calling the decorated function using an exponential backoff.
@ -151,3 +164,14 @@ def handle(f):
log.exception(e) log.exception(e)
return f_handle 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 from amqplib.client_0_8 import Message
import anyjson import anyjson
import eventlet import eventlet
from jsonschema import validate
from muranoapi.api.v1.schemas import ENV_SCHEMA
from muranoapi.common import config from muranoapi.common import config
from muranoapi.db.models import Session, Environment from muranoapi.db.models import Session, Environment
from muranoapi.db.session import get_session from muranoapi.db.session import get_session
@ -174,5 +176,6 @@ class EnvironmentServices(object):
unit = get_session() unit = get_session()
session = unit.query(Session).get(session_id) session = unit.query(Session).get(session_id)
validate(environment, ENV_SCHEMA)
session.description = environment session.description = environment
session.save(unit) session.save(unit)

View File

@ -11,7 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest import unittest2 as unittest
from muranoapi.common.utils import TraverseHelper from muranoapi.common.utils import TraverseHelper
@ -19,7 +19,7 @@ class TraverseHelperTests(unittest.TestCase):
def test_simple_root_get(self): def test_simple_root_get(self):
source = {"attr": True} source = {"attr": True}
value = TraverseHelper.get('/', source) value = TraverseHelper.get('/', source)
self.assertEqual(value, {"attr": True}) self.assertDictEqual(value, {"attr": True})
def test_simple_attribute_get(self): def test_simple_attribute_get(self):
source = {"attr": True} source = {"attr": True}
@ -61,10 +61,31 @@ class TraverseHelperTests(unittest.TestCase):
source = {"attr": [1, 2, 3]} source = {"attr": [1, 2, 3]}
TraverseHelper.insert('/attr', 4, source) TraverseHelper.insert('/attr', 4, source)
value = TraverseHelper.get('/attr', 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): def test_adding_item_to_list(self):
source = {"obj": {"attr": [1, 2, 3]}} source = {"obj": {"attr": [1, 2, 3]}}
TraverseHelper.insert('/obj/attr', 4, source) TraverseHelper.insert('/obj/attr', 4, source)
value = TraverseHelper.get('/obj/attr', 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 import muranoapi.api.v1.router as router
def my_mock(link, controller, action, conditions): def my_mock(link, controller, action, conditions, path=''):
return [link, controller, action, conditions] return [link, controller, action, conditions, path]
def func_mock(): def func_mock():