Fix docker image function

- Make docker image function work, add functional tests
- Use 'result' instead of 'output' in execution response
- Support string as execution input
- Update python runtime

Partially implements: blueprint qinling-functional-tests
Change-Id: Ie7e59983cfbc6f9e8514438e30a854f372a4c4d7
This commit is contained in:
Lingxian Kong 2017-12-19 10:28:31 +13:00
parent 11558af9ad
commit aa1469da68
17 changed files with 176 additions and 77 deletions

View File

@ -24,8 +24,10 @@ function install_k8s {
source tools/gate/setup_gate.sh source tools/gate/setup_gate.sh
popd popd
# Pre-pull the default docker image for python runtime # Pre-pull the default docker image for python runtime and image function
# test.
sudo docker pull $QINLING_PYTHON_RUNTIME_IMAGE sudo docker pull $QINLING_PYTHON_RUNTIME_IMAGE
sudo docker pull openstackqinling/alpine-test
} }

View File

@ -165,15 +165,19 @@ class FunctionsController(rest.RestController):
) )
store = False store = False
if values['code']['source'] == constants.PACKAGE_FUNCTION: create_trust = True
if source == constants.PACKAGE_FUNCTION:
store = True store = True
data = kwargs['package'].file.read() data = kwargs['package'].file.read()
elif values['code']['source'] == constants.SWIFT_FUNCTION: elif source == constants.SWIFT_FUNCTION:
swift_info = values['code'].get('swift', {}) swift_info = values['code'].get('swift', {})
self._check_swift(swift_info.get('container'), self._check_swift(swift_info.get('container'),
swift_info.get('object')) swift_info.get('object'))
else:
create_trust = False
values['entry'] = None
if cfg.CONF.pecan.auth_enable: if cfg.CONF.pecan.auth_enable and create_trust:
try: try:
values['trust_id'] = keystone_util.create_trust().id values['trust_id'] = keystone_util.create_trust().id
LOG.debug('Trust %s created', values['trust_id']) LOG.debug('Trust %s created', values['trust_id'])
@ -187,7 +191,6 @@ class FunctionsController(rest.RestController):
if store: if store:
ctx = context.get_ctx() ctx = context.get_ctx()
self.storage_provider.store( self.storage_provider.store(
ctx.projectid, ctx.projectid,
func_db.id, func_db.id,
@ -219,7 +222,6 @@ class FunctionsController(rest.RestController):
project_id=project_id, project_id=project_id,
) )
LOG.info("Get all %ss. filters=%s", self.type, filters) LOG.info("Get all %ss. filters=%s", self.type, filters)
db_functions = db_api.get_functions(insecure=all_projects, **filters) db_functions = db_api.get_functions(insecure=all_projects, **filters)
functions = [resources.Function.from_dict(db_model.to_dict()) functions = [resources.Function.from_dict(db_model.to_dict())
for db_model in db_functions] for db_model in db_functions]

View File

