Add functional tests for execution
Partially implements: blueprint qinling-functional-tests Change-Id: I448fd148c6d046412a459ec933ca1e88ddfa9cad
This commit is contained in:
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 oslo_policy policy_file $QINLING_POLICY_FILE
|
||||||
iniset $QINLING_CONF_FILE DEFAULT debug $QINLING_DEBUG
|
iniset $QINLING_CONF_FILE DEFAULT debug $QINLING_DEBUG
|
||||||
iniset $QINLING_CONF_FILE DEFAULT server all
|
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 storage file_system_dir $QINLING_FUNCTION_STORAGE_DIR
|
||||||
iniset $QINLING_CONF_FILE kubernetes qinling_service_address $DEFAULT_HOST_IP
|
iniset $QINLING_CONF_FILE kubernetes qinling_service_address $DEFAULT_HOST_IP
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ CONF = cfg.CONF
|
||||||
class DefaultEngine(object):
|
class DefaultEngine(object):
|
||||||
def __init__(self, orchestrator):
|
def __init__(self, orchestrator):
|
||||||
self.orchestrator = orchestrator
|
self.orchestrator = orchestrator
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
def create_runtime(self, ctx, runtime_id):
|
def create_runtime(self, ctx, runtime_id):
|
||||||
LOG.info('Start to create.',
|
LOG.info('Start to create.',
|
||||||
|
@ -102,7 +103,7 @@ class DefaultEngine(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
data = {'input': input, 'execution_id': execution_id}
|
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()
|
res = r.json()
|
||||||
|
|
||||||
LOG.debug('Finished execution %s', execution_id)
|
LOG.debug('Finished execution %s', execution_id)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import jinja2
|
||||||
from kubernetes import client
|
from kubernetes import client
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import requests
|
import requests
|
||||||
|
import six
|
||||||
import tenacity
|
import tenacity
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
@ -56,6 +57,10 @@ class KubernetesManager(base.OrchestratorBase):
|
||||||
self.service_template = jinja_env.get_template('service.j2')
|
self.service_template = jinja_env.get_template('service.j2')
|
||||||
self.pod_template = jinja_env.get_template('pod.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):
|
def _ensure_namespace(self):
|
||||||
ret = self.v1.list_namespace()
|
ret = self.v1.list_namespace()
|
||||||
cur_names = [i.metadata.name for i in ret.items]
|
cur_names = [i.metadata.name for i in ret.items]
|
||||||
|
@ -301,18 +306,24 @@ class KubernetesManager(base.OrchestratorBase):
|
||||||
name, request_url, data
|
name, request_url, data
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO(kong): Here we sleep some time to avoid 'Failed to establish a
|
exception = None
|
||||||
# new connection' error for some reason. Needs to find a better
|
for a in six.moves.xrange(10):
|
||||||
# solution.
|
try:
|
||||||
time.sleep(1)
|
r = self.session.post(request_url, json=data)
|
||||||
r = requests.post(request_url, json=data)
|
|
||||||
|
|
||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
raise exc.OrchestratorException(
|
raise exc.OrchestratorException(
|
||||||
'Failed to download function code package.'
|
'Failed to download function code package.'
|
||||||
)
|
)
|
||||||
|
|
||||||
return name, pod_service_url
|
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)
|
||||||
|
|
||||||
|
raise exc.OrchestratorException(
|
||||||
|
'Could not connect to service. Reason: %s', exception
|
||||||
|
)
|
||||||
|
|
||||||
def _create_pod(self, image, pod_name, labels, input):
|
def _create_pod(self, image, pod_name, labels, input):
|
||||||
pod_body = self.pod_template.render(
|
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)
|
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()
|
return r.json()
|
||||||
else:
|
else:
|
||||||
status = None
|
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(200, resp.status)
|
||||||
self.assertEqual('available', body['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.
|
4. Check result and execution log.
|
||||||
"""
|
"""
|
||||||
name = data_utils.rand_name('runtime', prefix=self.name_prefix)
|
name = data_utils.rand_name('runtime', prefix=self.name_prefix)
|
||||||
|
|
||||||
resp, body = self.admin_client.create_runtime(
|
resp, body = self.admin_client.create_runtime(
|
||||||
'openstackqinling/python-runtime', name
|
'openstackqinling/python-runtime', name
|
||||||
)
|
)
|
||||||
|
@ -77,7 +76,7 @@ class BasicOpsTest(base.BaseQinlingTest):
|
||||||
runtime_id = body['id']
|
runtime_id = body['id']
|
||||||
self.await_runtime_available(runtime_id)
|
self.await_runtime_available(runtime_id)
|
||||||
self.addCleanup(self.admin_client.delete_resource, 'runtimes',
|
self.addCleanup(self.admin_client.delete_resource, 'runtimes',
|
||||||
runtime_id)
|
runtime_id, ignore_notfound=True)
|
||||||
|
|
||||||
# Create function
|
# Create function
|
||||||
function_name = data_utils.rand_name('function',
|
function_name = data_utils.rand_name('function',
|
||||||
|
@ -94,7 +93,7 @@ class BasicOpsTest(base.BaseQinlingTest):
|
||||||
|
|
||||||
self.assertEqual(201, resp.status_code)
|
self.assertEqual(201, resp.status_code)
|
||||||
self.addCleanup(self.client.delete_resource, 'functions',
|
self.addCleanup(self.client.delete_resource, 'functions',
|
||||||
function_id)
|
function_id, ignore_notfound=True)
|
||||||
|
|
||||||
# Invoke function
|
# Invoke function
|
||||||
resp, body = self.client.create_execution(function_id,
|
resp, body = self.client.create_execution(function_id,
|
||||||
|
@ -105,7 +104,7 @@ class BasicOpsTest(base.BaseQinlingTest):
|
||||||
|
|
||||||
execution_id = body['id']
|
execution_id = body['id']
|
||||||
self.addCleanup(self.client.delete_resource, 'executions',
|
self.addCleanup(self.client.delete_resource, 'executions',
|
||||||
execution_id)
|
execution_id, ignore_notfound=True)
|
||||||
|
|
||||||
# Get execution log
|
# Get execution log
|
||||||
resp, body = self.client.get_execution_log(execution_id)
|
resp, body = self.client.get_execution_log(execution_id)
|
||||||
|
|
|
@ -23,12 +23,15 @@ function delete_resources(){
|
||||||
openstack function delete $id
|
openstack function delete $id
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "$1" = "admin" ]
|
||||||
|
then
|
||||||
# Delete runtimes
|
# Delete runtimes
|
||||||
ids=$(openstack runtime list -f yaml -c Id | awk '{print $3}')
|
ids=$(openstack runtime list -f yaml -c Id | awk '{print $3}')
|
||||||
for id in $ids
|
for id in $ids
|
||||||
do
|
do
|
||||||
openstack runtime delete $id
|
openstack runtime delete $id
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_resources
|
delete_resources
|
||||||
|
|
Loading…
Reference in New Issue