Function Alias API: create and get

This patch supports function alias create and get API.

- Function alias creation
- Get function alias
- Get all the function aliases

Change-Id: I98539d5db5e6a125a760eebcd0472e712bc4765f
story: #2002143
task: #19982
This commit is contained in:
Dong Ma 2018-06-14 16:25:12 +00:00 committed by Lingxian Kong
parent 2dbfd7af74
commit 18f8b9dc96
7 changed files with 299 additions and 1 deletions

View File

@ -0,0 +1,113 @@
# Copyright 2018 OpenStack Foundation.
#
# 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 oslo_config import cfg
from oslo_log import log as logging
from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from qinling.api import access_control as acl
from qinling.api.controllers.v1 import resources
from qinling import context
from qinling.db import api as db_api
from qinling import exceptions as exc
from qinling.utils import rest_utils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
POST_REQUIRED = set(['name', 'function_id'])
class FunctionAliasesController(rest.RestController):
def __init__(self, *args, **kwargs):
self.type = 'function_alias'
super(FunctionAliasesController, self).__init__(*args, **kwargs)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(
resources.FunctionAlias,
body=resources.FunctionAlias,
status_code=201
)
def post(self, body):
"""Create a new alias for the specified function.
The supported body params:
- function_id: Required. Function id the alias points to.
- name: Required. Alias name, must be unique within the project.
- function_version: Optional. Version number the alias points to.
- description: Optional. The description of the new alias.
"""
ctx = context.get_ctx()
acl.enforce('function_alias:create', ctx)
params = body.to_dict()
if not POST_REQUIRED.issubset(set(params.keys())):
raise exc.InputException(
'Required param is missing. Required: %s' % POST_REQUIRED
)
LOG.info("Creating Alias, params: %s", params)
values = {
'function_id': params.get('function_id'),
'name': params.get('name'),
'function_version': params.get('function_version'),
'description': params.get('description'),
}
alias = db_api.create_function_alias(**values)
LOG.info("New alias created.")
return resources.FunctionAlias.from_db_obj(alias)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.FunctionAlias, wtypes.text)
def get(self, alias_name):
acl.enforce('function_alias:get', context.get_ctx())
LOG.info("Getting function alias %s.", alias_name)
alias = db_api.get_function_alias(alias_name)
return resources.FunctionAlias.from_db_obj(alias)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.FunctionAliases, bool, wtypes.text)
def get_all(self, all_projects=False, project_id=None):
"""Get all the function aliases.
:param project_id: Optional. Admin user can query other projects
resources, the param is ignored for normal user.
:param all_projects: Optional. Get resources of all projects.
"""
ctx = context.get_ctx()
project_id, all_projects = rest_utils.get_project_params(
project_id, all_projects
)
if all_projects:
acl.enforce('function_version:get_all:all_projects', ctx)
filters = rest_utils.get_filters(project_id=project_id)
LOG.info("Get all function aliases. filters=%s", filters)
db_aliases = db_api.get_function_aliases(
insecure=all_projects, **filters)
aliases = [resources.FunctionAlias.from_db_obj(db_model)
for db_model in db_aliases]
return resources.FunctionAliases(function_aliases=aliases)

View File

@ -388,3 +388,23 @@ class FunctionVersions(ResourceList):
self._type = 'function_versions'
super(FunctionVersions, self).__init__(**kwargs)
class FunctionAlias(Resource):
id = types.uuid
name = wtypes.text
description = wtypes.text
function_id = types.uuid
function_version = wsme.wsattr(int, default=0)
project_id = wsme.wsattr(wtypes.text, readonly=True)
created_at = wsme.wsattr(wtypes.text, readonly=True)
updated_at = wsme.wsattr(wtypes.text, readonly=True)
class FunctionAliases(ResourceList):
function_aliases = [FunctionAlias]
def __init__(self, **kwargs):
self._type = 'function_aliases'
super(FunctionAliases, self).__init__(**kwargs)

View File

@ -17,6 +17,7 @@ import wsmeext.pecan as wsme_pecan
from qinling.api.controllers.v1 import execution
from qinling.api.controllers.v1 import function
from qinling.api.controllers.v1 import function_alias
from qinling.api.controllers.v1 import job
from qinling.api.controllers.v1 import resources
from qinling.api.controllers.v1 import runtime
@ -38,6 +39,7 @@ class Controller(object):
executions = execution.ExecutionsController()
jobs = job.JobsController()
webhooks = webhook.WebhooksController()
aliases = function_alias.FunctionAliasesController()
@wsme_pecan.wsexpose(RootResource)
def index(self):

View File