@ -279,12 +279,28 @@ class Execution(Resource):
description = wtypes.text description = wtypes.text
status = wsme.wsattr(wtypes.text, readonly=True) status = wsme.wsattr(wtypes.text, readonly=True)
sync = bool sync = bool
input = types.jsontype input = wtypes.text
output = wsme.wsattr(types.jsontype, readonly=True) result = wsme.wsattr(types.jsontype, readonly=True)
project_id = wsme.wsattr(wtypes.text, readonly=True) project_id = wsme.wsattr(wtypes.text, readonly=True)
created_at = wsme.wsattr(wtypes.text, readonly=True) created_at = wsme.wsattr(wtypes.text, readonly=True)
updated_at = wsme.wsattr(wtypes.text, readonly=True) updated_at = wsme.wsattr(wtypes.text, readonly=True)
@classmethod
def from_dict(cls, d):
obj = cls()
for key, val in d.items():
if key == 'input' and val:
if val.get('__function_input'):
setattr(obj, key, val.get('__function_input'))
else:
setattr(obj, key, json.dumps(val))
continue
if hasattr(obj, key):
setattr(obj, key, val)
return obj
@classmethod @classmethod
def sample(cls): def sample(cls):
return cls( return cls(
@ -294,7 +310,7 @@ class Execution(Resource):
status='success', status='success',
sync=True, sync=True,
input={'data': 'hello, world'}, input={'data': 'hello, world'},
output={'result': 'hello, world'}, result={'result': 'hello, world'},
project_id='default', project_id='default',
created_at='1970-01-01T00:00:00.000000', created_at='1970-01-01T00:00:00.000000',
updated_at='1970-01-01T00:00:00.000000' updated_at='1970-01-01T00:00:00.000000'

View File

@ -71,7 +71,7 @@ def upgrade():
sa.Column('memory_size', sa.Integer, nullable=True), sa.Column('memory_size', sa.Integer, nullable=True),
sa.Column('timeout', sa.Integer, nullable=True), sa.Column('timeout', sa.Integer, nullable=True),
sa.Column('code', st.JsonLongDictType(), nullable=False), sa.Column('code', st.JsonLongDictType(), nullable=False),
sa.Column('entry', sa.String(length=80), nullable=False), sa.Column('entry', sa.String(length=80), nullable=True),
sa.Column('count', sa.Integer, nullable=False), sa.Column('count', sa.Integer, nullable=False),
sa.Column('trust_id', sa.String(length=80), nullable=True), sa.Column('trust_id', sa.String(length=80), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
@ -90,7 +90,7 @@ def upgrade():
sa.Column('status', sa.String(length=32), nullable=False), sa.Column('status', sa.String(length=32), nullable=False),
sa.Column('sync', sa.BOOLEAN, nullable=False), sa.Column('sync', sa.BOOLEAN, nullable=False),
sa.Column('input', st.JsonLongDictType(), nullable=True), sa.Column('input', st.JsonLongDictType(), nullable=True),
sa.Column('output', st.JsonLongDictType(), nullable=True), sa.Column('result', st.JsonLongDictType(), nullable=True),
sa.Column('logs', sa.Text(), nullable=True), sa.Column('logs', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
info={"check_ifexists": True} info={"check_ifexists": True}

View File

@ -40,12 +40,12 @@ class Function(model_base.QinlingSecureModelBase):
) )
# We want to get runtime info when we query function # We want to get runtime info when we query function
runtime = relationship( runtime = relationship(
'Runtime', back_populates="functions", innerjoin=True, lazy='joined' 'Runtime', back_populates="functions", innerjoin=True, lazy='select'
) )
memory_size = sa.Column(sa.Integer) memory_size = sa.Column(sa.Integer)
timeout = sa.Column(sa.Integer) timeout = sa.Column(sa.Integer)
code = sa.Column(st.JsonLongDictType(), nullable=False) code = sa.Column(st.JsonLongDictType(), nullable=False)
entry = sa.Column(sa.String(80), nullable=False) entry = sa.Column(sa.String(80), nullable=True)
count = sa.Column(sa.Integer, default=0) count = sa.Column(sa.Integer, default=0)
trust_id = sa.Column(sa.String(80)) trust_id = sa.Column(sa.String(80))
@ -57,7 +57,7 @@ class Execution(model_base.QinlingSecureModelBase):
status = sa.Column(sa.String(32), nullable=False) status = sa.Column(sa.String(32), nullable=False)
sync = sa.Column(sa.BOOLEAN, default=True) sync = sa.Column(sa.BOOLEAN, default=True)
input = sa.Column(st.JsonLongDictType()) input = sa.Column(st.JsonLongDictType())
output = sa.Column(st.JsonLongDictType()) result = sa.Column(st.JsonLongDictType())
description = sa.Column(sa.String(255)) description = sa.Column(sa.String(255))
logs = sa.Column(sa.Text(), nullable=True) logs = sa.Column(sa.Text(), nullable=True)

View File

@ -157,7 +157,7 @@ class DefaultEngine(object):
{ {
'status': status.SUCCESS if success else status.FAILED, 'status': status.SUCCESS if success else status.FAILED,
'logs': res.pop('logs', ''), 'logs': res.pop('logs', ''),
'output': res 'result': res
} }
) )
return return
@ -196,7 +196,7 @@ class DefaultEngine(object):
logs = res.pop('logs', '') logs = res.pop('logs', '')
success = success and res.pop('success') success = success and res.pop('success')
else: else:
# If the function is created from docker image, the output is # If the function is created from docker image, the result is
# direct output, here we convert to a dict to fit into the db # direct output, here we convert to a dict to fit into the db
# schema. # schema.
res = {'output': res} res = {'output': res}
@ -210,7 +210,7 @@ class DefaultEngine(object):
{ {
'status': status.SUCCESS if success else status.FAILED, 'status': status.SUCCESS if success else status.FAILED,
'logs': logs, 'logs': logs,
'output': res 'result': res
} }
) )

