Browse Source

Functional test for function version detach/get

- Only admin user can detach function version.
- Admin user has read access to user's function version.

Change-Id: I345a2aa17d7131984038f99cdd6e6f246bec1d24
Story: 2001829
Task: 14456
changes/60/564960/6
Lingxian Kong 3 years ago
parent
commit
19cd85e818
6 changed files with 165 additions and 15 deletions
  1. +6
    -0
      qinling/api/controllers/v1/function.py
  2. +13
    -3
      qinling/api/controllers/v1/function_version.py
  3. +11
    -3
      qinling/db/sqlalchemy/api.py
  4. +39
    -6
      qinling_tempest_plugin/services/qinling_client.py
  5. +79
    -0
      qinling_tempest_plugin/tests/api/test_function_versions.py
  6. +17
    -3
      qinling_tempest_plugin/tests/api/test_functions.py

+ 6
- 0
qinling/api/controllers/v1/function.py View File

@ -100,7 +100,13 @@ class FunctionsController(rest.RestController):
@rest_utils.wrap_pecan_controller_exception
@pecan.expose()
@pecan.expose('json')
def get(self, id):
"""Get function information or download function package.
This method can support HTTP request using either
'Accept:application/json' or no 'Accept' header.
"""
LOG.info("Get function %s.", id)
download = strutils.bool_from_string(


+ 13
- 3
qinling/api/controllers/v1/function_version.py View File

@ -65,7 +65,7 @@ class FunctionVersionsController(rest.RestController):
with db_api.transaction():
# Get latest function package md5 and version number
func_db = db_api.get_function(function_id)
func_db = db_api.get_function(function_id, insecure=False)
if func_db.code['source'] != constants.PACKAGE_FUNCTION:
raise exc.NotAllowedException(
"Function versioning only allowed for %s type "
@ -140,8 +140,12 @@ class FunctionVersionsController(rest.RestController):
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.FunctionVersions, types.uuid)
def get_all(self, function_id):
"""Get all the versions of the given function.
Admin user can get all versions for the normal user's function.
"""
acl.enforce('function_version:get_all', context.get_ctx())
LOG.info("Getting function versions for function %s.", function_id)
LOG.info("Getting versions for function %s.", function_id)
# Getting function and versions needs to happen in a db transaction
with db_api.transaction():
@ -155,7 +159,13 @@ class FunctionVersionsController(rest.RestController):
@rest_utils.wrap_pecan_controller_exception
@pecan.expose()
@pecan.expose('json')
def get(self, function_id, version):
"""Get function version or download function version package.
This method can support HTTP request using either
'Accept:application/json' or no 'Accept' header.
"""
ctx = context.get_ctx()
acl.enforce('function_version:get', ctx)
@ -175,7 +185,7 @@ class FunctionVersionsController(rest.RestController):
LOG.info("Downloading version %s for function %s.", version,
function_id)
f = self.storage_provider.retrieve(ctx.projectid, function_id,
f = self.storage_provider.retrieve(version_db.project_id, function_id,
None, version=version)
if isinstance(f, collections.Iterable):


+ 11
- 3
qinling/db/sqlalchemy/api.py View File

@ -516,10 +516,18 @@ def increase_function_version(function_id, old_version, session=None,
return version
@db_base.insecure_aware()
@db_base.session_aware()
def get_function_version(function_id, version, session=None):
version_db = _secure_query(models.FunctionVersion).filter_by(
function_id=function_id, version_number=version).first()
def get_function_version(function_id, version, session=None, insecure=None):
if insecure:
query = db_base.model_query(models.FunctionVersion)
else:
query = _secure_query(models.FunctionVersion)
version_db = query.filter_by(
function_id=function_id, version_number=version
).first()
if not version_db:
raise exc.DBEntityNotFoundError(
"FunctionVersion not found [function_id=%s, version_number=%s]" %


+ 39
- 6
qinling_tempest_plugin/services/qinling_client.py View File

@ -106,14 +106,25 @@ class QinlingClient(client_base.QinlingClientBase):
return resp, json.loads(resp.text)
def get_function(self, function_id):
resp, body = self.get(
'/v1/functions/{id}'.format(id=function_id),
)
return resp, json.loads(body)
def download_function(self, function_id):
return self.get('/v1/functions/%s?download=true' % function_id,
headers={})
def detach_function(self, function_id):
return self.post('/v1/functions/%s/detach' % function_id,
None,
headers={})
def detach_function(self, function_id, version=0):
if version == 0:
url = '/v1/functions/%s/detach' % function_id
else:
url = '/v1/functions/%s/versions/%s/detach' % \
(function_id, version)
return self.post(url, None, headers={})
def create_execution(self, function_id, input=None, sync=True, version=0):
req_body = {
@ -130,8 +141,16 @@ class QinlingClient(client_base.QinlingClientBase):
return self.get('/v1/executions/%s/log' % execution_id,
headers={'Accept': 'text/plain'})
def get_function_workers(self, function_id):
return self.get_resources('functions/%s/workers' % function_id)
def get_function_workers(self, function_id, version=0):
q_params = None
if version > 0:
q_params = "/?function_version=%s" % version
url = 'functions/%s/workers' % function_id
if q_params:
url += q_params
return self.get_resources(url)
def create_webhook(self, function_id, version=0):
req_body = {"function_id": function_id, "function_version": version}
@ -174,3 +193,17 @@ class QinlingClient(client_base.QinlingClientBase):
pass
else:
raise
def get_function_version(self, function_id, version):
resp, body = self.get(
'/v1/functions/%s/versions/%s' % (function_id, version),
)
return resp, json.loads(body)
def get_function_versions(self, function_id):
resp, body = self.get(
'/v1/functions/%s/versions' % (function_id),
)
return resp, json.loads(body)

+ 79
- 0
qinling_tempest_plugin/tests/api/test_function_versions.py View File

@ -13,6 +13,7 @@
# limitations under the License.
from tempest.lib import decorators
from tempest.lib import exceptions
import tenacity
from qinling_tempest_plugin.tests import base
@ -70,3 +71,81 @@ class FunctionVersionsTest(base.BaseQinlingTest):
numbers = [v['version_number'] for v in body['function_versions']]
self.assertIn(version_1, numbers)
self.assertIn(version_2, numbers)
@decorators.idempotent_id('3f735ed4-64b0-4ec3-8bf2-507e38dcea19')
def test_create_admin_not_allowed(self):
"""test_create_admin_not_allowed
Even admin user can not create function version for normal user's
function.
"""
function_id = self.create_function()
self.assertRaises(
exceptions.NotFound,
self.admin_client.create_function_version,
function_id
)
@decorators.idempotent_id('43c06f41-d116-43a7-a61c-115f7591b22e')
def test_get_by_admin(self):
"""Admin user can get normal user's function version."""
function_id = self.create_function()
version = self.create_function_version(function_id)
resp, body = self.admin_client.get_function_version(function_id,
version)
self.assertEqual(200, resp.status)
self.assertEqual(version, body.get("version_number"))
@decorators.idempotent_id('e6b865d8-ffa8-4cfc-8afb-820c64f9b2af')
def test_get_all_by_admin(self):
"""Admin user can list normal user's function version."""
function_id = self.create_function()
version = self.create_function_version(function_id)
resp, body = self.admin_client.get_function_versions(function_id)
self.assertEqual(200, resp.status)
self.assertIn(
version,
[v['version_number'] for v in body['function_versions']]
)
@decorators.idempotent_id('7898f89f-a490-42a3-8cf7-63cbd9543a06')
def test_detach(self):
"""Admin only operation."""
function_id = self.create_function()
version = self.create_function_version(function_id)
# Create execution to allocate worker
resp, _ = self.client.create_execution(
function_id, input='{"name": "Qinling"}', version=version
)
self.assertEqual(201, resp.status)
resp, body = self.admin_client.get_function_workers(function_id,
version=version)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(body['workers']))
# Detach function version from workers
resp, _ = self.admin_client.detach_function(function_id,
version=version)
self.assertEqual(202, resp.status)
def _assert_workers():
resp, body = self.admin_client.get_function_workers(
function_id,
version=version
)
self.assertEqual(200, resp.status)
self.assertEqual(0, len(body['workers']))
r = tenacity.Retrying(
wait=tenacity.wait_fixed(1),
stop=tenacity.stop_after_attempt(5),
retry=tenacity.retry_if_exception_type(AssertionError)
)
r.call(_assert_workers)

+ 17
- 3
qinling_tempest_plugin/tests/api/test_functions.py View File

@ -68,12 +68,27 @@ class FunctionsTest(base.BaseQinlingTest):
self.assertEqual(400, resp.status_code)
@decorators.idempotent_id('f8dde7fc-fbcc-495c-9b39-70666b7d3f64')
def test_get_by_admin(self):
"""test_get_by_admin
Admin user can get the function by directly specifying the function id.
"""
function_id = self.create_function(self.python_zip_file)
resp, body = self.admin_client.get_function(function_id)
self.assertEqual(200, resp.status)
self.assertEqual(function_id, body['id'])
@decorators.idempotent_id('051f3106-df01-4fcd-a0a3-c81c99653163')
def test_get_all_admin(self):
# Create function by normal user
"""test_get_all_admin
Admin user needs to specify filters to get all the functions.
"""
function_id = self.create_function(self.python_zip_file)
# Get functions by admin
resp, body = self.admin_client.get_resources('functions')
self.assertEqual(200, resp.status)
@ -82,7 +97,6 @@ class FunctionsTest(base.BaseQinlingTest):
[function['id'] for function in body['functions']]
)
# Get other projects functions by admin
resp, body = self.admin_client.get_resources(
'functions?all_projects=true'
)


Loading…
Cancel
Save