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:
Nikolay Mahotkin 2014-10-06 16:17:37 +04:00
parent 412f5ced97
commit 57cc6864a7
13 changed files with 174 additions and 84 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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