View File

@ -35,22 +35,18 @@ class EngineService(service.Service):
def start(self): def start(self):
orchestrator = orchestra_base.load_orchestrator(CONF) orchestrator = orchestra_base.load_orchestrator(CONF)
db_api.setup_db() db_api.setup_db()
LOG.info('Starting periodic tasks...')
periodics.start_function_mapping_handler(orchestrator)
topic = CONF.engine.topic topic = CONF.engine.topic
server = CONF.engine.host server = CONF.engine.host
transport = messaging.get_rpc_transport(CONF) transport = messaging.get_rpc_transport(CONF)
target = messaging.Target(topic=topic, server=server, fanout=False) target = messaging.Target(topic=topic, server=server, fanout=False)
endpoints = [engine.DefaultEngine(orchestrator)] endpoint = engine.DefaultEngine(orchestrator)
access_policy = dispatcher.DefaultRPCAccessPolicy access_policy = dispatcher.DefaultRPCAccessPolicy
self.server = messaging.get_rpc_server( self.server = messaging.get_rpc_server(
transport, transport,
target, target,
endpoints, [endpoint],
executor='eventlet', executor='eventlet',
access_policy=access_policy, access_policy=access_policy,
serializer=rpc.ContextSerializer( serializer=rpc.ContextSerializer(
@ -58,6 +54,9 @@ class EngineService(service.Service):
) )
) )
LOG.info('Starting function mapping periodic task...')
periodics.start_function_mapping_handler(endpoint)
LOG.info('Starting engine...') LOG.info('Starting engine...')
self.server.start() self.server.start()

View File

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
import copy import copy
import json
import os import os
import time import time
@ -315,12 +316,19 @@ class KubernetesManager(base.OrchestratorBase):
return pod_name, pod_service_url return pod_name, pod_service_url
def _create_pod(self, image, pod_name, labels, input): def _create_pod(self, image, pod_name, labels, input):
if not input:
input_list = []
elif input.get('__function_input'):
input_list = input.get('__function_input').split()
else:
input_list = [json.dumps(input)]
pod_body = self.pod_template.render( pod_body = self.pod_template.render(
{ {
"pod_name": pod_name, "pod_name": pod_name,
"labels": labels, "labels": labels,
"pod_image": image, "pod_image": image,
"input": input "input": input_list
} }
) )
@ -386,6 +394,7 @@ class KubernetesManager(base.OrchestratorBase):
def run_execution(self, execution_id, function_id, input=None, def run_execution(self, execution_id, function_id, input=None,
identifier=None, service_url=None, entry='main.main', identifier=None, service_url=None, entry='main.main',
trust_id=None): trust_id=None):
"""Run execution and get output."""
if service_url: if service_url:
func_url = '%s/execute' % service_url func_url = '%s/execute' % service_url
data = utils.get_request_data( data = utils.get_request_data(
@ -398,24 +407,34 @@ class KubernetesManager(base.OrchestratorBase):
return utils.url_request(self.session, func_url, body=data) return utils.url_request(self.session, func_url, body=data)
else: else:
status = None def _wait_complete():
# Wait for execution to be finished.
# TODO(kong): Do not retry infinitely.
while status != 'Succeeded':
pod = self.v1.read_namespaced_pod( pod = self.v1.read_namespaced_pod(
identifier, identifier,
self.conf.kubernetes.namespace self.conf.kubernetes.namespace
) )
status = pod.status.phase status = pod.status.phase
time.sleep(0.5) return True if status == 'Succeeded' else False
try:
r = tenacity.Retrying(
wait=tenacity.wait_fixed(1),
stop=tenacity.stop_after_delay(180),
retry=tenacity.retry_if_result(
lambda result: result is False)
)
r.call(_wait_complete)
except Exception as e:
LOG.exception(
"Failed to get pod output, pod: %s, error: %s",
identifier, str(e)
)
return False, {'error': 'Function execution failed.'}
output = self.v1.read_namespaced_pod_log( output = self.v1.read_namespaced_pod_log(
identifier, identifier,
self.conf.kubernetes.namespace, self.conf.kubernetes.namespace,
) )
return True, output
return output
def delete_function(self, function_id, labels=None): def delete_function(self, function_id, labels=None):
selector = common.convert_dict_to_string(labels) selector = common.convert_dict_to_string(labels)

View File

@ -36,9 +36,13 @@ CONF = cfg.CONF
_periodic_tasks = {} _periodic_tasks = {}
def handle_function_service_expiration(ctx, engine_client, orchestrator): def handle_function_service_expiration(ctx, engine):
context.set_ctx(ctx) """Clean up resources related to expired functions.
If it's image function, we will rely on the orchestrator itself to do the
image clean up, e.g. image collection feature in kubernetes.
"""
context.set_ctx(ctx)
delta = timedelta(seconds=CONF.engine.function_service_expiration) delta = timedelta(seconds=CONF.engine.function_service_expiration)
expiry_time = datetime.utcnow() - delta expiry_time = datetime.utcnow() - delta
@ -60,8 +64,7 @@ def handle_function_service_expiration(ctx, engine_client, orchestrator):
) )
# Delete resources related to the function # Delete resources related to the function
engine_client.delete_function(func_db.id) engine.delete_function(func_db.id)
# Delete etcd keys # Delete etcd keys
etcd_util.delete_function(func_db.id) etcd_util.delete_function(func_db.id)
@ -144,16 +147,13 @@ def handle_job(engine_client):
context.set_ctx(None) context.set_ctx(None)
def start_function_mapping_handler(orchestrator): def start_function_mapping_handler(engine):
tg = threadgroup.ThreadGroup(1) tg = threadgroup.ThreadGroup(1)
engine_client = rpc.get_engine_client()
tg.add_timer( tg.add_timer(
300, 300,
handle_function_service_expiration, handle_function_service_expiration,
ctx=context.Context(), ctx=context.Context(),
engine_client=engine_client, engine=engine,
orchestrator=orchestrator
) )
_periodic_tasks[constants.PERIODIC_FUNC_MAPPING_HANDLER] = tg _periodic_tasks[constants.PERIODIC_FUNC_MAPPING_HANDLER] = tg

