Merge "Add support for static actions API"
This commit is contained in:
commit
32b1ec7c2d
|
@ -0,0 +1,89 @@
|
|||
# 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.
|
||||
|
||||
"""Application-catalog v1 action implementation"""
|
||||
|
||||
import json
|
||||
|
||||
from muranoclient.openstack.common.apiclient import exceptions
|
||||
from osc_lib.command import command
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StaticActionCall(command.ShowOne):
|
||||
"""Call static method of the MuranoPL class."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(StaticActionCall, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
"class_name",
|
||||
metavar='<CLASS>',
|
||||
help="FQN of the class with static method",
|
||||
)
|
||||
parser.add_argument(
|
||||
"method_name",
|
||||
metavar='<METHOD>',
|
||||
help="Static method to run",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--arguments",
|
||||
metavar='<KEY=VALUE>',
|
||||
nargs='*',
|
||||
help="Method arguments. No arguments by default",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--package-name",
|
||||
metavar='<PACKAGE>',
|
||||
default='',
|
||||
help='Optional FQN of the package to look for the class in',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--class-version",
|
||||
default='',
|
||||
help='Optional version of the class, otherwise version 0.0.0 is '
|
||||
'used ',
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
LOG.debug("take_action({0})".format(parsed_args))
|
||||
client = self.app.client_manager.application_catalog
|
||||
|
||||
arguments = {}
|
||||
for argument in parsed_args.arguments or []:
|
||||
if '=' not in argument:
|
||||
raise exceptions.CommandError(
|
||||
"Argument should be in form of KEY=VALUE. Found: "
|
||||
"{0}".format(argument))
|
||||
key, value = argument.split('=', 1)
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except ValueError:
|
||||
# treat value as a string if it doesn't load as json
|
||||
pass
|
||||
|
||||
arguments[key] = value
|
||||
|
||||
request_body = {
|
||||
"className": parsed_args.class_name,
|
||||
"methodName": parsed_args.method_name,
|
||||
"packageName": parsed_args.package_name or None,
|
||||
"classVersion": parsed_args.class_version or '0.0.0',
|
||||
"parameters": arguments
|
||||
}
|
||||
|
||||
print("Waiting for result...")
|
||||
result = client.static_actions.call(request_body).get_result()
|
||||
return ["Static action result"], [result]
|
|
@ -6,6 +6,12 @@ Namespaces:
|
|||
|
||||
Extends: std:Application
|
||||
|
||||
Properties:
|
||||
greeting:
|
||||
Usage: Static
|
||||
Contract: $.string()
|
||||
Default: 'Hello, '
|
||||
|
||||
Methods:
|
||||
testAction:
|
||||
Usage: Action
|
||||
|
@ -15,3 +21,14 @@ Methods:
|
|||
deploy:
|
||||
Body:
|
||||
- $this.find(std:Environment).reporter.report($this, 'Follow the white rabbit')
|
||||
staticAction:
|
||||
Scope: Public
|
||||
Usage: Static
|
||||
Arguments:
|
||||
- myName:
|
||||
Contract: $.string().notNull()
|
||||
- myAge:
|
||||
Contract: $.int().notNull()
|
||||
Body:
|
||||
- $futureAge: $myAge + 5
|
||||
- Return: concat($.greeting, $myName, ". In 5 years you will be {0} years old.".format($futureAge))
|
||||
|
|
|
@ -808,3 +808,30 @@ class BundleMuranoSanityClientTest(utils.CLIUtilsTestPackagesBase):
|
|||
fail_ok=False)
|
||||
except utils.exceptions.CommandFailed as exception:
|
||||
self.assertIn("Can't parse bundle contents", exception.stdout)
|
||||
|
||||
|
||||
class StaticActionMuranoClientTest(utils.CLIUtilsTestPackagesBase):
|
||||
"""Tests for testing static actions execution.
|
||||
|
||||
Tests for the Murano CLI commands which check the result of sample
|
||||
static action execution.
|
||||
"""
|
||||
|
||||
def test_static_action_call(self):
|
||||
"""Test scenario:
|
||||
|
||||
1) import package
|
||||
2) call static action of the class in that package
|
||||
3) check the result of action
|
||||
"""
|
||||
package = self.import_package(
|
||||
self.app_name,
|
||||
self.dummy_app_path
|
||||
)
|
||||
result = self.murano(
|
||||
'static-action-call', params='{0} staticAction --package-name {1} '
|
||||
'--arguments myName=John myAge=28'.format(package['FQN'],
|
||||
package['FQN']))
|
||||
expected = "Waiting for result...\nStatic action result: Hello, " \
|
||||
"John. In 5 years you will be 33 years old.\n"
|
||||
self.assertEqual(expected, result)
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
# 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 mock
|
||||
|
||||
from muranoclient.osc.v1 import action as osc_action
|
||||
from muranoclient.tests.unit.osc.v1 import fakes
|
||||
from muranoclient.v1 import static_actions as api_static_actions
|
||||
|
||||
|
||||
class TestAction(fakes.TestApplicationCatalog):
|
||||
def setUp(self):
|
||||
super(TestAction, self).setUp()
|
||||
self.static_actions_mock = \
|
||||
self.app.client_manager.application_catalog.static_actions
|
||||
|
||||
|
||||
class TestStaticActionCall(TestAction):
|
||||
def setUp(self):
|
||||
super(TestStaticActionCall, self).setUp()
|
||||
self.static_actions_mock.call.return_value = \
|
||||
api_static_actions.StaticActionResult('result')
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_action.StaticActionCall(self.app, None)
|
||||
|
||||
@mock.patch('osc_lib.utils.get_item_properties')
|
||||
def test_static_action_call_basic(self, mock_util):
|
||||
mock_util.return_value = 'result'
|
||||
|
||||
arglist = ['class.name', 'method.name']
|
||||
verifylist = [('class_name', 'class.name'),
|
||||
('method_name', 'method.name')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = ['Static action result']
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = ['result']
|
||||
self.assertEqual(expected_data, data)
|
||||
|
||||
@mock.patch('osc_lib.utils.get_item_properties')
|
||||
def test_static_action_call_full(self, mock_util):
|
||||
mock_util.return_value = 'result'
|
||||
|
||||
arglist = ['class.name', 'method.name',
|
||||
'--arguments', 'food=spam', 'parrot=dead',
|
||||
'--package-name', 'package.name',
|
||||
'--class-version', '>1']
|
||||
verifylist = [('class_name', 'class.name'),
|
||||
('method_name', 'method.name'),
|
||||
('arguments', ['food=spam', 'parrot=dead']),
|
||||
('package_name', 'package.name'),
|
||||
('class_version', '>1')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = ['Static action result']
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = ['result']
|
||||
self.assertEqual(expected_data, data)
|
|
@ -21,6 +21,7 @@ from muranoclient.v1 import actions
|
|||
import muranoclient.v1.environments as environments
|
||||
from muranoclient.v1 import packages
|
||||
import muranoclient.v1.sessions as sessions
|
||||
from muranoclient.v1 import static_actions
|
||||
import muranoclient.v1.templates as templates
|
||||
|
||||
|
||||
|
@ -251,6 +252,14 @@ class UnitTestsForClassesAndFunctions(testtools.TestCase):
|
|||
result = manager.get_result('testEnvId', '1234')
|
||||
self.assertEqual({'a': 'b'}, result)
|
||||
|
||||
def test_static_action_manager_call(self):
|
||||
api_mock = mock.MagicMock(
|
||||
json_request=lambda *args, **kwargs: (None, 'result'))
|
||||
manager = static_actions.StaticActionManager(api_mock)
|
||||
args = {'className': 'cls', 'methodName': 'method'}
|
||||
result = manager.call(args).get_result()
|
||||
self.assertEqual('result', result)
|
||||
|
||||
def test_env_template_manager_list(self):
|
||||
"""Tests the list of environment templates."""
|
||||
manager = templates.EnvTemplateManager(api)
|
||||
|
|
|
@ -517,6 +517,86 @@ class ShellCommandTest(ShellTest):
|
|||
self.client.actions.get_result.assert_called_once_with(
|
||||
'12345', '54321')
|
||||
|
||||
@mock.patch('muranoclient.v1.static_actions.StaticActionManager')
|
||||
@requests_mock.mock()
|
||||
def test_static_action_call_basic(self, mock_manager, m_requests):
|
||||
self.client.static_actions = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('static-action-call class.name method.name')
|
||||
self.client.static_actions.call.assert_called_once_with({
|
||||
"className": 'class.name',
|
||||
"methodName": 'method.name',
|
||||
"packageName": None,
|
||||
"classVersion": '0.0.0',
|
||||
"parameters": {}
|
||||
})
|
||||
|
||||
@mock.patch('muranoclient.v1.static_actions.StaticActionManager')
|
||||
@requests_mock.mock()
|
||||
def test_static_action_call_full(self, mock_manager, m_requests):
|
||||
self.client.static_actions = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('static-action-call class.name method.name '
|
||||
'--package-name package.name --class-version ">1"')
|
||||
self.client.static_actions.call.assert_called_once_with({
|
||||
"className": 'class.name',
|
||||
"methodName": 'method.name',
|
||||
"packageName": 'package.name',
|
||||
"classVersion": '">1"',
|
||||
"parameters": {}
|
||||
})
|
||||
|
||||
@mock.patch('muranoclient.v1.static_actions.StaticActionManager')
|
||||
@requests_mock.mock()
|
||||
def test_static_action_call_string_args(self, mock_manager, m_requests):
|
||||
self.client.static_actions = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell('static-action-call class.name method.name '
|
||||
'--arguments food=spam parrot=dead')
|
||||
self.client.static_actions.call.assert_called_once_with({
|
||||
"className": 'class.name',
|
||||
"methodName": 'method.name',
|
||||
"packageName": None,
|
||||
"classVersion": '0.0.0',
|
||||
"parameters": {'food': 'spam', 'parrot': 'dead'}
|
||||
})
|
||||
|
||||
@mock.patch('muranoclient.v1.static_actions.StaticActionManager')
|
||||
@requests_mock.mock()
|
||||
def test_static_action_call_json_args(self, mock_manager, m_requests):
|
||||
self.client.static_actions = mock_manager()
|
||||
self.make_env()
|
||||
self.register_keystone_discovery_fixture(m_requests)
|
||||
self.register_keystone_token_fixture(m_requests)
|
||||
self.shell("""static-action-call class.name method.name
|
||||
--arguments
|
||||
dictArg={"key1":"value1","key2":"value2"}
|
||||
listArg=["item1","item2","item3"]
|
||||
nullArg=null
|
||||
stringArg="null"
|
||||
intArg=5
|
||||
compoundArg=["foo",14,{"key1":null,"key2":8}]""")
|
||||
self.client.static_actions.call.assert_called_once_with({
|
||||
"className": 'class.name',
|
||||
"methodName": 'method.name',
|
||||
"packageName": None,
|
||||
"classVersion": '0.0.0',
|
||||
"parameters": {
|
||||
'dictArg': {u'key1': u'value1', u'key2': u'value2'},
|
||||
'listArg': [u'item1', u'item2', u'item3'],
|
||||
'nullArg': None,
|
||||
'stringArg': u'null',
|
||||
'intArg': 5,
|
||||
'compoundArg': [u'foo', 14, {u'key1': None, u'key2': 8}]
|
||||
}
|
||||
})
|
||||
|
||||
@mock.patch('muranoclient.v1.templates.EnvTemplateManager')
|
||||
@requests_mock.mock()
|
||||
def test_env_template_delete(self, mock_manager, m_requests):
|
||||
|
|
|
@ -23,6 +23,7 @@ from muranoclient.v1 import packages
|
|||
from muranoclient.v1 import request_statistics
|
||||
from muranoclient.v1 import services
|
||||
from muranoclient.v1 import sessions
|
||||
from muranoclient.v1 import static_actions
|
||||
from muranoclient.v1 import templates
|
||||
|
||||
|
||||
|
@ -59,4 +60,6 @@ class Client(object):
|
|||
else:
|
||||
self.packages = pkg_mgr
|
||||
self.actions = actions.ActionManager(self.http_client)
|
||||
self.static_actions = static_actions.StaticActionManager(
|
||||
self.http_client)
|
||||
self.categories = categories.CategoryManager(self.http_client)
|
||||
|
|
|
@ -210,6 +210,54 @@ def do_environment_action_get_result(mc, args):
|
|||
print("Task id result: {0}".format(result))
|
||||
|
||||
|
||||
@utils.arg("class_name", metavar='<CLASS>',
|
||||
help="FQN of the class with static method")
|
||||
@utils.arg("method_name", metavar='<METHOD>', help="Static method to run")
|
||||
@utils.arg("--arguments", metavar='<KEY=VALUE>', nargs='*',
|
||||
help="Method arguments. No arguments by default")
|
||||
@utils.arg("--package-name", metavar='<PACKAGE>', default='',
|
||||
help='Optional FQN of the package to look for the class in')
|
||||
@utils.arg("--class-version", default='',
|
||||
help='Optional version of the class, otherwise version 0.0.0 is '
|
||||
'used ')
|
||||
def do_static_action_call(mc, args):
|
||||
"""Call static method `METHOD` of the class `CLASS` with `ARGUMENTS`.
|
||||
|
||||
Returns the result of the method execution.
|
||||
`PACKAGE` and `CLASS_VERSION` can be specified optionally to find class in
|
||||
a particular package and to look for the specific version of a class
|
||||
respectively.
|
||||
"""
|
||||
arguments = {}
|
||||
for argument in args.arguments or []:
|
||||
if '=' not in argument:
|
||||
raise exceptions.CommandError(
|
||||
"Argument should be in form of KEY=VALUE. Found: {0}".format(
|
||||
argument))
|
||||
key, value = argument.split('=', 1)
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except ValueError:
|
||||
# treat value as a string if it doesn't load as json
|
||||
pass
|
||||
arguments[key] = value
|
||||
|
||||
request_body = {
|
||||
"className": args.class_name,
|
||||
"methodName": args.method_name,
|
||||
"packageName": args.package_name or None,
|
||||
"classVersion": args.class_version or '0.0.0',
|
||||
"parameters": arguments
|
||||
}
|
||||
|
||||
print("Waiting for result...")
|
||||
try:
|
||||
result = mc.static_actions.call(request_body).get_result()
|
||||
print("Static action result: {0}".format(result))
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
|
||||
@utils.arg("id", metavar="<ID>", help="ID of Environment to add session to.")
|
||||
def do_environment_session_create(mc, args):
|
||||
"""Creates a new configuration session for environment ID."""
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright (c) 2016 Mirantis, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
class StaticActionResult(object):
|
||||
def __init__(self, result, exception=None):
|
||||
self._result = result
|
||||
self._exception = exception
|
||||
|
||||
def get_result(self):
|
||||
if self._exception:
|
||||
raise self._exception
|
||||
return self._result
|
||||
|
||||
def check_result(self):
|
||||
return True
|
||||
|
||||
|
||||
# Not a true manager yet; should be changed to be one if CRUD
|
||||
# functionality becomes available for actions.
|
||||
class StaticActionManager(object):
|
||||
def __init__(self, api):
|
||||
self.api = api
|
||||
|
||||
def call(self, arguments):
|
||||
url = '/v1/actions'
|
||||
try:
|
||||
resp, body = self.api.json_request(url, 'POST', data=arguments)
|
||||
return StaticActionResult(body)
|
||||
except Exception as e:
|
||||
if e.code >= 500:
|
||||
raise
|
||||
return StaticActionResult(None, exception=e)
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
features:
|
||||
- Support for static actions API was added
|
||||
- New Murano CLI command ``murano static-action-call [--arguments
|
||||
[<KEY=VALUE> [<KEY=VALUE> ...]]] [--package-name <PACKAGE>]
|
||||
[--class-version CLASS_VERSION] <CLASS> <METHOD>``
|
||||
- New OSC command ``openstack static-action call [--arguments
|
||||
[<KEY=VALUE> [<KEY=VALUE> ...]]] [--package-name <PACKAGE>]
|
||||
[--class-version CLASS_VERSION] <CLASS> <METHOD>``
|
Loading…
Reference in New Issue