Add session support for function
Also add the documentation. Partially implements: blueprint qinling-support-user-session Change-Id: Ic11b3b9f6ffe65bb08cfd880b81d41a40590919b
This commit is contained in:
parent
4272c94a08
commit
d2dd717a2f
@ -22,8 +22,6 @@ In this section you will find information helpful for contributing to qinling
|
|||||||
project.
|
project.
|
||||||
|
|
||||||
|
|
||||||
Programming HowTos and Tutorials
|
|
||||||
--------------------------------
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
33
doc/source/function_developer/index.rst
Normal file
33
doc/source/function_developer/index.rst
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
..
|
||||||
|
Copyright 2010-2011 United States Government as represented by the
|
||||||
|
Administrator of the National Aeronautics and Space Administration.
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Function Programming Guide
|
||||||
|
==========================
|
||||||
|
|
||||||
|
In this section you will find information helpful for developing functions.
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
openstack_integration
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`search`
|
51
doc/source/function_developer/openstack_integration.rst
Normal file
51
doc/source/function_developer/openstack_integration.rst
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
..
|
||||||
|
Copyright 2017 Catalyst IT Ltd
|
||||||
|
All Rights Reserved.
|
||||||
|
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.
|
||||||
|
|
||||||
|
Interact with other OpenStack services
|
||||||
|
======================================
|
||||||
|
|
||||||
|
It's very easy to interact with other OpenStack services in your function.
|
||||||
|
Let's take Python programming language and integration with Swift service for
|
||||||
|
an example.
|
||||||
|
|
||||||
|
At the time you create a function, you specify an entry, which is a function
|
||||||
|
in your code module that Qinling can invoke when the service executes your
|
||||||
|
code. Use the following general syntax structure when creating a function in
|
||||||
|
Python which will interact with Swift service in OpenStack.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import swiftclient
|
||||||
|
|
||||||
|
def main(region_name, container, object, context=None, **kwargs):
|
||||||
|
conn = swiftclient.Connection(
|
||||||
|
session=context['os_session'],
|
||||||
|
os_options={'region_name': region_name},
|
||||||
|
)
|
||||||
|
|
||||||
|
obj_info = conn.head_object(container, object)
|
||||||
|
return obj_info
|
||||||
|
|
||||||
|
In the above code, note the following:
|
||||||
|
|
||||||
|
- Qinling supports most of OpenStack service clients, so you don't need to
|
||||||
|
install ``python-swiftclient`` in your code package.
|
||||||
|
- There is a parameter named ``context``, this is a parameter provided by
|
||||||
|
Qinling that is usually of the Python dict type. You can easily get a valid
|
||||||
|
Keystone session object from it. As a result, you don't need to pass any
|
||||||
|
sensitive data to Qinling in order to interact with OpenStack services.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Please avoid using ``context`` as your own parameter in the code.
|
@ -27,24 +27,32 @@ Lambda). Qinling supports different container orchestration platforms
|
|||||||
* IRC channel on Freenode: #openstack-qinling
|
* IRC channel on Freenode: #openstack-qinling
|
||||||
|
|
||||||
|
|
||||||
Table of Contents
|
Overview
|
||||||
=================
|
--------
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
quick_start
|
quick_start
|
||||||
|
|
||||||
Contributor/Developer Docs
|
Contributor/Developer Guide
|
||||||
==========================
|
---------------------------
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
contributor/index
|
contributor/index
|
||||||
|
|
||||||
|
Function Programming Guide
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
function_developer/index
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
------------------
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
* :ref:`search`
|
* :ref:`search`
|
||||||
|
@ -11,17 +11,15 @@
|
|||||||
# 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.
|
||||||
|
from keystoneauth1 import loading
|
||||||
"""
|
from keystonemiddleware import auth_token
|
||||||
Configuration options registration and useful routines.
|
|
||||||
"""
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from qinling import version
|
from qinling import version
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
launch_opt = cfg.ListOpt(
|
launch_opt = cfg.ListOpt(
|
||||||
'server',
|
'server',
|
||||||
default=['all'],
|
default=['all'],
|
||||||
@ -137,25 +135,24 @@ kubernetes_opts = [
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CLI_OPTS = [launch_opt]
|
|
||||||
CONF.register_cli_opts(CLI_OPTS)
|
|
||||||
default_group_opts = itertools.chain(CLI_OPTS, [])
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
def list_opts():
|
||||||
return [
|
keystone_middleware_opts = auth_token.list_opts()
|
||||||
|
keystone_loading_opts = [(
|
||||||
|
'keystone_authtoken', loading.get_auth_plugin_conf_options('password')
|
||||||
|
)]
|
||||||
|
|
||||||
|
qinling_opts = [
|
||||||
(API_GROUP, api_opts),
|
(API_GROUP, api_opts),
|
||||||
(PECAN_GROUP, pecan_opts),
|
(PECAN_GROUP, pecan_opts),
|
||||||
(ENGINE_GROUP, engine_opts),
|
(ENGINE_GROUP, engine_opts),
|
||||||
(STORAGE_GROUP, storage_opts),
|
(STORAGE_GROUP, storage_opts),
|
||||||
(KUBERNETES_GROUP, kubernetes_opts),
|
(KUBERNETES_GROUP, kubernetes_opts),
|
||||||
(None, default_group_opts)
|
(None, [launch_opt])
|
||||||
]
|
]
|
||||||
|
|
||||||
|
return keystone_middleware_opts + keystone_loading_opts + qinling_opts
|
||||||
|
|
||||||
for group, options in list_opts():
|
|
||||||
CONF.register_opts(list(options), group)
|
|
||||||
|
|
||||||
_DEFAULT_LOG_LEVELS = [
|
_DEFAULT_LOG_LEVELS = [
|
||||||
'eventlet.wsgi.server=WARN',
|
'eventlet.wsgi.server=WARN',
|
||||||
@ -176,6 +173,12 @@ def parse_args(args=None, usage=None, default_config_files=None):
|
|||||||
log.set_defaults(default_log_levels=default_log_levels)
|
log.set_defaults(default_log_levels=default_log_levels)
|
||||||
log.register_options(CONF)
|
log.register_options(CONF)
|
||||||
|
|
||||||
|
CLI_OPTS = [launch_opt]
|
||||||
|
CONF.register_cli_opts(CLI_OPTS)
|
||||||
|
|
||||||
|
for group, options in list_opts():
|
||||||
|
CONF.register_opts(list(options), group)
|
||||||
|
|
||||||
CONF(
|
CONF(
|
||||||
args=args,
|
args=args,
|
||||||
project='qinling',
|
project='qinling',
|
||||||
|
@ -134,7 +134,7 @@ def upgrade():
|
|||||||
sa.Column('function_input', st.JsonLongDictType(), nullable=True),
|
sa.Column('function_input', st.JsonLongDictType(), nullable=True),
|
||||||
sa.Column('status', sa.String(length=32), nullable=False),
|
sa.Column('status', sa.String(length=32), nullable=False),
|
||||||
sa.Column('name', sa.String(length=255), nullable=True),
|
sa.Column('name', sa.String(length=255), nullable=True),
|
||||||
sa.Column('pattern', sa.String(length=32), nullable=False),
|
sa.Column('pattern', sa.String(length=32), nullable=True),
|
||||||
sa.Column('first_execution_time', sa.DateTime(), nullable=True),
|
sa.Column('first_execution_time', sa.DateTime(), nullable=True),
|
||||||
sa.Column('next_execution_time', sa.DateTime(), nullable=False),
|
sa.Column('next_execution_time', sa.DateTime(), nullable=False),
|
||||||
sa.Column('count', sa.Integer(), nullable=True),
|
sa.Column('count', sa.Integer(), nullable=True),
|
||||||
|
@ -16,6 +16,7 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from qinling import context
|
||||||
from qinling.db import api as db_api
|
from qinling.db import api as db_api
|
||||||
from qinling import status
|
from qinling import status
|
||||||
from qinling.utils import common
|
from qinling.utils import common
|
||||||
@ -103,6 +104,14 @@ class DefaultEngine(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
data = {'input': input, 'execution_id': execution_id}
|
data = {'input': input, 'execution_id': execution_id}
|
||||||
|
if CONF.pecan.auth_enable:
|
||||||
|
data.update(
|
||||||
|
{
|
||||||
|
'token': context.get_ctx().auth_token,
|
||||||
|
'trust_id': context.get_ctx().trust_id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
r = self.session.post(func_url, json=data)
|
r = self.session.post(func_url, json=data)
|
||||||
res = r.json()
|
res = r.json()
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ class EngineService(service.Service):
|
|||||||
executor='eventlet',
|
executor='eventlet',
|
||||||
access_policy=access_policy,
|
access_policy=access_policy,
|
||||||
serializer=rpc.ContextSerializer(
|
serializer=rpc.ContextSerializer(
|
||||||
messaging.serializer.JsonPayloadSerializer())
|
messaging.serializer.JsonPayloadSerializer()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG.info('Starting engine...')
|
LOG.info('Starting engine...')
|
||||||
|
@ -298,12 +298,19 @@ class KubernetesManager(base.OrchestratorBase):
|
|||||||
'download_url': download_url,
|
'download_url': download_url,
|
||||||
'function_id': actual_function,
|
'function_id': actual_function,
|
||||||
'entry': entry,
|
'entry': entry,
|
||||||
'token': context.get_ctx().auth_token,
|
|
||||||
}
|
}
|
||||||
|
if self.conf.pecan.auth_enable:
|
||||||
|
data.update(
|
||||||
|
{
|
||||||
|
'token': context.get_ctx().auth_token,
|
||||||
|
'auth_url': self.conf.keystone_authtoken.auth_uri,
|
||||||
|
'username': self.conf.keystone_authtoken.username,
|
||||||
|
'password': self.conf.keystone_authtoken.password,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
LOG.debug(
|
LOG.info(
|
||||||
'Send request to pod %s, request_url: %s, data: %s',
|
'Send request to pod %s, request_url: %s', name, request_url
|
||||||
name, request_url, data
|
|
||||||
)
|
)
|
||||||
|
|
||||||
exception = None
|
exception = None
|
||||||
@ -390,7 +397,17 @@ class KubernetesManager(base.OrchestratorBase):
|
|||||||
identifier=None, service_url=None):
|
identifier=None, service_url=None):
|
||||||
if service_url:
|
if service_url:
|
||||||
func_url = '%s/execute' % service_url
|
func_url = '%s/execute' % service_url
|
||||||
data = {'input': input, 'execution_id': execution_id}
|
data = {
|
||||||
|
'input': input,
|
||||||
|
'execution_id': execution_id,
|
||||||
|
}
|
||||||
|
if self.conf.pecan.auth_enable:
|
||||||
|
data.update(
|
||||||
|
{
|
||||||
|
'token': context.get_ctx().auth_token,
|
||||||
|
'trust_id': context.get_ctx().trust_id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
LOG.info('Invoke function %s, url: %s', function_id, func_url)
|
LOG.info('Invoke function %s, url: %s', function_id, func_url)
|
||||||
|
|
||||||
|
@ -23,8 +23,7 @@ from webtest import app as webtest_app
|
|||||||
|
|
||||||
from qinling.tests.unit import base
|
from qinling.tests.unit import base
|
||||||
|
|
||||||
# Disable authentication by default for API tests.
|
CONF = cfg.CONF
|
||||||
cfg.CONF.set_default('auth_enable', False, group='pecan')
|
|
||||||
|
|
||||||
|
|
||||||
class APITest(base.DbTestCase):
|
class APITest(base.DbTestCase):
|
||||||
@ -36,7 +35,13 @@ class APITest(base.DbTestCase):
|
|||||||
self.override_config('file_system_dir', package_dir, 'storage')
|
self.override_config('file_system_dir', package_dir, 'storage')
|
||||||
self.addCleanup(shutil.rmtree, package_dir, True)
|
self.addCleanup(shutil.rmtree, package_dir, True)
|
||||||
|
|
||||||
pecan_opts = cfg.CONF.pecan
|
# Disable authentication by default for API tests.
|
||||||
|
CONF.set_default('auth_enable', False, group='pecan')
|
||||||
|
self.addCleanup(
|
||||||
|
CONF.set_default, 'auth_enable', False, group='pecan'
|
||||||
|
)
|
||||||
|
|
||||||
|
pecan_opts = CONF.pecan
|
||||||
self.app = pecan.testing.load_test_app({
|
self.app = pecan.testing.load_test_app({
|
||||||
'app': {
|
'app': {
|
||||||
'root': pecan_opts.root,
|
'root': pecan_opts.root,
|
||||||
@ -47,9 +52,6 @@ class APITest(base.DbTestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.addCleanup(pecan.set_config, {}, overwrite=True)
|
self.addCleanup(pecan.set_config, {}, overwrite=True)
|
||||||
self.addCleanup(
|
|
||||||
cfg.CONF.set_default, 'auth_enable', False, group='pecan'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.patch_ctx = mock.patch('qinling.context.Context.from_environ')
|
self.patch_ctx = mock.patch('qinling.context.Context.from_environ')
|
||||||
self.mock_ctx = self.patch_ctx.start()
|
self.mock_ctx = self.patch_ctx.start()
|
||||||
|
@ -19,13 +19,12 @@ import random
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
|
from qinling import config
|
||||||
from qinling import context as auth_context
|
from qinling import context as auth_context
|
||||||
from qinling.db import api as db_api
|
from qinling.db import api as db_api
|
||||||
from qinling.db.sqlalchemy import sqlite_lock
|
from qinling.db.sqlalchemy import sqlite_lock
|
||||||
from qinling import status
|
from qinling import status
|
||||||
from qinling.tests.unit import config as test_config
|
|
||||||
|
|
||||||
test_config.parse_args()
|
|
||||||
DEFAULT_PROJECT_ID = 'default'
|
DEFAULT_PROJECT_ID = 'default'
|
||||||
OPT_PROJECT_ID = '55-66-77-88'
|
OPT_PROJECT_ID = '55-66-77-88'
|
||||||
|
|
||||||
@ -132,6 +131,17 @@ class DbTestCase(BaseTest):
|
|||||||
cfg.CONF.set_default('max_overflow', -1, group='database')
|
cfg.CONF.set_default('max_overflow', -1, group='database')
|
||||||
cfg.CONF.set_default('max_pool_size', 1000, group='database')
|
cfg.CONF.set_default('max_pool_size', 1000, group='database')
|
||||||
|
|
||||||
|
qinling_opts = [
|
||||||
|
(config.API_GROUP, config.api_opts),
|
||||||
|
(config.PECAN_GROUP, config.pecan_opts),
|
||||||
|
(config.ENGINE_GROUP, config.engine_opts),
|
||||||
|
(config.STORAGE_GROUP, config.storage_opts),
|
||||||
|
(config.KUBERNETES_GROUP, config.kubernetes_opts),
|
||||||
|
(None, [config.launch_opt])
|
||||||
|
]
|
||||||
|
for group, options in qinling_opts:
|
||||||
|
cfg.CONF.register_opts(list(options), group)
|
||||||
|
|
||||||
db_api.setup_db()
|
db_api.setup_db()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
# Copyright 2017 Catalyst IT Limited
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
||||||
from qinling import config
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
# Look for .mistral.conf in the project directory by default.
|
|
||||||
project_dir = '%s/../..' % os.path.dirname(__file__)
|
|
||||||
config_file = '%s/.qinling.conf' % os.path.realpath(project_dir)
|
|
||||||
config_files = [config_file] if os.path.isfile(config_file) else None
|
|
||||||
|
|
||||||
config.parse_args(args=[], default_config_files=config_files)
|
|
@ -13,5 +13,5 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
def main(name='World'):
|
def main(name='World', **kwargs):
|
||||||
print('Hello, %s' % name)
|
print('Hello, %s' % name)
|
||||||
|
@ -27,21 +27,34 @@ from flask import abort
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask import Response
|
from flask import Response
|
||||||
|
from keystoneauth1.identity import generic
|
||||||
|
from keystoneauth1 import session
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
zip_file = ''
|
zip_file = ''
|
||||||
function_module = 'main'
|
function_module = 'main'
|
||||||
function_method = 'main'
|
function_method = 'main'
|
||||||
|
auth_url = None
|
||||||
|
username = None
|
||||||
|
password = None
|
||||||
|
|
||||||
|
|
||||||
@app.route('/download', methods=['POST'])
|
@app.route('/download', methods=['POST'])
|
||||||
def download():
|
def download():
|
||||||
params = request.get_json() or {}
|
params = request.get_json() or {}
|
||||||
|
|
||||||
download_url = params.get('download_url')
|
download_url = params.get('download_url')
|
||||||
function_id = params.get('function_id')
|
function_id = params.get('function_id')
|
||||||
entry = params.get('entry')
|
entry = params.get('entry')
|
||||||
|
|
||||||
|
global auth_url
|
||||||
|
global username
|
||||||
|
global password
|
||||||
token = params.get('token')
|
token = params.get('token')
|
||||||
|
auth_url = params.get('auth_url')
|
||||||
|
username = params.get('username')
|
||||||
|
password = params.get('password')
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
if token:
|
if token:
|
||||||
@ -101,10 +114,32 @@ def execute():
|
|||||||
global zip_file
|
global zip_file
|
||||||
global function_module
|
global function_module
|
||||||
global function_method
|
global function_method
|
||||||
|
global auth_url
|
||||||
|
global username
|
||||||
|
global password
|
||||||
|
|
||||||
params = request.get_json() or {}
|
params = request.get_json() or {}
|
||||||
input = params.get('input') or {}
|
input = params.get('input') or {}
|
||||||
execution_id = params['execution_id']
|
execution_id = params['execution_id']
|
||||||
|
token = params.get('token')
|
||||||
|
trust_id = params.get('trust_id')
|
||||||
|
|
||||||
|
# Provide an openstack session to user's function
|
||||||
|
os_session = None
|
||||||
|
if auth_url:
|
||||||
|
if not trust_id:
|
||||||
|
auth = generic.Token(auth_url=auth_url, token=token)
|
||||||
|
else:
|
||||||
|
auth = generic.Password(
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
auth_url=auth_url,
|
||||||
|
trust_id=trust_id,
|
||||||
|
user_domain_name='Default'
|
||||||
|
)
|
||||||
|
os_session = session.Session(auth=auth, verify=False)
|
||||||
|
|
||||||
|
input.update({'context': {'os_session': os_session}})
|
||||||
|
|
||||||
manager = Manager()
|
manager = Manager()
|
||||||
return_dict = manager.dict()
|
return_dict = manager.dict()
|
||||||
@ -124,7 +159,6 @@ def execute():
|
|||||||
|
|
||||||
with open('%s.out' % execution_id) as f:
|
with open('%s.out' % execution_id) as f:
|
||||||
logs = f.read()
|
logs = f.read()
|
||||||
|
|
||||||
os.remove('%s.out' % execution_id)
|
os.remove('%s.out' % execution_id)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
|
Loading…
Reference in New Issue
Block a user