View File

@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
from oslo_log import log as logging from oslo_log import log as logging
@ -23,13 +24,14 @@ LOG = logging.getLogger(__name__)
def _update_function_db(function_id): def _update_function_db(function_id):
# NOTE(kong): Store function info in cache? with db_api.transaction():
func_db = db_api.get_function(function_id) # NOTE(kong): Store function info in cache?
runtime_db = func_db.runtime func_db = db_api.get_function(function_id)
if runtime_db and runtime_db.status != status.AVAILABLE: runtime_db = func_db.runtime
raise exc.RuntimeNotAvailableException( if runtime_db and runtime_db.status != status.AVAILABLE:
'Runtime %s is not available.' % func_db.runtime_id raise exc.RuntimeNotAvailableException(
) 'Runtime %s is not available.' % func_db.runtime_id
)
# Function update is done using UPDATE ... FROM ... WHERE # Function update is done using UPDATE ... FROM ... WHERE
# non-locking clause. # non-locking clause.
@ -59,6 +61,12 @@ def _update_function_db(function_id):
def create_execution(engine_client, params): def create_execution(engine_client, params):
function_id = params['function_id'] function_id = params['function_id']
is_sync = params.get('sync', True) is_sync = params.get('sync', True)
input = params.get('input')
if input:
try:
params['input'] = json.loads(input)
except ValueError:
params['input'] = {'__function_input': input}
runtime_id = _update_function_db(function_id) runtime_id = _update_function_db(function_id)

View File

@ -0,0 +1,17 @@
# 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.
def main(name, **kwargs):
return 'Hello, %s' % name

View File

