Add generating parameters for openstack-actions
* Fixed Action model to keep more data in input field * Fixed ActionGenerator to return list of action dictionaries instead of dict * Removed all wrong action methods in mapping.json * Fixed keystone group in mapping.json (oauth1) * Generator now uses fake python client to retrieve client method for making description and parameters * Fixed inspect_utils Change-Id: Ib9b5ff6ea32296058d09180f5766c7afff1a3f4f
This commit is contained in:
parent
412f5ced97
commit
57cc6864a7
@ -18,13 +18,14 @@ import abc
|
||||
class ActionGenerator(object):
|
||||
"""Action generator.
|
||||
|
||||
Action generator uses some data to build Action class
|
||||
Action generator uses some data to build Action classes
|
||||
dynamically.
|
||||
"""
|
||||
@abc.abstractmethod
|
||||
def create_action_classes(self, *args, **kwargs):
|
||||
def create_actions(self, *args, **kwargs):
|
||||
"""Constructs classes of needed action.
|
||||
|
||||
return: dict of action names and Action classes.
|
||||
return: list of actions dicts containing name, class,
|
||||
description and parameter info.
|
||||
"""
|
||||
pass
|
@ -18,6 +18,8 @@ from oslo.config import cfg
|
||||
import pkg_resources as pkg
|
||||
|
||||
from mistral.actions import action_generator
|
||||
from mistral.openstack.common import log as logging
|
||||
from mistral.utils import inspect_utils as i_u
|
||||
from mistral import version
|
||||
|
||||
os_actions_mapping_path = cfg.StrOpt('openstack_actions_mapping_path',
|
||||
@ -26,9 +28,29 @@ os_actions_mapping_path = cfg.StrOpt('openstack_actions_mapping_path',
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opt(os_actions_mapping_path)
|
||||
LOG = logging.getLogger(__name__)
|
||||
MAPPING_PATH = CONF.openstack_actions_mapping_path
|
||||
|
||||
|
||||
def get_mapping():
|
||||
def delete_comment(map_part):
|
||||
for key, value in map_part.items():
|
||||
if isinstance(value, dict):
|
||||
delete_comment(value)
|
||||
if '_comment' in map_part:
|
||||
del map_part['_comment']
|
||||
|
||||
mapping = json.loads(open(pkg.resource_filename(
|
||||
version.version_info.package,
|
||||
MAPPING_PATH)).read())
|
||||
|
||||
for k, v in mapping.items():
|
||||
if isinstance(v, dict):
|
||||
delete_comment(v)
|
||||
|
||||
return mapping
|
||||
|
||||
|
||||
class OpenStackActionGenerator(action_generator.ActionGenerator):
|
||||
"""OpenStackActionGenerator.
|
||||
|
||||
@ -51,15 +73,35 @@ class OpenStackActionGenerator(action_generator.ActionGenerator):
|
||||
return action_class
|
||||
|
||||
@classmethod
|
||||
def create_action_classes(cls):
|
||||
mapping = json.loads(open(pkg.resource_filename(
|
||||
version.version_info.package,
|
||||
MAPPING_PATH)).read())
|
||||
def create_actions(cls):
|
||||
mapping = get_mapping()
|
||||
method_dict = mapping[cls.action_namespace]
|
||||
|
||||
action_classes = {}
|
||||
action_classes = []
|
||||
|
||||
for action_name, method_name in method_dict.items():
|
||||
action_classes[action_name] = cls.create_action_class(method_name)
|
||||
clazz = cls.create_action_class(method_name)
|
||||
|
||||
try:
|
||||
client_method = clazz.get_fake_client_method()
|
||||
except Exception as e:
|
||||
LOG.debug("Failed to get fake client method: %s" % e)
|
||||
client_method = None
|
||||
|
||||
if client_method:
|
||||
arg_list = i_u.get_arg_list_as_str(client_method)
|
||||
description = i_u.get_docstring(client_method)
|
||||
else:
|
||||
arg_list = ''
|
||||
description = None
|
||||
|
||||
action_classes.append(
|
||||
{
|
||||
'class': clazz,
|
||||
'name': "%s.%s" % (cls.action_namespace, action_name),
|
||||
'description': description,
|
||||
'arg_list': arg_list,
|
||||
}
|
||||
)
|
||||
|
||||
return action_classes
|
||||
|
@ -16,6 +16,7 @@ import inspect
|
||||
|
||||
from glanceclient.v2 import client as glanceclient
|
||||
from heatclient.v1 import client as heatclient
|
||||
from keystoneclient import httpclient
|
||||
from keystoneclient.v3 import client as keystoneclient
|
||||
from neutronclient.v2_0 import client as neutronclient
|
||||
from novaclient.v1_1 import client as novaclient
|
||||
@ -52,6 +53,10 @@ class GlanceAction(base.OpenStackAction):
|
||||
|
||||
return self._client_class(endpoint_url, token=ctx.auth_token)
|
||||
|
||||
@classmethod
|
||||
def _get_fake_client(cls):
|
||||
return cls._client_class("")
|
||||
|
||||
|
||||
class KeystoneAction(base.OpenStackAction):
|
||||
_client_class = keystoneclient.Client
|
||||
@ -63,6 +68,19 @@ class KeystoneAction(base.OpenStackAction):
|
||||
return self._client_class(token=ctx.auth_token,
|
||||
auth_url=auth_url)
|
||||
|
||||
@classmethod
|
||||
def _get_fake_client(cls):
|
||||
# Here we need to replace httpclient authenticate method temporarily
|
||||
authenticate = httpclient.HTTPClient.authenticate
|
||||
|
||||
httpclient.HTTPClient.authenticate = lambda x: True
|
||||
fake_client = cls._client_class()
|
||||
|
||||
# Once we get fake client, return back authenticate method
|
||||
httpclient.HTTPClient.authenticate = authenticate
|
||||
|
||||
return fake_client
|
||||
|
||||
|
||||
class HeatAction(base.OpenStackAction):
|
||||
_client_class = heatclient.Client
|
||||
@ -76,9 +94,13 @@ class HeatAction(base.OpenStackAction):
|
||||
token=ctx.auth_token,
|
||||
username=ctx.user_name)
|
||||
|
||||
@classmethod
|
||||
def _get_fake_client(cls):
|
||||
return cls._client_class("")
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
method = self._get_client_method()
|
||||
method = self._get_client_method(self._get_client())
|
||||
result = method(**self._kwargs_for_run)
|
||||
if inspect.isgenerator(result):
|
||||
return [v for v in result]
|
||||
|
@ -41,18 +41,33 @@ class OpenStackAction(base.Action):
|
||||
"""
|
||||
pass
|
||||
|
||||
def _get_client_method(self):
|
||||
hierarchy_list = self.client_method_name.split('.')
|
||||
attribute = self._get_client()
|
||||
@classmethod
|
||||
def _get_client_method(cls, client):
|
||||
hierarchy_list = cls.client_method_name.split('.')
|
||||
attribute = client
|
||||
|
||||
for attr in hierarchy_list:
|
||||
attribute = getattr(attribute, attr)
|
||||
|
||||
return attribute
|
||||
|
||||
@classmethod
|
||||
def _get_fake_client(cls):
|
||||
"""Returns python-client instance which initiated via wrong args.
|
||||
|
||||
It is needed for getting client-method args and description for
|
||||
saving into DB.
|
||||
"""
|
||||
# Default is simple _client_class instance
|
||||
return cls._client_class()
|
||||
|
||||
@classmethod
|
||||
def get_fake_client_method(cls):
|
||||
return cls._get_client_method(cls._get_fake_client())
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
method = self._get_client_method()
|
||||
method = self._get_client_method(self._get_client())
|
||||
|
||||
return method(**self._kwargs_for_run)
|
||||
except Exception as e:
|
||||
|
@ -11,7 +11,6 @@
|
||||
"availability_zones_find": "availability_zones.find",
|
||||
"availability_zones_findall": "availability_zones.findall",
|
||||
"availability_zones_list": "availability_zones.list",
|
||||
"availability_zones_return_parameter_name": "availability_zones.return_parameter_name",
|
||||
"certs_create": "certs.create",
|
||||
"certs_get": "certs.get",
|
||||
"dns_domains_create_private": "dns_domains.create_private",
|
||||
@ -36,7 +35,6 @@
|
||||
"flavors_find": "flavors.find",
|
||||
"flavors_findall": "flavors.findall",
|
||||
"flavors_get": "flavors.get",
|
||||
"flavors_is_alphanum_id_allowed": "flavors.is_alphanum_id_allowed",
|
||||
"flavors_list": "flavors.list",
|
||||
"floating_ip_pools_find": "floating_ip_pools.find",
|
||||
"floating_ip_pools_findall": "floating_ip_pools.findall",
|
||||
@ -73,8 +71,6 @@
|
||||
"keypairs_find": "keypairs.find",
|
||||
"keypairs_findall": "keypairs.findall",
|
||||
"keypairs_get": "keypairs.get",
|
||||
"keypairs_is_alphanum_id_allowed": "keypairs.is_alphanum_id_allowed",
|
||||
"keypairs_keypair_prefix": "keypairs.keypair_prefix",
|
||||
"keypairs_list": "keypairs.list",
|
||||
"networks_add": "networks.add",
|
||||
"networks_associate_host": "networks.associate_host",
|
||||
@ -195,10 +191,8 @@
|
||||
"image_members_create": "image_members.create",
|
||||
"image_members_delete": "image_members.delete",
|
||||
"image_members_list": "image_members.list",
|
||||
"image_members_model": "image_members.model",
|
||||
"image_members_update": "image_members.update",
|
||||
"image_tags_delete": "image_tags.delete",
|
||||
"image_tags_model": "image_tags.model",
|
||||
"image_tags_update": "image_tags.update",
|
||||
"images_add_location": "images.add_location",
|
||||
"images_create": "images.create",
|
||||
@ -207,7 +201,6 @@
|
||||
"images_delete_locations": "images.delete_locations",
|
||||
"images_get": "images.get",
|
||||
"images_list": "images.list",
|
||||
"images_model": "images.model",
|
||||
"images_update": "images.update",
|
||||
"images_update_location": "images.update_location",
|
||||
"images_upload": "images.upload",
|
||||
@ -233,18 +226,37 @@
|
||||
"endpoints_get": "endpoints.get",
|
||||
"endpoints_list": "endpoints.list",
|
||||
"endpoints_update": "endpoints.update",
|
||||
"federation_identity_providers": "federation.identity_providers",
|
||||
"federation_mappings": "federation.mappings",
|
||||
"federation_protocols": "federation.protocols",
|
||||
"groups_create": "groups.create",
|
||||
"groups_delete": "groups.delete",
|
||||
"groups_find": "groups.find",
|
||||
"groups_get": "groups.get",
|
||||
"groups_list": "groups.list",
|
||||
"groups_update": "groups.update",
|
||||
"oauth1_access_tokens": "oauth1.access_tokens",
|
||||
"oauth1_consumers": "oauth1.consumers",
|
||||
"oauth1_request_tokens": "oauth1.request_tokens",
|
||||
"oauth1.consumers_build_url": "oauth1.consumers.build_url",
|
||||
"oauth1.consumers_create": "oauth1.consumers.create",
|
||||
"oauth1.consumers_delete": "oauth1.consumers.delete",
|
||||
"oauth1.consumers_find": "oauth1.consumers.find",
|
||||
"oauth1.consumers_get": "oauth1.consumers.get",
|
||||
"oauth1.consumers_list": "oauth1.consumers.list",
|
||||
"oauth1.consumers_put": "oauth1.consumers.put",
|
||||
"oauth1.consumers_update": "oauth1.consumers.update",
|
||||
"oauth1.request_tokens_authorize": "oauth1.request_tokens.authorize",
|
||||
"oauth1.request_tokens_build_url": "oauth1.request_tokens.build_url",
|
||||
"oauth1.request_tokens_create": "oauth1.request_tokens.create",
|
||||
"oauth1.request_tokens_delete": "oauth1.request_tokens.delete",
|
||||
"oauth1.request_tokens_find": "oauth1.request_tokens.find",
|
||||
"oauth1.request_tokens_get": "oauth1.request_tokens.get",
|
||||
"oauth1.request_tokens_list": "oauth1.request_tokens.list",
|
||||
"oauth1.request_tokens_put": "oauth1.request_tokens.put",
|
||||
"oauth1.request_tokens_update": "oauth1.request_tokens.update",
|
||||
"oauth1.access_tokens_build_url": "oauth1.access_tokens.build_url",
|
||||
"oauth1.access_tokens_create": "oauth1.access_tokens.create",
|
||||
"oauth1.access_tokens_delete": "oauth1.access_tokens.delete",
|
||||
"oauth1.access_tokens_find": "oauth1.access_tokens.find",
|
||||
"oauth1.access_tokens_get": "oauth1.access_tokens.get",
|
||||
"oauth1.access_tokens_list": "oauth1.access_tokens.list",
|
||||
"oauth1.access_tokens_put": "oauth1.access_tokens.put",
|
||||
"oauth1.access_tokens_update": "oauth1.access_tokens.update",
|
||||
"policies_create": "policies.create",
|
||||
"policies_delete": "policies.delete",
|
||||
"policies_find": "policies.find",
|
||||
@ -264,7 +276,6 @@
|
||||
"regions_list": "regions.list",
|
||||
"regions_update": "regions.update",
|
||||
"roles_check": "roles.check",
|
||||
"roles_client": "roles.client",
|
||||
"roles_create": "roles.create",
|
||||
"roles_delete": "roles.delete",
|
||||
"roles_find": "roles.find",
|
||||
@ -303,7 +314,6 @@
|
||||
"users_list": "users.list",
|
||||
"users_put": "users.put",
|
||||
"users_remove_from_group": "users.remove_from_group",
|
||||
"users_resource_class": "users.resource_class",
|
||||
"users_update": "users.update",
|
||||
"users_update_password": "users.update_password"
|
||||
},
|
||||
|
@ -149,7 +149,7 @@ class Action(mb.MistralModelBase):
|
||||
name = sa.Column(sa.String(200))
|
||||
description = sa.Column(sa.Text())
|
||||
tags = sa.Column(st.JsonListType())
|
||||
input = sa.Column(sa.String(240))
|
||||
input = sa.Column(sa.Text())
|
||||
|
||||
# Ad-hoc action properties.
|
||||
definition = sa.Column(sa.Text(), nullable=True)
|
||||
|
@ -64,23 +64,22 @@ def sync_db():
|
||||
|
||||
def _register_dynamic_action_classes():
|
||||
for generator in generator_factory.all_generators():
|
||||
action_classes = generator.create_action_classes()
|
||||
actions = generator.create_actions()
|
||||
|
||||
module = generator.base_action_class.__module__
|
||||
class_name = generator.base_action_class.__name__
|
||||
|
||||
action_class_str = "%s.%s" % (module, class_name)
|
||||
|
||||
for action_name, action in action_classes.items():
|
||||
full_action_name =\
|
||||
"%s.%s" % (generator.action_namespace, action_name)
|
||||
|
||||
attrs = i_utils.get_public_fields(action)
|
||||
for action in actions:
|
||||
attrs = i_utils.get_public_fields(action['class'])
|
||||
|
||||
_register_action_in_db(
|
||||
full_action_name,
|
||||
action['name'],
|
||||
action_class_str,
|
||||
attrs
|
||||
attrs,
|
||||
action['description'],
|
||||
action['arg_list']
|
||||
)
|
||||
|
||||
|
||||
|
@ -12,21 +12,21 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from mistral.actions.openstack.action_generator import generators
|
||||
from mistral.actions.openstack import actions
|
||||
from mistral.tests import base
|
||||
|
||||
|
||||
class GlanceGeneratorTest(base.BaseTestCase):
|
||||
class GlanceGeneratorTest(base.BaseTest):
|
||||
def test_generator(self):
|
||||
action_name = "glance.images_list"
|
||||
generator = generators.GlanceActionGenerator
|
||||
action_classes = generator.create_action_classes()
|
||||
short_action_name = action_name.split(".")[1]
|
||||
action_class = action_classes[short_action_name]
|
||||
action_classes = generator.create_actions()
|
||||
action = self._assert_single_item(
|
||||
action_classes,
|
||||
name=action_name
|
||||
)
|
||||
|
||||
self.assertIsNotNone(generator)
|
||||
self.assertIn(short_action_name, action_classes)
|
||||
self.assertTrue(issubclass(action_class, actions.GlanceAction))
|
||||
self.assertEqual("images.list", action_class.client_method_name)
|
||||
self.assertTrue(issubclass(action['class'], actions.GlanceAction))
|
||||
self.assertEqual("images.list", action['class'].client_method_name)
|
||||
|
@ -12,21 +12,21 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from mistral.actions.openstack.action_generator import generators
|
||||
from mistral.actions.openstack import actions
|
||||
from mistral.tests import base
|
||||
|
||||
|
||||
class HeatGeneratorTest(base.BaseTestCase):
|
||||
class HeatGeneratorTest(base.BaseTest):
|
||||
def test_generator(self):
|
||||
action_name = "heat.stacks_list"
|
||||
generator = generators.HeatActionGenerator
|
||||
action_classes = generator.create_action_classes()
|
||||
short_action_name = action_name.split(".")[1]
|
||||
action_class = action_classes[short_action_name]
|
||||
action_classes = generator.create_actions()
|
||||
action = self._assert_single_item(
|
||||
action_classes,
|
||||
name=action_name
|
||||
)
|
||||
|
||||
self.assertIsNotNone(generator)
|
||||
self.assertIn(short_action_name, action_classes)
|
||||
self.assertTrue(issubclass(action_class, actions.HeatAction))
|
||||
self.assertEqual("stacks.list", action_class.client_method_name)
|
||||
self.assertTrue(issubclass(action['class'], actions.HeatAction))
|
||||
self.assertEqual("stacks.list", action['class'].client_method_name)
|
||||
|
@ -12,21 +12,21 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from mistral.actions.openstack.action_generator import generators
|
||||
from mistral.actions.openstack import actions
|
||||
from mistral.tests import base
|
||||
|
||||
|
||||
class KeystoneGeneratorTest(base.BaseTestCase):
|
||||
class KeystoneGeneratorTest(base.BaseTest):
|
||||
def test_generator(self):
|
||||
action_name = "keystone.users_create"
|
||||
generator = generators.KeystoneActionGenerator
|
||||
action_classes = generator.create_action_classes()
|
||||
short_action_name = action_name.split(".")[1]
|
||||
action_class = action_classes[short_action_name]
|
||||
action_classes = generator.create_actions()
|
||||
action = self._assert_single_item(
|
||||
action_classes,
|
||||
name=action_name
|
||||
)
|
||||
|
||||
self.assertIsNotNone(generator)
|
||||
self.assertIn(short_action_name, action_classes)
|
||||
self.assertTrue(issubclass(action_class, actions.KeystoneAction))
|
||||
self.assertEqual("users.create", action_class.client_method_name)
|
||||
self.assertTrue(issubclass(action['class'], actions.KeystoneAction))
|
||||
self.assertEqual("users.create", action['class'].client_method_name)
|
||||
|
@ -12,21 +12,21 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from mistral.actions.openstack.action_generator import generators
|
||||
from mistral.actions.openstack import actions
|
||||
from mistral.tests import base
|
||||
|
||||
|
||||
class NeutronGeneratorTest(base.BaseTestCase):
|
||||
class NeutronGeneratorTest(base.BaseTest):
|
||||
def test_generator(self):
|
||||
action_name = "neutron.show_network"
|
||||
generator = generators.NeutronActionGenerator
|
||||
action_classes = generator.create_action_classes()
|
||||
short_action_name = action_name.split(".")[1]
|
||||
action_class = action_classes[short_action_name]
|
||||
action_classes = generator.create_actions()
|
||||
action = self._assert_single_item(
|
||||
action_classes,
|
||||
name=action_name
|
||||
)
|
||||
|
||||
self.assertIsNotNone(generator)
|
||||
self.assertIn(short_action_name, action_classes)
|
||||
self.assertTrue(issubclass(action_class, actions.NeutronAction))
|
||||
self.assertEqual("show_network", action_class.client_method_name)
|
||||
self.assertTrue(issubclass(action['class'], actions.NeutronAction))
|
||||
self.assertEqual("show_network", action['class'].client_method_name)
|
||||
|
@ -12,21 +12,21 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from mistral.actions.openstack.action_generator import generators
|
||||
from mistral.actions.openstack import actions
|
||||
from mistral.tests import base
|
||||
|
||||
|
||||
class NovaGeneratorTest(base.BaseTestCase):
|
||||
class NovaGeneratorTest(base.BaseTest):
|
||||
def test_generator(self):
|
||||
action_name = "nova.servers_get"
|
||||
generator = generators.NovaActionGenerator
|
||||
action_classes = generator.create_action_classes()
|
||||
short_action_name = action_name.split(".")[1]
|
||||
action_class = action_classes[short_action_name]
|
||||
action_classes = generator.create_actions()
|
||||
action = self._assert_single_item(
|
||||
action_classes,
|
||||
name=action_name
|
||||
)
|
||||
|
||||
self.assertIsNotNone(generator)
|
||||
self.assertIn(short_action_name, action_classes)
|
||||
self.assertTrue(issubclass(action_class, actions.NovaAction))
|
||||
self.assertEqual("servers.get", action_class.client_method_name)
|
||||
self.assertTrue(issubclass(action['class'], actions.NovaAction))
|
||||
self.assertEqual("servers.get", action['class'].client_method_name)
|
||||
|
@ -43,7 +43,8 @@ def get_arg_list_as_str(func):
|
||||
defs = list(argspec.defaults or [])
|
||||
|
||||
args = argspec.args
|
||||
args.remove('self')
|
||||
if 'self' in args:
|
||||
args.remove('self')
|
||||
|
||||
diff_args_defs = len(args) - len(defs)
|
||||
arg_str_list = []
|
||||
|
Loading…
Reference in New Issue
Block a user