docs: fix issues
[parser.git] / tosca2heat / heat-translator-0.3.0 / translator / common / utils.py
1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
2 #    not use this file except in compliance with the License. You may obtain
3 #    a copy of the License at
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
7 #    Unless required by applicable law or agreed to in writing, software
8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 #    License for the specific language governing permissions and limitations
11 #    under the License.
12
13
14 import logging
15 import math
16 import numbers
17 import os
18 import re
19 from six.moves.urllib.parse import urlparse
20 import yaml
21
22 from toscaparser.utils.gettextutils import _
23 import toscaparser.utils.yamlparser
24
25 YAML_ORDER_PARSER = toscaparser.utils.yamlparser.simple_ordered_parse
26 log = logging.getLogger('tosca')
27 log = logging.getLogger('heat-translator')
28
29
30 class MemoryUnit(object):
31
32     UNIT_SIZE_DEFAULT = 'B'
33     UNIT_SIZE_DICT = {'B': 1, 'kB': 1000, 'KiB': 1024, 'MB': 1000000,
34                       'MiB': 1048576, 'GB': 1000000000,
35                       'GiB': 1073741824, 'TB': 1000000000000,
36                       'TiB': 1099511627776}
37
38     @staticmethod
39     def convert_unit_size_to_num(size, unit=None):
40         """Convert given size to a number representing given unit.
41
42         If unit is None, convert to a number representing UNIT_SIZE_DEFAULT
43         :param size: unit size e.g. 1 TB
44         :param unit: unit to be converted to e.g GB
45         :return: converted number e.g. 1000 for 1 TB size and unit GB
46         """
47         if unit:
48             unit = MemoryUnit.validate_unit(unit)
49         else:
50             unit = MemoryUnit.UNIT_SIZE_DEFAULT
51             log.info(_('A memory unit is not provided for size; using the '
52                        'default unit %(default)s') % {'default': 'B'})
53         regex = re.compile('(\d*)\s*(\w*)')
54         result = regex.match(str(size)).groups()
55         if result[1]:
56             unit_size = MemoryUnit.validate_unit(result[1])
57             converted = int(str_to_num(result[0])
58                             * MemoryUnit.UNIT_SIZE_DICT[unit_size]
59                             * math.pow(MemoryUnit.UNIT_SIZE_DICT
60                                        [unit], -1))
61             log.info(_('Given size %(size)s is converted to %(num)s '
62                        '%(unit)s') % {'size': size,
63                      'num': converted, 'unit': unit})
64         else:
65             converted = (str_to_num(result[0]))
66         return converted
67
68     @staticmethod
69     def validate_unit(unit):
70         if unit in MemoryUnit.UNIT_SIZE_DICT.keys():
71             return unit
72         else:
73             for key in MemoryUnit.UNIT_SIZE_DICT.keys():
74                 if key.upper() == unit.upper():
75                     return key
76
77             msg = _('Provided unit "{0}" is not valid. The valid units are'
78                     ' {1}').format(unit, MemoryUnit.UNIT_SIZE_DICT.keys())
79             log.warning(msg)
80             raise ValueError(msg)
81
82
83 class CompareUtils(object):
84
85     MISMATCH_VALUE1_LABEL = "<Expected>"
86     MISMATCH_VALUE2_LABEL = "<Provided>"
87     ORDERLESS_LIST_KEYS = ['allowed_values', 'depends_on']
88
89     @staticmethod
90     def compare_dicts(dict1, dict2):
91         """Return False if not equal, True if both are equal."""
92
93         if dict1 is None and dict2 is None:
94             return True
95         if dict1 is None or dict2 is None:
96             return False
97
98         both_equal = True
99         for dict1_item, dict2_item in zip(dict1.items(), dict2.items()):
100             if dict1_item != dict2_item:
101                 msg = (_("%(label1)s: %(item1)s \n is not equal to \n:"
102                          "%(label2)s: %(item2)s")
103                        % {'label1': CompareUtils.MISMATCH_VALUE2_LABEL,
104                           'item1': dict1_item,
105                           'label2': CompareUtils.MISMATCH_VALUE1_LABEL,
106                           'item2': dict2_item})
107                 log.warning(msg)
108                 both_equal = False
109                 break
110         return both_equal
111
112     @staticmethod
113     def compare_hot_yamls(generated_yaml, expected_yaml):
114         hot_translated_dict = YAML_ORDER_PARSER(generated_yaml)
115         hot_expected_dict = YAML_ORDER_PARSER(expected_yaml)
116         return CompareUtils.compare_dicts(hot_translated_dict,
117                                           hot_expected_dict)
118
119     @staticmethod
120     def reorder(dic):
121         '''Canonicalize list items in the dictionary for ease of comparison.
122
123         For properties whose value is a list in which the order does not
124         matter, some pre-processing is required to bring those lists into a
125         canonical format. We use sorting just to make sure such differences
126         in ordering would not cause to a mismatch.
127         '''
128
129         if type(dic) is not dict:
130             return None
131
132         reordered = {}
133         for key in dic.keys():
134             value = dic[key]
135             if type(value) is dict:
136                 reordered[key] = CompareUtils.reorder(value)
137             elif type(value) is list \
138                 and key in CompareUtils.ORDERLESS_LIST_KEYS:
139                 reordered[key] = sorted(value)
140             else:
141                 reordered[key] = value
142         return reordered
143
144     @staticmethod
145     def diff_dicts(dict1, dict2, reorder=True):
146         '''Compares two dictionaries and returns their differences.
147
148         Returns a dictionary of mismatches between the two dictionaries.
149         An empty dictionary is returned if two dictionaries are equivalent.
150         The reorder parameter indicates whether reordering is required
151         before comparison or not.
152         '''
153
154         if reorder:
155             dict1 = CompareUtils.reorder(dict1)
156             dict2 = CompareUtils.reorder(dict2)
157
158         if dict1 is None and dict2 is None:
159             return {}
160         if dict1 is None or dict2 is None:
161             return {CompareUtils.MISMATCH_VALUE1_LABEL: dict1,
162                     CompareUtils.MISMATCH_VALUE2_LABEL: dict2}
163
164         diff = {}
165         keys1 = set(dict1.keys())
166         keys2 = set(dict2.keys())
167         for key in keys1.union(keys2):
168             if key in keys1 and key not in keys2:
169                 diff[key] = {CompareUtils.MISMATCH_VALUE1_LABEL: dict1[key],
170                              CompareUtils.MISMATCH_VALUE2_LABEL: None}
171             elif key not in keys1 and key in keys2:
172                 diff[key] = {CompareUtils.MISMATCH_VALUE1_LABEL: None,
173                              CompareUtils.MISMATCH_VALUE2_LABEL: dict2[key]}
174             else:
175                 val1 = dict1[key]
176                 val2 = dict2[key]
177                 if val1 != val2:
178                     if type(val1) is dict and type(val2) is dict:
179                         diff[key] = CompareUtils.diff_dicts(val1, val2, False)
180                     else:
181                         diff[key] = {CompareUtils.MISMATCH_VALUE1_LABEL: val1,
182                                      CompareUtils.MISMATCH_VALUE2_LABEL: val2}
183         return diff
184
185
186 class YamlUtils(object):
187
188     @staticmethod
189     def get_dict(yaml_file):
190         '''Returns the dictionary representation of the given YAML spec.'''
191         try:
192             return yaml.load(open(yaml_file))
193         except IOError:
194             return None
195
196     @staticmethod
197     def compare_yamls(yaml1_file, yaml2_file):
198         '''Returns true if two dictionaries are equivalent, false otherwise.'''
199         dict1 = YamlUtils.get_dict(yaml1_file)
200         dict2 = YamlUtils.get_dict(yaml2_file)
201         return CompareUtils.compare_dicts(dict1, dict2)
202
203     @staticmethod
204     def compare_yaml_dict(yaml_file, dic):
205         '''Returns true if yaml matches the dictionary, false otherwise.'''
206         return CompareUtils.compare_dicts(YamlUtils.get_dict(yaml_file), dic)
207
208
209 class TranslationUtils(object):
210
211     @staticmethod
212     def compare_tosca_translation_with_hot(tosca_file, hot_file, params):
213         '''Verify tosca translation against the given hot specification.
214
215         inputs:
216         tosca_file: relative local path or URL to the tosca input file
217         hot_file: relative path to expected hot output
218         params: dictionary of parameter name value pairs
219
220         Returns as a dictionary the difference between the HOT translation
221         of the given tosca_file and the given hot_file.
222         '''
223
224         from toscaparser.tosca_template import ToscaTemplate
225         from translator.hot.tosca_translator import TOSCATranslator
226
227         tosca_tpl = os.path.normpath(os.path.join(
228             os.path.dirname(os.path.abspath(__file__)), tosca_file))
229         a_file = os.path.isfile(tosca_tpl)
230         if not a_file:
231             tosca_tpl = tosca_file
232
233         expected_hot_tpl = os.path.join(
234             os.path.dirname(os.path.abspath(__file__)), hot_file)
235
236         tosca = ToscaTemplate(tosca_tpl, params, a_file)
237         translate = TOSCATranslator(tosca, params)
238
239         output = translate.translate()
240         output_dict = toscaparser.utils.yamlparser.simple_parse(output)
241         expected_output_dict = YamlUtils.get_dict(expected_hot_tpl)
242         return CompareUtils.diff_dicts(output_dict, expected_output_dict)
243
244
245 class UrlUtils(object):
246
247     @staticmethod
248     def validate_url(path):
249         """Validates whether the given path is a URL or not.
250
251         If the given path includes a scheme (http, https, ftp, ...) and a net
252         location (a domain name such as www.github.com) it is validated as a
253         URL.
254         """
255         parsed = urlparse(path)
256         return bool(parsed.scheme) and bool(parsed.netloc)
257
258
259 def str_to_num(value):
260     """Convert a string representation of a number into a numeric type."""
261     if isinstance(value, numbers.Number):
262         return value
263     try:
264         return int(value)
265     except ValueError:
266         return float(value)