@ -88,7 +88,7 @@ class ExecutionsTest(base.BaseQinlingTest):
self._create_function() self._create_function()
resp, body = self.client.create_execution(self.function_id, resp, body = self.client.create_execution(self.function_id,
input={'name': 'Qinling'}) input='{"name": "Qinling"}')
self.assertEqual(201, resp.status) self.assertEqual(201, resp.status)
@ -117,8 +117,9 @@ class ExecutionsTest(base.BaseQinlingTest):
"""Admin user can get executions of other projects""" """Admin user can get executions of other projects"""
self._create_function() self._create_function()
resp, body = self.client.create_execution(self.function_id, resp, body = self.client.create_execution(
input={'name': 'Qinling'}) self.function_id, input='{"name": "Qinling"}'
)
self.assertEqual(201, resp.status) self.assertEqual(201, resp.status)
execution_id = body['id'] execution_id = body['id']
@ -160,15 +161,14 @@ class ExecutionsTest(base.BaseQinlingTest):
execution_id, ignore_notfound=True) execution_id, ignore_notfound=True)
self.assertEqual('running', body['status']) self.assertEqual('running', body['status'])
self.await_execution_success(execution_id) self.await_execution_success(execution_id)
@decorators.idempotent_id('6cb47b1d-a8c6-48f2-a92f-c4f613c33d1c') @decorators.idempotent_id('6cb47b1d-a8c6-48f2-a92f-c4f613c33d1c')
def test_execution_log(self): def test_execution_log(self):
self._create_function() self._create_function()
resp, body = self.client.create_execution(
resp, body = self.client.create_execution(self.function_id, self.function_id, input='{"name": "OpenStack"}'
input={'name': 'OpenStack'}) )
self.assertEqual(201, resp.status) self.assertEqual(201, resp.status)
self.addCleanup(self.client.delete_resource, 'executions', self.addCleanup(self.client.delete_resource, 'executions',
@ -237,6 +237,20 @@ class ExecutionsTest(base.BaseQinlingTest):
self.assertEqual(200, resp.status) self.assertEqual(200, resp.status)
self.assertEqual(2, len(body['workers'])) self.assertEqual(2, len(body['workers']))
@decorators.idempotent_id('ccfe67ce-e467-11e7-916c-00224d6b7bc1')
def test_python_execution_positional_args(self):
self._create_function(name='test_python_positional_args.py')
resp, body = self.client.create_execution(self.function_id,
input='Qinling')
self.assertEqual(201, resp.status)
self.addCleanup(self.client.delete_resource, 'executions',
body['id'], ignore_notfound=True)
self.assertEqual('success', body['status'])
result = jsonutils.loads(body['result'])
self.assertIn('Qinling', result['output'])
@decorators.idempotent_id('a948382a-84af-4f0e-ad08-4297345e302c') @decorators.idempotent_id('a948382a-84af-4f0e-ad08-4297345e302c')
def test_python_execution_file_limit(self): def test_python_execution_file_limit(self):
self._create_function(name='test_python_file_limit.py') self._create_function(name='test_python_file_limit.py')
@ -248,9 +262,9 @@ class ExecutionsTest(base.BaseQinlingTest):
body['id'], ignore_notfound=True) body['id'], ignore_notfound=True)
self.assertEqual('failed', body['status']) self.assertEqual('failed', body['status'])
output = jsonutils.loads(body['output']) result = jsonutils.loads(body['result'])
self.assertIn( self.assertIn(
'Too many open files', output['output'] 'Too many open files', result['output']
) )
@decorators.idempotent_id('bf6f8f35-fa88-469b-8878-7aa85a8ce5ab') @decorators.idempotent_id('bf6f8f35-fa88-469b-8878-7aa85a8ce5ab')
@ -264,7 +278,20 @@ class ExecutionsTest(base.BaseQinlingTest):
body['id'], ignore_notfound=True) body['id'], ignore_notfound=True)
self.assertEqual('failed', body['status']) self.assertEqual('failed', body['status'])
output = jsonutils.loads(body['output']) result = jsonutils.loads(body['result'])
self.assertIn( self.assertIn(
'too much resource consumption', output['output'] 'too much resource consumption', result['output']
) )
@decorators.idempotent_id('d0598868-e45d-11e7-9125-00224d6b7bc1')
def test_execution_image_function(self):
function_id = self.create_function(image=True)
resp, body = self.client.create_execution(function_id,
input='Qinling')
self.assertEqual(201, resp.status)
execution_id = body['id']
self.addCleanup(self.client.delete_resource, 'executions',
execution_id, ignore_notfound=True)
self.assertEqual('success', body['status'])
self.assertIn('Qinling', jsonutils.loads(body['result'])['output'])

View File

