Add CLI support for runtime/function/execution

Change-Id: Ib37e87b298c310d04fba533d171c12f3f8989a6e
This commit is contained in:
Lingxian Kong
2017-07-07 15:31:24 +12:00
parent 727b716510
commit 6b312b4eaf
12 changed files with 390 additions and 21 deletions

View File

@@ -210,3 +210,17 @@ class SSLCertificateError(BaseException):
class NoUniqueMatch(ClientException):
"""Multiple entities found instead of one."""
pass
class QinlingClientException(Exception):
"""Base Exception for Qinling client."""
message = "An unknown exception occurred"
code = "UNKNOWN_EXCEPTION"
def __str__(self):
return self.message
def __init__(self, message=message):
self.message = message
super(QinlingClientException, self).__init__(
'%s: %s' % (self.code, self.message))

View File

@@ -19,6 +19,21 @@ from osc_lib.command import command
from osc_lib import utils
import six
from qinlingclient.common import exceptions
RUNTIME_COLUMNS = (
'id', 'name', 'image', 'status', 'description', 'project_id',
'created_at', 'updated_at'
)
FUNCTION_COLUMNS = (
'id', 'name', 'count', 'code', 'runtime_id', 'entry', 'created_at',
'updated_at'
)
EXECUTION_COLUMNS = (
'id', 'function_id', 'input', 'output', 'status', 'sync', 'created_at',
'updated_at'
)
@six.add_metaclass(abc.ABCMeta)
class QinlingLister(command.Lister):
@@ -31,12 +46,8 @@ class QinlingLister(command.Lister):
# No-op by default.
pass
@abc.abstractmethod
def _list_columns(self):
raise NotImplementedError
def _list_headers(self):
return [c.capitalize() for c in self._list_columns()]
def _headers(self):
return [c.capitalize() for c in self.columns]
def take_action(self, parsed_args):
self._validate_parsed_args(parsed_args)
@@ -46,14 +57,33 @@ class QinlingLister(command.Lister):
ret = [ret]
return (
self._list_headers(),
self._headers(),
list(utils.get_item_properties(
s,
self._list_columns(),
self.columns,
) for s in ret)
)
class QinlingDeleter(command.Command):
def delete_resources(self, ids):
"""Delete one or more resources."""
failure_flag = False
success_msg = "Request to delete %s %s has been accepted."
error_msg = "Unable to delete the specified %s(s)."
for id in ids:
try:
self.delete(id)
print(success_msg % (self.resource, id))
except Exception as e:
failure_flag = True
print(e)
if failure_flag:
raise exceptions.QinlingClientException(error_msg)
def cut(string, length=25):
if string and len(string) > length:
return "%s..." % string[:length]

View File

@@ -11,20 +11,132 @@
# 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
"""Qinling v1 function action implementation"""
from osc_lib.command import command
from osc_lib import utils
from oslo_serialization import jsonutils
from qinlingclient.common import exceptions
from qinlingclient.osc.v1 import base
class List(base.QinlingLister):
"""List available runtimes."""
columns = base.FUNCTION_COLUMNS
def _get_resources(self, parsed_args):
client = self.app.client_manager.function_engine
return client.functions.list(**base.get_filters(parsed_args))
def _list_columns(self):
return ('id', 'name', 'count', 'code', 'runtime_id', 'entry',
'created_at', 'updated_at')
class Create(command.ShowOne):
columns = base.FUNCTION_COLUMNS
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
"name",
metavar='NAME',
help="New function name.",
)
parser.add_argument(
"runtime",
metavar='RUNTIME',
help="Runtime ID.",
)
parser.add_argument(
"code",
metavar='CODE',
help="Code definition.",
)
parser.add_argument(
"--entry",
metavar="FUNCTION_ENTRY",
help="Function entry."
)
protected_group = parser.add_mutually_exclusive_group(required=True)
# TODO(kong): Take a look at the file type in argparse module.
protected_group.add_argument(
"--file",
metavar="CODE_FILE_PATH",
help="Code file path."
)
protected_group.add_argument(
"--package",
metavar="CODE_PACKAGE_PATH",
help="Code package zip file path."
)
return parser
def take_action(self, parsed_args):
zip_file = None
if parsed_args.file:
if not os.path.isfile(parsed_args.file):
raise exceptions.QinlingClientException(
'File %s not exist.' % parsed_args.file
)
base_name, extention = os.path.splitext(parsed_args.file)
base_name = os.path.basename(base_name)
zip_file = os.path.join(
tempfile.gettempdir(),
'%s.zip' % base_name
)
zf = zipfile.ZipFile(zip_file, mode='w')
try:
# Use default compression mode, may change in future.
zf.write(
parsed_args.file,
'%s%s' % (base_name, extention),
compress_type=zipfile.ZIP_STORED
)
finally:
zf.close()
if parsed_args.package:
if not zipfile.is_zipfile(parsed_args.package):
raise exceptions.QinlingClientException(
'Package %s is not a valid ZIP file.' % parsed_args.package
)
zip_file = parsed_args.package
with open(zip_file, 'rb') as package:
client = self.app.client_manager.function_engine
function = client.functions.create(
name=parsed_args.name,
runtime=parsed_args.runtime,
code=jsonutils.loads(parsed_args.code),
package=package,
entry=parsed_args.entry,
)
os.remove(zip_file)
return self.columns, utils.get_item_properties(function, self.columns)
class Delete(base.QinlingDeleter):
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'function',
nargs='+',
metavar='FUNCTION',
help='Id of function(s).'
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.function_engine
self.delete = client.functions.delete
self.resource = 'function'
self.delete_resources(parsed_args.function)

