Allow for dynamic class loading of target types
Tosca types need to be mapped to corresponding target translation types (i.e. HOT). This commit allows for the target types to be dynamically loaded from a directory. It allows for more flexibility by pulling types from a known directory instead of defining each individiual type statically in the code. For example, with this commit you no longer need a separate import for each target type. Also, this commit adds the notion of a global configuration object that can be shared throughout the translator. The configuration defines the location directory for custom defined target types. This configuration can be set by the user in the corresponding translator/conf/translator.conf file. In the future, additional values can be added to this configuration and the code can be extended to support them, but only the required values were implemented here. Change-Id: If7b8da12eef5b8ed8a2e11b1f412203d4ed59c5a Implements: blueprint dynamic-tosca-to-hot-map
This commit is contained in:
parent
0a3a2b2bf6
commit
3f98139a7e
0
contrib/hot/__init__.py
Normal file
0
contrib/hot/__init__.py
Normal file
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
48
translator/common/exception.py
Normal file
48
translator/common/exception.py
Normal file
@ -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.')
|
36
translator/conf/__init__.py
Normal file
36
translator/conf/__init__.py
Normal file
@ -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()
|
67
translator/conf/config.py
Normal file
67
translator/conf/config.py
Normal file
@ -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
|
4
translator/conf/translator.conf
Normal file
4
translator/conf/translator.conf
Normal file
@ -0,0 +1,4 @@
|
||||
[DEFAULT]
|
||||
|
||||
# Relative path location for custom types
|
||||
custom_types_location=contrib/hot
|
@ -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())
|
@ -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())
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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.'''
|
||||
|
@ -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.'''
|
||||
|
@ -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.'''
|
||||
|
@ -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.'''
|
||||
|
@ -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')
|
||||
|
@ -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.'''
|
||||
|
@ -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.'''
|
||||
|
@ -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.'''
|
||||
|
@ -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.'''
|
||||
|
57
translator/tests/test_conf.py
Normal file
57
translator/tests/test_conf.py
Normal file
@ -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')
|
Loading…
Reference in New Issue
Block a user