Add support for static actions API

StaticActionManager is added to use static actions API.
Corresponding tools are added to Murano CLI and OpenStackClient.

Depends-on: I17ab2eba0fd6c42309667f42d0644d21940ab02d
Change-Id: Ib6a60f8e33c5d3593a55db9f758e94e27f0a4445
Partially-implements: blueprint static-actions
This commit is contained in:
Valerii Kovalchuk 2016-07-01 18:57:37 +03:00
parent ab8fcb8210
commit 5f952f4427
11 changed files with 409 additions and 0 deletions

View File

@ -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]

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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."""

View File

@ -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)

View File

@ -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>``

View File

@ -49,6 +49,8 @@ openstack.application_catalog.v1 =
deployment_list = muranoclient.osc.v1.deployment:ListDeployment
static-action_call = muranoclient.osc.v1.action:StaticActionCall
[global]
setup-hooks =
pbr.hooks.setup_hook