Add functional tests for execution
Partially implements: blueprint qinling-functional-tests Change-Id: I448fd148c6d046412a459ec933ca1e88ddfa9cadchanges/97/504997/4
parent
0b36d909d6
commit
4272c94a08
|
@ -72,7 +72,7 @@ function configure_qinling {
|
|||
iniset $QINLING_CONF_FILE oslo_policy policy_file $QINLING_POLICY_FILE
|
||||
iniset $QINLING_CONF_FILE DEFAULT debug $QINLING_DEBUG
|
||||
iniset $QINLING_CONF_FILE DEFAULT server all
|
||||
iniset $QINLING_CONF_FILE DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s %(resource)s"
|
||||
iniset $QINLING_CONF_FILE DEFAULT logging_context_format_string "%(asctime)s %(process)d %(color)s %(levelname)s [%(request_id)s] %(message)s %(resource)s (%(name)s)"
|
||||
iniset $QINLING_CONF_FILE storage file_system_dir $QINLING_FUNCTION_STORAGE_DIR
|
||||
iniset $QINLING_CONF_FILE kubernetes qinling_service_address $DEFAULT_HOST_IP
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ CONF = cfg.CONF
|
|||
class DefaultEngine(object):
|
||||
def __init__(self, orchestrator):
|
||||
self.orchestrator = orchestrator
|
||||
self.session = requests.Session()
|
||||
|
||||
def create_runtime(self, ctx, runtime_id):
|
||||
LOG.info('Start to create.',
|
||||
|
@ -102,7 +103,7 @@ class DefaultEngine(object):
|
|||
)
|
||||
|
||||
data = {'input': input, 'execution_id': execution_id}
|
||||
r = requests.post(func_url, json=data)
|
||||
r = self.session.post(func_url, json=data)
|
||||
res = r.json()
|
||||
|
||||
LOG.debug('Finished execution %s', execution_id)
|
||||
|
|
|
@ -20,6 +20,7 @@ import jinja2
|
|||
from kubernetes import client
|
||||
from oslo_log import log as logging
|
||||
import requests
|
||||
import six
|
||||
import tenacity
|
||||
import yaml
|
||||
|
||||
|
@ -56,6 +57,10 @@ class KubernetesManager(base.OrchestratorBase):
|
|||
self.service_template = jinja_env.get_template('service.j2')
|
||||
self.pod_template = jinja_env.get_template('pod.j2')
|
||||
|
||||
# Refer to
|
||||
# http://docs.python-requests.org/en/master/user/advanced/#session-objects
|
||||
self.session = requests.Session()
|
||||
|
||||
def _ensure_namespace(self):
|
||||
ret = self.v1.list_namespace()
|
||||
cur_names = [i.metadata.name for i in ret.items]
|
||||
|
@ -301,18 +306,24 @@ class KubernetesManager(base.OrchestratorBase):
|
|||
name, request_url, data
|
||||
)
|
||||
|
||||
# TODO(kong): Here we sleep some time to avoid 'Failed to establish a
|
||||
# new connection' error for some reason. Needs to find a better
|
||||
# solution.
|
||||
time.sleep(1)
|
||||
r = requests.post(request_url, json=data)
|
||||
exception = None
|
||||
for a in six.moves.xrange(10):
|
||||
try:
|
||||
r = self.session.post(request_url, json=data)
|
||||
if r.status_code != requests.codes.ok:
|
||||
raise exc.OrchestratorException(
|
||||
'Failed to download function code package.'
|
||||
)
|
||||
|
||||
if r.status_code != requests.codes.ok:
|
||||
raise exc.OrchestratorException(
|
||||
'Failed to download function code package.'
|
||||
)
|
||||
return name, pod_service_url
|
||||
except (requests.ConnectionError, requests.Timeout) as e:
|
||||
exception = e
|
||||
LOG.warning("Could not connect to service. Retrying.")
|
||||
time.sleep(1)
|
||||
|
||||
return name, pod_service_url
|
||||
raise exc.OrchestratorException(
|
||||
'Could not connect to service. Reason: %s', exception
|
||||
)
|
||||
|
||||
def _create_pod(self, image, pod_name, labels, input):
|
||||
pod_body = self.pod_template.render(
|
||||
|
@ -383,7 +394,7 @@ class KubernetesManager(base.OrchestratorBase):
|
|||
|
||||
LOG.info('Invoke function %s, url: %s', function_id, func_url)
|
||||
|
||||
r = requests.post(func_url, json=data)
|
||||
r = self.session.post(func_url, json=data)
|
||||
return r.json()
|
||||
else:
|
||||
status = None
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
# Copyright 2017 Catalyst IT Ltd
|
||||
#
|
||||
# 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 os
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import decorators
|
||||
|
||||
from qinling_tempest_plugin.tests import base
|
||||
|
||||
|
||||
# TODO(kong): Be careful that for k8s cluster, the default pool size is 3,
|
||||
# maybe we need customize that in future if there are many test cases but with
|
||||
# insufficient pods.
|
||||
class ExecutionsTest(base.BaseQinlingTest):
|
||||
name_prefix = 'ExecutionsTest'
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(ExecutionsTest, cls).resource_setup()
|
||||
|
||||
cls.runtime_id = None
|
||||
|
||||
# Create runtime for execution tests
|
||||
name = data_utils.rand_name('runtime', prefix=cls.name_prefix)
|
||||
_, body = cls.admin_client.create_runtime(
|
||||
'openstackqinling/python-runtime', name
|
||||
)
|
||||
cls.runtime_id = body['id']
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
if cls.runtime_id:
|
||||
cls.admin_client.delete_resource('runtimes', cls.runtime_id,
|
||||
ignore_notfound=True)
|
||||
|
||||
super(ExecutionsTest, cls).resource_cleanup()
|
||||
|
||||
def setUp(self):
|
||||
super(ExecutionsTest, self).setUp()
|
||||
|
||||
# Wait until runtime is available
|
||||
self.await_runtime_available(self.runtime_id)
|
||||
|
||||
python_file_path = os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
os.pardir,
|
||||
os.pardir,
|
||||
'functions/python_test.py'
|
||||
)
|
||||
)
|
||||
base_name, extention = os.path.splitext(python_file_path)
|
||||
self.base_name = os.path.basename(base_name)
|
||||
self.python_zip_file = os.path.join(
|
||||
tempfile.gettempdir(),
|
||||
'%s.zip' % self.base_name
|
||||
)
|
||||
|
||||
if not os.path.isfile(self.python_zip_file):
|
||||
zf = zipfile.ZipFile(self.python_zip_file, mode='w')
|
||||
try:
|
||||
# Use default compression mode, may change in future.
|
||||
zf.write(
|
||||
python_file_path,
|
||||
'%s%s' % (self.base_name, extention),
|
||||
compress_type=zipfile.ZIP_STORED
|
||||
)
|
||||
finally:
|
||||
zf.close()
|
||||
|
||||
# Create function
|
||||
function_name = data_utils.rand_name('function',
|
||||
prefix=self.name_prefix)
|
||||
with open(self.python_zip_file, 'rb') as package_data:
|
||||
resp, body = self.client.create_function(
|
||||
{"source": "package"},
|
||||
self.runtime_id,
|
||||
name=function_name,
|
||||
package_data=package_data,
|
||||
entry='%s.main' % self.base_name
|
||||
)
|
||||
self.function_id = body['id']
|
||||
|
||||
self.addCleanup(self.client.delete_resource, 'functions',
|
||||
self.function_id, ignore_notfound=True)
|
||||
|
||||
@decorators.idempotent_id('2a93fab0-2dae-4748-b0d4-f06b735ff451')
|
||||
def test_create_list_get_delete_execution(self):
|
||||
resp, body = self.client.create_execution(self.function_id,
|
||||
input={'name': 'Qinling'})
|
||||
|
||||
self.assertEqual(201, resp.status)
|
||||
self.assertEqual('success', body['status'])
|
||||
|
||||
execution_id = body['id']
|
||||
self.addCleanup(self.client.delete_resource, 'executions',
|
||||
execution_id, ignore_notfound=True)
|
||||
|
||||
# Get executions
|
||||
resp, body = self.client.get_resources('executions')
|
||||
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertIn(
|
||||
execution_id,
|
||||
[execution['id'] for execution in body['executions']]
|
||||
)
|
||||
|
||||
# Delete execution
|
||||
resp = self.client.delete_resource('executions', execution_id)
|
||||
|
||||
self.assertEqual(204, resp.status)
|
||||
|
||||
@decorators.idempotent_id('8096cc52-64d2-4660-a657-9ac0bdd743ae')
|
||||
def test_execution_async(self):
|
||||
resp, body = self.client.create_execution(self.function_id, sync=False)
|
||||
|
||||
self.assertEqual(201, resp.status)
|
||||
self.assertEqual('running', body['status'])
|
||||
|
||||
execution_id = body['id']
|
||||
self.addCleanup(self.client.delete_resource, 'executions',
|
||||
execution_id, ignore_notfound=True)
|
||||
|
||||
self.await_execution_success(execution_id)
|
||||
|
||||
@decorators.idempotent_id('6cb47b1d-a8c6-48f2-a92f-c4f613c33d1c')
|
||||
def test_execution_log(self):
|
||||
resp, body = self.client.create_execution(self.function_id,
|
||||
input={'name': 'OpenStack'})
|
||||
|
||||
self.assertEqual(201, resp.status)
|
||||
self.assertEqual('success', body['status'])
|
||||
|
||||
execution_id = body['id']
|
||||
self.addCleanup(self.client.delete_resource, 'executions',
|
||||
execution_id, ignore_notfound=True)
|
||||
|
||||
# Get execution log
|
||||
resp, body = self.client.get_execution_log(execution_id)
|
||||
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertIn('Hello, OpenStack', body)
|
|
@ -53,3 +53,14 @@ class BaseQinlingTest(test.BaseTestCase):
|
|||
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertEqual('available', body['status'])
|
||||
|
||||
@tenacity.retry(
|
||||
wait=tenacity.wait_fixed(3),
|
||||
stop=tenacity.stop_after_attempt(10),
|
||||
retry=tenacity.retry_if_exception_type(AssertionError)
|
||||
)
|
||||
def await_execution_success(self, id):
|
||||
resp, body = self.client.get_resource('executions', id)
|
||||
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertEqual('success', body['status'])
|
||||
|
|
|
@ -65,7 +65,6 @@ class BasicOpsTest(base.BaseQinlingTest):
|
|||
4. Check result and execution log.
|
||||
"""
|
||||
name = data_utils.rand_name('runtime', prefix=self.name_prefix)
|
||||
|
||||
resp, body = self.admin_client.create_runtime(
|
||||
'openstackqinling/python-runtime', name
|
||||
)
|
||||
|
@ -77,7 +76,7 @@ class BasicOpsTest(base.BaseQinlingTest):
|
|||
runtime_id = body['id']
|
||||
self.await_runtime_available(runtime_id)
|
||||
self.addCleanup(self.admin_client.delete_resource, 'runtimes',
|
||||
runtime_id)
|
||||
runtime_id, ignore_notfound=True)
|
||||
|
||||
# Create function
|
||||
function_name = data_utils.rand_name('function',
|
||||
|
@ -94,7 +93,7 @@ class BasicOpsTest(base.BaseQinlingTest):
|
|||
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.addCleanup(self.client.delete_resource, 'functions',
|
||||
function_id)
|
||||
function_id, ignore_notfound=True)
|
||||
|
||||
# Invoke function
|
||||
resp, body = self.client.create_execution(function_id,
|
||||
|
@ -105,7 +104,7 @@ class BasicOpsTest(base.BaseQinlingTest):
|
|||
|
||||
execution_id = body['id']
|
||||
self.addCleanup(self.client.delete_resource, 'executions',
|
||||
execution_id)
|
||||
execution_id, ignore_notfound=True)
|
||||
|
||||
# Get execution log
|
||||
resp, body = self.client.get_execution_log(execution_id)
|
||||
|
|
|
@ -23,12 +23,15 @@ function delete_resources(){
|
|||
openstack function delete $id
|
||||
done
|
||||
|
||||
# Delete runtimes
|
||||
ids=$(openstack runtime list -f yaml -c Id | awk '{print $3}')
|
||||
for id in $ids
|
||||
do
|
||||
openstack runtime delete $id
|
||||
done
|
||||
if [ "$1" = "admin" ]
|
||||
then
|
||||
# Delete runtimes
|
||||
ids=$(openstack runtime list -f yaml -c Id | awk '{print $3}')
|
||||
for id in $ids
|
||||
do
|
||||
openstack runtime delete $id
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
delete_resources
|
||||
|
|
Loading…
Reference in New Issue