View File

@@ -0,0 +1,91 @@
# 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.
from osc_lib.command import command
from osc_lib import utils
from oslo_serialization import jsonutils
from qinlingclient.osc.v1 import base
class List(base.QinlingLister):
columns = base.EXECUTION_COLUMNS
def _get_resources(self, parsed_args):
client = self.app.client_manager.function_engine
return client.function_executions.list(**base.get_filters(parsed_args))
class Create(command.ShowOne):
columns = base.EXECUTION_COLUMNS
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
"function",
metavar='FUNCTION_ID',
help="Function ID.",
)
parser.add_argument(
"--sync",
type=bool,
metavar='SYNC',
default=True,
help="If the execution will be ran synchronously.",
)
parser.add_argument(
"--input",
metavar='INPUT',
help="Input for the function.",
)
return parser
def take_action(self, parsed_args):
if parsed_args.input:
input = jsonutils.loads(parsed_args.input)
else:
input = {}
client = self.app.client_manager.function_engine
execution = client.function_executions.create(
function=parsed_args.function,
sync=parsed_args.sync,
input=input
)
return self.columns, utils.get_item_properties(execution, self.columns)
class Delete(base.QinlingDeleter):
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'execution',
nargs='+',
metavar='EXECUTION',
help='ID of function execution(s).'
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.function_engine
self.delete = client.function_executions.delete
self.resource = 'execution'
self.delete_resources(parsed_args.execution)

View File

@@ -12,19 +12,67 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Qinling v1 runtime action implementation"""
from osc_lib.command import command
from osc_lib import utils
from qinlingclient.osc.v1 import base
class List(base.QinlingLister):
"""List available runtimes."""
columns = base.RUNTIME_COLUMNS
def _get_resources(self, parsed_args):
client = self.app.client_manager.function_engine
return client.runtimes.list(**base.get_filters(parsed_args))
def _list_columns(self):
return ('id', 'name', 'image', 'status', 'project_id', 'created_at',
'updated_at')
class Create(command.ShowOne):
columns = base.RUNTIME_COLUMNS
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
"name",
metavar='NAME',
help="New runtime name.",
)
parser.add_argument(
"image",
metavar='IMAGE',
help="Container image name used by runtime.",
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.function_engine
runtime = client.runtimes.create(
name=parsed_args.name,
image=parsed_args.image
)
return self.columns, utils.get_item_properties(runtime, self.columns)
class Delete(base.QinlingDeleter):
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'runtime',
nargs='+',
metavar='RUNTIME',
help='Id of runtime(s).'
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.function_engine
self.delete = client.runtimes.delete
self.resource = 'runtime'
self.delete_resources(parsed_args.runtime)

View File

@@ -14,6 +14,7 @@
from qinlingclient.common import http
from qinlingclient.v1 import function
from qinlingclient.v1 import function_execution
from qinlingclient.v1 import runtime
@@ -32,5 +33,5 @@ class Client(object):
self.runtimes = runtime.RuntimeManager(self.http_client)
self.functions = function.FunctionManager(self.http_client)
# self.function_executions = function_executions.ExecutionManager(
# self.http_client)
self.function_executions = function_execution.ExecutionManager(
self.http_client)

View File

@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_serialization import jsonutils
from qinlingclient.common import base
@@ -24,3 +26,25 @@ class FunctionManager(base.Manager):
def list(self, **kwargs):
return self._list("/v1/functions", response_key='functions')
def create(self, name, runtime, code, package, entry=None):
data = {
'name': name,
'runtime_id': runtime,
'code': jsonutils.dumps(code)
}
if entry:
data['entry'] = entry
response = self.http_client.request(
'/v1/functions',
'POST',
data=data,
files={'package': package}
)
body = jsonutils.loads(response.text)
return self.resource_class(self, body)
def delete(self, id):
self._delete('/v1/functions/%s' % id)

View File

@@ -0,0 +1,33 @@
# 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.
from qinlingclient.common import base
class FunctionExecution(base.Resource):
pass
class ExecutionManager(base.Manager):
resource_class = FunctionExecution
def list(self, **kwargs):
return self._list("/v1/executions", response_key='executions')
def create(self, function, sync=True, input={}):
data = {'function_id': function, 'sync': sync, 'input': input}
return self._create('/v1/executions', data)
def delete(self, id):
self._delete('/v1/executions/%s' % id)

View File

@@ -24,3 +24,10 @@ class RuntimeManager(base.Manager):
def list(self, **kwargs):
return self._list("/v1/runtimes", response_key='runtimes')
def create(self, name, image):
data = {'name': name, 'image': image}
return self._create('/v1/runtimes', data)
def delete(self, id):
self._delete('/v1/runtimes/%s' % id)

View File

@@ -14,3 +14,4 @@ osc-lib>=1.5.1 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0

View File

@@ -35,8 +35,16 @@ openstack.cli.extension =
openstack.function_engine.v1 =
runtime_list = qinlingclient.osc.v1.runtime:List
runtime_create = qinlingclient.osc.v1.runtime:Create
runtime_delete = qinlingclient.osc.v1.runtime:Delete
function_list = qinlingclient.osc.v1.function:List
function_create = qinlingclient.osc.v1.function:Create
function_delete = qinlingclient.osc.v1.function:Delete
function_execution_list = qinlingclient.osc.v1.function_execution:List
function_execution_create = qinlingclient.osc.v1.function_execution:Create
function_execution_delete = qinlingclient.osc.v1.function_execution:Delete
[global]
setup-hooks =

View File

@@ -44,4 +44,4 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasen
[flake8]
show-source = true
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,build
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,build,releasenotes