@ -152,8 +152,9 @@ class FunctionsTest(base.BaseQinlingTest):
def test_detach(self): def test_detach(self):
"""Admin only operation.""" """Admin only operation."""
function_id = self.create_function(self.python_zip_file) function_id = self.create_function(self.python_zip_file)
resp, _ = self.client.create_execution(function_id, resp, _ = self.client.create_execution(
input={'name': 'Qinling'}) function_id, input='{"name": "Qinling"}'
)
self.assertEqual(201, resp.status) self.assertEqual(201, resp.status)
resp, body = self.admin_client.get_function_workers(function_id) resp, body = self.admin_client.get_function_workers(function_id)

View File

@ -68,24 +68,31 @@ class BaseQinlingTest(test.BaseTestCase):
self.assertEqual(200, resp.status) self.assertEqual(200, resp.status)
self.assertEqual('success', body['status']) self.assertEqual('success', body['status'])
def create_function(self, package_path): def create_function(self, package_path=None, image=False):
function_name = data_utils.rand_name('function', function_name = data_utils.rand_name('function',
prefix=self.name_prefix) prefix=self.name_prefix)
base_name, _ = os.path.splitext(package_path)
module_name = os.path.basename(base_name)
with open(package_path, 'rb') as package_data: if not image:
base_name, _ = os.path.splitext(package_path)
module_name = os.path.basename(base_name)
with open(package_path, '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' % module_name
)
self.addCleanup(os.remove, package_path)
else:
resp, body = self.client.create_function( resp, body = self.client.create_function(
{"source": "package"}, {"source": "image", "image": "openstackqinling/alpine-test"},
self.runtime_id, None,
name=function_name, name=function_name,
package_data=package_data,
entry='%s.main' % module_name
) )
self.assertEqual(201, resp.status_code) self.assertEqual(201, resp.status_code)
function_id = body['id'] function_id = body['id']
self.addCleanup(os.remove, package_path)
self.addCleanup(self.client.delete_resource, 'functions', self.addCleanup(self.client.delete_resource, 'functions',
function_id, ignore_notfound=True) function_id, ignore_notfound=True)

View File

@ -93,8 +93,9 @@ class BasicOpsTest(base.BaseQinlingTest):
function_id, ignore_notfound=True) function_id, ignore_notfound=True)
# Invoke function # Invoke function
resp, body = self.client.create_execution(function_id, resp, body = self.client.create_execution(
input={'name': 'Qinling'}) function_id, input='{"name": "Qinling"}'
)
self.assertEqual(201, resp.status) self.assertEqual(201, resp.status)
self.assertEqual('success', body['status']) self.assertEqual('success', body['status'])

View File

@ -53,7 +53,7 @@ def _print_trace():
print(''.join(line for line in lines)) print(''.join(line for line in lines))
def _invoke_function(execution_id, zip_file, module_name, method, input, def _invoke_function(execution_id, zip_file, module_name, method, arg, input,
return_dict): return_dict):
"""Thie function is supposed to be running in a child process.""" """Thie function is supposed to be running in a child process."""
sys.path.insert(0, zip_file) sys.path.insert(0, zip_file)
@ -64,7 +64,7 @@ def _invoke_function(execution_id, zip_file, module_name, method, input,
try: try:
module = importlib.import_module(module_name) module = importlib.import_module(module_name)
func = getattr(module, method) func = getattr(module, method)
return_dict['result'] = func(**input) return_dict['result'] = func(arg, **input) if arg else func(**input)
return_dict['success'] = True return_dict['success'] = True
except Exception as e: except Exception as e:
_print_trace() _print_trace()
@ -180,7 +180,7 @@ def execute():
p = Process( p = Process(
target=_invoke_function, target=_invoke_function,
args=(execution_id, zip_file, function_module, function_method, args=(execution_id, zip_file, function_module, function_method,
input, return_dict) input.pop('__function_input', None), input, return_dict)
) )
p.start() p.start()
p.join() p.join()
@ -224,4 +224,4 @@ setup_logger(logging.DEBUG)
app.logger.info("Starting server") app.logger.info("Starting server")
# Just for testing purpose # Just for testing purpose
app.run(host='0.0.0.0', port='9090', threaded=True) app.run(host='0.0.0.0', port=9090, threaded=True)

View File

@ -15,7 +15,7 @@ function delete_resources(){
ids=$(openstack function execution list -f yaml -c Id | awk '{print $3}') ids=$(openstack function execution list -f yaml -c Id | awk '{print $3}')
for id in $ids for id in $ids
do do
openstack function execution delete $id openstack function execution delete --execution $id
done done
# Delete functions # Delete functions