diff --git a/translator/hot/tosca/custom_types/__init__.py b/contrib/__init__.py similarity index 100% rename from translator/hot/tosca/custom_types/__init__.py rename to contrib/__init__.py diff --git a/contrib/hot/__init__.py b/contrib/hot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/translator/hot/tosca/custom_types/tosca_collectd.py b/contrib/hot/tosca_collectd.py similarity index 82% rename from translator/hot/tosca/custom_types/tosca_collectd.py rename to contrib/hot/tosca_collectd.py index e4063dff..ec517d00 100755 --- a/translator/hot/tosca/custom_types/tosca_collectd.py +++ b/contrib/hot/tosca_collectd.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaCollectd' + class ToscaCollectd(HotResource): '''Translate TOSCA node type tosca.nodes.SoftwareComponent.Collectd.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.SoftwareComponent.Collectd' diff --git a/translator/hot/tosca/custom_types/tosca_elasticsearch.py b/contrib/hot/tosca_elasticsearch.py similarity index 83% rename from translator/hot/tosca/custom_types/tosca_elasticsearch.py rename to contrib/hot/tosca_elasticsearch.py index 036ea53f..34d76703 100755 --- a/translator/hot/tosca/custom_types/tosca_elasticsearch.py +++ b/contrib/hot/tosca_elasticsearch.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaElasticsearch' + class ToscaElasticsearch(HotResource): '''Translate TOSCA type tosca.nodes.SoftwareComponent.Elasticsearch.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.SoftwareComponent.Elasticsearch' diff --git a/translator/hot/tosca/custom_types/tosca_kibana.py b/contrib/hot/tosca_kibana.py similarity index 82% rename from translator/hot/tosca/custom_types/tosca_kibana.py rename to contrib/hot/tosca_kibana.py index 8f0be73d..71f34026 100755 --- a/translator/hot/tosca/custom_types/tosca_kibana.py +++ b/contrib/hot/tosca_kibana.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaKibana' + class ToscaKibana(HotResource): '''Translate TOSCA node type tosca.nodes.SoftwareComponent.Kibana.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.SoftwareComponent.Kibana' diff --git a/translator/hot/tosca/custom_types/tosca_logstash.py b/contrib/hot/tosca_logstash.py similarity index 82% rename from translator/hot/tosca/custom_types/tosca_logstash.py rename to contrib/hot/tosca_logstash.py index 0259cd33..39859e06 100755 --- a/translator/hot/tosca/custom_types/tosca_logstash.py +++ b/contrib/hot/tosca_logstash.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaLogstash' + class ToscaLogstash(HotResource): '''Translate TOSCA node type tosca.nodes.SoftwareComponent.Logstash.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.SoftwareComponent.Logstash' diff --git a/translator/hot/tosca/custom_types/tosca_nodejs.py b/contrib/hot/tosca_nodejs.py similarity index 82% rename from translator/hot/tosca/custom_types/tosca_nodejs.py rename to contrib/hot/tosca_nodejs.py index 99fad698..2102af5a 100755 --- a/translator/hot/tosca/custom_types/tosca_nodejs.py +++ b/contrib/hot/tosca_nodejs.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaNodejs' + class ToscaNodejs(HotResource): '''Translate TOSCA node type tosca.nodes.SoftwareComponent.Nodejs.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.SoftwareComponent.Nodejs' diff --git a/translator/hot/tosca/custom_types/tosca_paypalpizzastore.py b/contrib/hot/tosca_paypalpizzastore.py similarity index 83% rename from translator/hot/tosca/custom_types/tosca_paypalpizzastore.py rename to contrib/hot/tosca_paypalpizzastore.py index cd62dc47..ae3865b5 100755 --- a/translator/hot/tosca/custom_types/tosca_paypalpizzastore.py +++ b/contrib/hot/tosca_paypalpizzastore.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaPaypalPizzaStore' + class ToscaPaypalPizzaStore(HotResource): '''Translate TOSCA type tosca.nodes.WebApplication.PayPalPizzaStore.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.WebApplication.PayPalPizzaStore' diff --git a/translator/hot/tosca/custom_types/tosca_rsyslog.py b/contrib/hot/tosca_rsyslog.py similarity index 82% rename from translator/hot/tosca/custom_types/tosca_rsyslog.py rename to contrib/hot/tosca_rsyslog.py index 70b843b3..9604d3c9 100755 --- a/translator/hot/tosca/custom_types/tosca_rsyslog.py +++ b/contrib/hot/tosca_rsyslog.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaRsyslog' + class ToscaRsyslog(HotResource): '''Translate TOSCA node type tosca.nodes.SoftwareComponent.Rsyslog.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.SoftwareComponent.Rsyslog' diff --git a/translator/hot/tosca/custom_types/tosca_wordpress.py b/contrib/hot/tosca_wordpress.py similarity index 82% rename from translator/hot/tosca/custom_types/tosca_wordpress.py rename to contrib/hot/tosca_wordpress.py index 21409ade..c20fe6a8 100755 --- a/translator/hot/tosca/custom_types/tosca_wordpress.py +++ b/contrib/hot/tosca_wordpress.py @@ -13,12 +13,12 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaWordpress' + class ToscaWordpress(HotResource): '''Translate TOSCA node type tosca.nodes.WebApplication.WordPress.''' - # TODO(anyone): this is a custom TOSCA type so it should be kept separate - # from the TOSCA base types; need to come up with a scheme so new custom - # types can be added by users. toscatype = 'tosca.nodes.WebApplication.WordPress' diff --git a/translator/common/exception.py b/translator/common/exception.py new file mode 100644 index 00000000..be861169 --- /dev/null +++ b/translator/common/exception.py @@ -0,0 +1,48 @@ +# 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. + +''' +Exceptions for the TOSCA Translator package. +''' + +from toscaparser.common.exception import TOSCAException +from toscaparser.utils.gettextutils import _ + + +class ConfFileParseError(TOSCAException): + msg_fmt = _('%(message)s') + + +class ConfOptionNotDefined(TOSCAException): + msg_fmt = _('Option %(key)s in section %(section)s ' + 'is not defined in conf file') + + +class ConfSectionNotDefined(TOSCAException): + msg_fmt = _('Section %(section)s is not defined in conf file') + + +class ToscaModImportError(TOSCAException): + msg_fmt = _('Unable to import module %(mod_name)s. ' + 'Check to see that it exists and has no ' + 'language definition errors.') + + +class ToscaClassImportError(TOSCAException): + msg_fmt = _('Unable to import class %(name)s in ' + 'module %(mod_name)s. Check to see that it ' + 'exists and has no language definition errors.') + + +class ToscaClassAttributeError(TOSCAException): + msg_fmt = _('Class attribute referenced not found. ' + '%(message)s. Check to see that it is defined.') diff --git a/translator/conf/__init__.py b/translator/conf/__init__.py new file mode 100644 index 00000000..2c61252a --- /dev/null +++ b/translator/conf/__init__.py @@ -0,0 +1,36 @@ +# +# 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. + +''' Initialize the global configuration for the translator ''' + +import os + +from translator.conf.config import ConfigProvider + +CONF_FILENAME = 'translator.conf' + + +def init_global_conf(): + '''Initialize the configuration provider. + + Allows the configuration to be shared throughout the translator code. + The file used is translator.conf, and is within the conf/ directory. It + is a standard ini format, and is prcessed using the ConfigParser module. + + ''' + conf_path = os.path.dirname(os.path.abspath(__file__)) + conf_file = os.path.join(conf_path, CONF_FILENAME) + ConfigProvider._load_config(conf_file) + + +init_global_conf() diff --git a/translator/conf/config.py b/translator/conf/config.py new file mode 100644 index 00000000..4e8fe87c --- /dev/null +++ b/translator/conf/config.py @@ -0,0 +1,67 @@ +# +# 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. + +''' Provide a global configuration for the TOSCA translator''' + +from six.moves import configparser + +from toscaparser.utils.gettextutils import _ +import translator.common.exception as exception + + +class ConfigProvider(object): + '''Global config proxy that wraps a ConfigParser object. + + Allows for class based access to config values. Should only be initialized + once using the corresponding translator.conf file in the conf directory. + + ''' + + # List that captures all of the conf file sections. + # Append any new sections to this list. + _sections = ['DEFAULT'] + _translator_config = None + + @classmethod + def _load_config(cls, conf_file): + '''Private method only to be called once from the __init__ module''' + + cls._translator_config = configparser.ConfigParser() + try: + cls._translator_config.read(conf_file) + except configparser.ParsingError: + msg = _('Unable to parse translator.conf file.' + 'Check to see that it exists in the conf directory.') + raise exception.ConfFileParseError(message=msg) + + @classmethod + def get_value(cls, section, key): + try: + value = cls._translator_config.get(section, key) + except configparser.NoOptionError: + raise exception.ConfOptionNotDefined(key=key, section=section) + except configparser.NoSectionError: + raise exception.ConfSectionNotDefined(section=section) + + return value + + @classmethod + def get_all_values(cls): + values = [] + for section in cls._sections: + try: + values.extend(cls._translator_config.items(section=section)) + except configparser.NoOptionError: + raise exception.ConfSectionNotDefined(section=section) + + return values diff --git a/translator/conf/translator.conf b/translator/conf/translator.conf new file mode 100644 index 00000000..62962594 --- /dev/null +++ b/translator/conf/translator.conf @@ -0,0 +1,4 @@ +[DEFAULT] + +# Relative path location for custom types +custom_types_location=contrib/hot \ No newline at end of file diff --git a/translator/hot/tosca/tests/test_tosca_elasticsearch.py b/translator/hot/tosca/tests/test_tosca_elasticsearch.py deleted file mode 100644 index 12ae4397..00000000 --- a/translator/hot/tosca/tests/test_tosca_elasticsearch.py +++ /dev/null @@ -1,37 +0,0 @@ -# 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 toscaparser.tests.base import TestCase -from translator.hot.tosca.custom_types.tosca_elasticsearch import ( - ToscaElasticsearch - ) - - -class ToscaElasticsearchTest(TestCase): - - @mock.patch('toscaparser.nodetemplate.NodeTemplate') - @mock.patch('translator.hot.tosca.custom_types.tosca_elasticsearch.' - 'HotResource.__init__') - def test_init(self, mock_hotres_init, mock_node): - ToscaElasticsearch(mock_node) - # Make sure base class gets called - mock_hotres_init.assert_called_once_with(mock_node) - self.assertEqual(ToscaElasticsearch.toscatype, - 'tosca.nodes.SoftwareComponent.Elasticsearch') - - @mock.patch('toscaparser.nodetemplate.NodeTemplate') - @mock.patch('translator.hot.tosca.custom_types.tosca_elasticsearch.' - 'HotResource.__init__') - def test_handle_properties(self, mock_hotres_init, mock_node): - p = ToscaElasticsearch(mock_node) - self.assertIsNone(p.handle_properties()) diff --git a/translator/hot/tosca/tests/test_tosca_kibana.py b/translator/hot/tosca/tests/test_tosca_kibana.py deleted file mode 100644 index d34e6cbd..00000000 --- a/translator/hot/tosca/tests/test_tosca_kibana.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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 toscaparser.tests.base import TestCase -from translator.hot.tosca.custom_types.tosca_kibana import ToscaKibana - - -class ToscaKibanaTest(TestCase): - - @mock.patch('toscaparser.nodetemplate.NodeTemplate') - @mock.patch('translator.hot.tosca.custom_types.tosca_kibana.' - 'HotResource.__init__') - def test_init(self, mock_hotres_init, mock_node): - ToscaKibana(mock_node) - # Make sure base class gets called - mock_hotres_init.assert_called_once_with(mock_node) - self.assertEqual(ToscaKibana.toscatype, - 'tosca.nodes.SoftwareComponent.Kibana') - - @mock.patch('toscaparser.nodetemplate.NodeTemplate') - @mock.patch('translator.hot.tosca.custom_types.tosca_kibana.' - 'HotResource.__init__') - def test_handle_properties(self, mock_hotres_init, mock_node): - p = ToscaKibana(mock_node) - self.assertIsNone(p.handle_properties()) diff --git a/translator/hot/tosca/tosca_block_storage.py b/translator/hot/tosca/tosca_block_storage.py index 6b14ac17..482db3eb 100644 --- a/translator/hot/tosca/tosca_block_storage.py +++ b/translator/hot/tosca/tosca_block_storage.py @@ -20,10 +20,15 @@ from translator.hot.syntax.hot_resource import HotResource log = logging.getLogger("tosca") +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaBlockStorage' + class ToscaBlockStorage(HotResource): '''Translate TOSCA node type tosca.nodes.BlockStorage.''' + toscatype = 'tosca.nodes.BlockStorage' + def __init__(self, nodetemplate): super(ToscaBlockStorage, self).__init__(nodetemplate, type='OS::Cinder::Volume') diff --git a/translator/hot/tosca/tosca_block_storage_attachment.py b/translator/hot/tosca/tosca_block_storage_attachment.py index 3c8a1c47..715d5b3d 100644 --- a/translator/hot/tosca/tosca_block_storage_attachment.py +++ b/translator/hot/tosca/tosca_block_storage_attachment.py @@ -14,10 +14,15 @@ from toscaparser.functions import GetInput from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaBlockStorageAttachment' + class ToscaBlockStorageAttachment(HotResource): '''Translate TOSCA relationship AttachesTo for Compute and BlockStorage.''' + toscatype = 'tosca.nodes.BlockStorageAttachment' + def __init__(self, template, nodetemplates, instance_uuid, volume_id): super(ToscaBlockStorageAttachment, self).__init__(template, type='OS::Cinder::VolumeAttachment') diff --git a/translator/hot/tosca/tosca_compute.py b/translator/hot/tosca/tosca_compute.py index bb05f032..75445779 100755 --- a/translator/hot/tosca/tosca_compute.py +++ b/translator/hot/tosca/tosca_compute.py @@ -17,6 +17,10 @@ import translator.common.utils from translator.hot.syntax.hot_resource import HotResource log = logging.getLogger('tosca') +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaCompute' + + # A design issue to be resolved is how to translate the generic TOSCA server # properties to OpenStack flavors and images. At the Atlanta design summit, # there was discussion on using Glance to store metadata and Graffiti to diff --git a/translator/hot/tosca/tosca_database.py b/translator/hot/tosca/tosca_database.py index 253cfcf0..26c9d4d8 100755 --- a/translator/hot/tosca/tosca_database.py +++ b/translator/hot/tosca/tosca_database.py @@ -13,6 +13,9 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaDatabase' + class ToscaDatabase(HotResource): '''Translate TOSCA node type tosca.nodes.Database.''' diff --git a/translator/hot/tosca/tosca_dbms.py b/translator/hot/tosca/tosca_dbms.py index 227f6c14..38c31bd3 100755 --- a/translator/hot/tosca/tosca_dbms.py +++ b/translator/hot/tosca/tosca_dbms.py @@ -13,6 +13,9 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaDbms' + class ToscaDbms(HotResource): '''Translate TOSCA node type tosca.nodes.DBMS.''' diff --git a/translator/hot/tosca/tosca_network_network.py b/translator/hot/tosca/tosca_network_network.py index 6cf7a562..909c1b75 100644 --- a/translator/hot/tosca/tosca_network_network.py +++ b/translator/hot/tosca/tosca_network_network.py @@ -14,6 +14,9 @@ from toscaparser.common.exception import InvalidPropertyValueError from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaNetwork' + class ToscaNetwork(HotResource): '''Translate TOSCA node type tosca.nodes.network.Network.''' diff --git a/translator/hot/tosca/tosca_network_port.py b/translator/hot/tosca/tosca_network_port.py index e3f99e2e..f5f0b259 100644 --- a/translator/hot/tosca/tosca_network_port.py +++ b/translator/hot/tosca/tosca_network_port.py @@ -13,6 +13,9 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaNetworkPort' + class ToscaNetworkPort(HotResource): '''Translate TOSCA node type tosca.nodes.network.Port.''' diff --git a/translator/hot/tosca/tosca_object_storage.py b/translator/hot/tosca/tosca_object_storage.py index 0fe2d7bb..ed283b2f 100644 --- a/translator/hot/tosca/tosca_object_storage.py +++ b/translator/hot/tosca/tosca_object_storage.py @@ -14,10 +14,15 @@ from toscaparser.elements.scalarunit import ScalarUnit_Size from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaObjectStorage' + class ToscaObjectStorage(HotResource): '''Translate TOSCA node type tosca.nodes.ObjectStorage.''' + toscatype = 'tosca.nodes.ObjectStorage' + def __init__(self, nodetemplate): super(ToscaObjectStorage, self).__init__(nodetemplate, type='OS::Swift::Container') diff --git a/translator/hot/tosca/tosca_software_component.py b/translator/hot/tosca/tosca_software_component.py index fef3b795..044de43a 100755 --- a/translator/hot/tosca/tosca_software_component.py +++ b/translator/hot/tosca/tosca_software_component.py @@ -13,6 +13,9 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaSoftwareComponent' + class ToscaSoftwareComponent(HotResource): '''Translate TOSCA node type tosca.nodes.SoftwareComponent.''' diff --git a/translator/hot/tosca/tosca_web_application.py b/translator/hot/tosca/tosca_web_application.py index 31e0314e..d0a9c5d0 100755 --- a/translator/hot/tosca/tosca_web_application.py +++ b/translator/hot/tosca/tosca_web_application.py @@ -13,6 +13,9 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaWebApplication' + class ToscaWebApplication(HotResource): '''Translate TOSCA node type tosca.nodes.WebApplication.''' diff --git a/translator/hot/tosca/tosca_webserver.py b/translator/hot/tosca/tosca_webserver.py index 488d31ae..83bda802 100755 --- a/translator/hot/tosca/tosca_webserver.py +++ b/translator/hot/tosca/tosca_webserver.py @@ -13,6 +13,9 @@ from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaWebserver' + class ToscaWebserver(HotResource): '''Translate TOSCA node type tosca.nodes.WebServer.''' diff --git a/translator/hot/translate_node_templates.py b/translator/hot/translate_node_templates.py index 035807ba..c70d670d 100644 --- a/translator/hot/translate_node_templates.py +++ b/translator/hot/translate_node_templates.py @@ -11,42 +11,113 @@ # License for the specific language governing permissions and limitations # under the License. +import imp import logging +import os import six from toscaparser.functions import GetAttribute from toscaparser.functions import GetInput from toscaparser.functions import GetProperty from toscaparser.relationship_template import RelationshipTemplate +from translator.common.exception import ToscaClassAttributeError +from translator.common.exception import ToscaClassImportError +from translator.common.exception import ToscaModImportError +from translator.conf.config import ConfigProvider as translatorConfig from translator.hot.syntax.hot_resource import HotResource -from translator.hot.tosca.custom_types.tosca_collectd import ToscaCollectd -from translator.hot.tosca.custom_types.tosca_elasticsearch import ( - ToscaElasticsearch - ) -from translator.hot.tosca.custom_types.tosca_kibana import ToscaKibana -from translator.hot.tosca.custom_types.tosca_logstash import ToscaLogstash -from translator.hot.tosca.custom_types.tosca_nodejs import ToscaNodejs -from translator.hot.tosca.custom_types.tosca_paypalpizzastore import ( - ToscaPaypalPizzaStore - ) -from translator.hot.tosca.custom_types.tosca_rsyslog import ToscaRsyslog -from translator.hot.tosca.custom_types.tosca_wordpress import ToscaWordpress -from translator.hot.tosca.tosca_block_storage import ToscaBlockStorage from translator.hot.tosca.tosca_block_storage_attachment import ( ToscaBlockStorageAttachment ) -from translator.hot.tosca.tosca_compute import ToscaCompute -from translator.hot.tosca.tosca_database import ToscaDatabase -from translator.hot.tosca.tosca_dbms import ToscaDbms -from translator.hot.tosca.tosca_network_network import ToscaNetwork -from translator.hot.tosca.tosca_network_port import ToscaNetworkPort -from translator.hot.tosca.tosca_object_storage import ToscaObjectStorage -from translator.hot.tosca.tosca_software_component import ( - ToscaSoftwareComponent - ) -from translator.hot.tosca.tosca_web_application import ToscaWebApplication -from translator.hot.tosca.tosca_webserver import ToscaWebserver +########################### +# Module utility Functions +# for dynamic class loading +########################### + + +def _generate_type_map(): + '''Generate TOSCA translation types map. + + Load user defined classes from location path specified in conf file. + Base classes are located within the tosca directory. + + ''' + + # Base types directory + BASE_PATH = 'translator/hot/tosca' + + # Custom types directory defined in conf file + custom_path = translatorConfig.get_value('DEFAULT', + 'custom_types_location') + + # First need to load the parent module, for example 'contrib.hot', + # for all of the dynamically loaded classes. + _load_custom_mod(custom_path) + classes = [] + _load_classes((BASE_PATH, custom_path), classes) + try: + types_map = {clazz.toscatype: clazz for clazz in classes} + except AttributeError as e: + raise ToscaClassAttributeError(message=e.message) + + return types_map + + +def _load_custom_mod(custom_path): + '''Dynamically load the parent module for all the custom types.''' + + try: + fp, filename, desc = imp.find_module(custom_path) + imp.load_module(custom_path.replace('/', '.'), + fp, filename, desc) + except ImportError: + raise ToscaModImportError(mod_name=custom_path) + finally: + if fp: + fp.close() + + +def _load_classes(locations, classes): + '''Dynamically load all the classes from the given locations.''' + + for cls_path in locations: + # Grab all the tosca type module files in the given path + mod_files = [f for f in os.listdir(cls_path) if f.endswith('.py') + and not f.startswith('__init__') + and f.startswith('tosca_')] + + # For each module, pick out the target translation class + for f in mod_files: + # NOTE: For some reason the existing code does not use the map to + # instantiate ToscaBlockStorageAttachment. Don't add it to the map + # here until the dependent code is fixed to use the map. + if f == 'tosca_block_storage_attachment.py': + continue + + mod_name = cls_path + '/' + f.strip('.py') + try: + fp, filename, desc = imp.find_module(mod_name) + mod = imp.load_module(mod_name.replace('/', '.'), + fp, filename, desc) + target_name = getattr(mod, 'TARGET_CLASS_NAME') + clazz = getattr(mod, target_name) + classes.append(clazz) + except ImportError: + raise ToscaModImportError(mod_name=mod_name) + except AttributeError: + if target_name: + raise ToscaClassImportError(name=target_name, + mod_name=mod_name) + else: + # TARGET_CLASS_NAME is not defined in module. + # Re-raise the exception + raise + finally: + fp.close() + +################## +# Module constants +################## SECTIONS = (TYPE, PROPERTIES, REQUIREMENTS, INTERFACES, LIFECYCLE, INPUT) = \ ('type', 'properties', 'requirements', @@ -64,28 +135,6 @@ REQUIRES = (CONTAINER, DEPENDENCY, DATABASE_ENDPOINT, CONNECTION, HOST) = \ INTERFACES_STATE = (CREATE, START, CONFIGURE, START, DELETE) = \ ('create', 'stop', 'configure', 'start', 'delete') -# dict to look up HOT translation class, -# TODO(replace with function to scan the classes in translator.hot.tosca) -TOSCA_TO_HOT_TYPE = {'tosca.nodes.Compute': ToscaCompute, - 'tosca.nodes.WebServer': ToscaWebserver, - 'tosca.nodes.DBMS': ToscaDbms, - 'tosca.nodes.Database': ToscaDatabase, - 'tosca.nodes.WebApplication': ToscaWebApplication, - 'tosca.nodes.WebApplication.WordPress': ToscaWordpress, - 'tosca.nodes.BlockStorage': ToscaBlockStorage, - 'tosca.nodes.SoftwareComponent': ToscaSoftwareComponent, - 'tosca.nodes.SoftwareComponent.Nodejs': ToscaNodejs, - 'tosca.nodes.network.Network': ToscaNetwork, - 'tosca.nodes.network.Port': ToscaNetworkPort, - 'tosca.nodes.ObjectStorage': ToscaObjectStorage, - 'tosca.nodes.SoftwareComponent.Collectd': ToscaCollectd, - 'tosca.nodes.SoftwareComponent.Rsyslog': ToscaRsyslog, - 'tosca.nodes.SoftwareComponent.Kibana': ToscaKibana, - 'tosca.nodes.SoftwareComponent.Logstash': ToscaLogstash, - 'tosca.nodes.SoftwareComponent.Elasticsearch': - ToscaElasticsearch, - 'tosca.nodes.WebApplication.PayPalPizzaStore': - ToscaPaypalPizzaStore} TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server', 'dependency': 'depends_on', "connects": 'depends_on'} @@ -93,6 +142,8 @@ TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server', TOSCA_TO_HOT_PROPERTIES = {'properties': 'input'} log = logging.getLogger('heat-translator') +TOSCA_TO_HOT_TYPE = _generate_type_map() + class TranslateNodeTemplates(object): '''Translate TOSCA NodeTemplates to Heat Resources.''' diff --git a/translator/tests/test_conf.py b/translator/tests/test_conf.py new file mode 100644 index 00000000..6506c272 --- /dev/null +++ b/translator/tests/test_conf.py @@ -0,0 +1,57 @@ +# 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 +import os + +from translator.conf.config import ConfigProvider as translatorConfig +from translator.tests.base import TestCase + + +def reload_config(func): + '''Decorator to reload config. + + Set to default values defined in translator.conf file + + ''' + + def reload(*args): + func(*args) + path = os.path.dirname(os.path.abspath(__file__)) + '/../conf/' + conf_file = os.path.join(path, 'translator.conf') + translatorConfig._load_config(conf_file) + + return reload + + +class ConfTest(TestCase): + + @reload_config + @mock.patch('six.moves.configparser.ConfigParser') + def test_load_config(self, mock_config_parser): + translatorConfig._translator_config.read = mock.MagicMock() + translatorConfig._load_config('fake_file.conf') + self.assertTrue(translatorConfig._translator_config.read.called) + + def test_get_value(self): + ret_value = mock.MagicMock(return_value='hot') + translatorConfig._translator_config.get = ret_value + value = translatorConfig.get_value('DEFAULT', 'language') + self.assertTrue(translatorConfig._translator_config.get.called) + self.assertEqual(value, 'hot') + + def test_get_all_values(self): + ret_value = mock.MagicMock(return_value=['hot']) + translatorConfig._translator_config.items = ret_value + values = translatorConfig.get_all_values() + self.assertTrue(translatorConfig._translator_config.items.called) + self.assertEqual(values[0], 'hot')