@ -59,6 +59,7 @@ def delete_all():
delete_jobs(insecure=True)
delete_webhooks(insecure=True)
delete_executions(insecure=True)
delete_function_aliases(insecure=True)
delete_functions(insecure=True)
delete_runtimes(insecure=True)
@ -223,3 +224,20 @@ def delete_function_version(function_id, version):
def get_function_versions(**kwargs):
return IMPL.get_function_versions(**kwargs)
def create_function_alias(**kwargs):
return IMPL.create_function_alias(**kwargs)
def get_function_alias(name, **kwargs):
return IMPL.get_function_alias(name, **kwargs)
def get_function_aliases(**kwargs):
return IMPL.get_function_aliases(**kwargs)
# For unit test
def delete_function_aliases(**kwargs):
return IMPL.delete_function_aliases(**kwargs)

View File

@ -200,6 +200,13 @@ def _delete_all(model, insecure=None, **kwargs):
query.filter_by(**kwargs).delete(synchronize_session="fetch")
@db_base.insecure_aware()
def _get_db_object_by_name(model, name, insecure=None):
query = db_base.model_query(model) if insecure else _secure_query(model)
return query.filter_by(name=name).first()
@db_base.session_aware()
def conditional_update(model, values, expected_values, insecure=False,
filters=None, session=None):
@ -555,3 +562,44 @@ def delete_function_version(function_id, version, session=None):
@db_base.session_aware()
def get_function_versions(session=None, **kwargs):
return _get_collection_sorted_by_time(models.FunctionVersion, **kwargs)
@db_base.session_aware()
def create_function_alias(session=None, **kwargs):
alias = models.FunctionAlias()
alias.update(kwargs.copy())
try:
alias.save(session=session)
except oslo_db_exc.DBDuplicateEntry as e:
raise exc.DBError(
"Duplicate entry for function_aliases: %s" % e.columns
)
return alias
@db_base.insecure_aware()
@db_base.session_aware()
def get_function_alias(name, session=None, insecure=None):
alias = _get_db_object_by_name(models.FunctionAlias,
name,
insecure=insecure)
if not alias:
raise exc.DBEntityNotFoundError(
"FunctionAlias not found [name=%s]" %
(name)
)
return alias
@db_base.session_aware()
def get_function_aliases(session=None, **kwargs):
return _get_collection_sorted_by_time(models.FunctionAlias, **kwargs)
@db_base.session_aware()
def delete_function_aliases(session=None, **kwargs):
return _delete_all(models.FunctionAlias, **kwargs)

View File

@ -0,0 +1,98 @@
# Copyright 2018 OpenStack Foundation.
#
# 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 qinling import context
from qinling.db import api as db_api
from qinling.tests.unit.api import base
from qinling.tests.unit import base as unit_base
class TestFunctionAliasController(base.APITest):
def setUp(self):
super(TestFunctionAliasController, self).setUp()
self.db_func = self.create_function()
self.func_id = self.db_func.id
def test_post(self):
name = 'TestAlias'
body = {'function_id': self.func_id,
'name': name,
'description': 'new alias'}
resp = self.app.post_json('/v1/aliases', body)
self.assertEqual(201, resp.status_int)
self._assertDictContainsSubset(resp.json, body)
context.set_ctx(self.ctx)
func_alias_db = db_api.get_function_alias(name)
self.assertEqual(name, func_alias_db.name)
self.assertEqual(0, func_alias_db.function_version)
def test_post_without_required_params(self):
body = {}
resp = self.app.post_json('/v1/aliases',
body,
expect_errors=True)
self.assertEqual(400, resp.status_int)
self.assertIn("Required param is missing.", resp.json['faultstring'])
def test_get(self):
name = 'TestAlias'
function_version = 0
body = {'function_id': self.func_id,
'function_version': function_version,
'name': name,
'description': 'new alias'}
db_api.create_function_alias(**body)
resp = self.app.get('/v1/aliases/%s' % name)
context.set_ctx(self.ctx)
self.assertEqual(200, resp.status_int)
self.assertEqual("new alias", resp.json.get('description'))
def test_get_notfound(self):
resp = self.app.get('/v1/aliases/%s' % 'fake_name',
expect_errors=True)
self.assertEqual(404, resp.status_int)
self.assertIn("FunctionAlias not found", resp.json['faultstring'])
def test_get_all(self):
name = self.rand_name(name="alias", prefix=self.prefix)
body = {
'function_id': self.func_id,
'name': name
}
db_api.create_function_alias(**body)
resp = self.app.get('/v1/aliases')
self.assertEqual(200, resp.status_int)
expected = {
"name": name,
'function_id': self.func_id,
'function_version': 0,
"project_id": unit_base.DEFAULT_PROJECT_ID,
}
actual = self._assert_single_item(resp.json['function_aliases'],
name=name)
self._assertDictContainsSubset(actual, expected)

View File

@ -88,7 +88,6 @@ def get_filters(**params):
if data is not None:
if isinstance(data, six.string_types):
f_type, value = _extract_filter_type_and_value(data)
create_or_update_filter(column, value, f_type, filters)
else:
create_or_update_filter(column, data, _filter=filters)