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:
Julio Ruano 2015-08-24 21:15:33 -05:00
parent 0a3a2b2bf6
commit 3f98139a7e
29 changed files with 373 additions and 142 deletions

0
contrib/hot/__init__.py Normal file
View File

View 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'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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.')

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

View File

@ -0,0 +1,4 @@
[DEFAULT]
# Relative path location for custom types
custom_types_location=contrib/hot

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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')