Merge "update docs build names in .gitignore"
[parser.git] / tosca2heat / heat-translator / 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 json
15 import logging
16 import math
17 import numbers
18 import os
19 import re
20 import requests
21 from six.moves.urllib.parse import urlparse
22 import yaml
23
24 from toscaparser.utils.gettextutils import _
25 import toscaparser.utils.yamlparser
26
27 YAML_ORDER_PARSER = toscaparser.utils.yamlparser.simple_ordered_parse
28 log = logging.getLogger('heat-translator')
29
30 # Required environment variables to create openstackclient object.
31 ENV_VARIABLES = ['OS_AUTH_URL', 'OS_PASSWORD', 'OS_USERNAME', 'OS_TENANT_NAME']
32
33
34 class MemoryUnit(object):
35
36     UNIT_SIZE_DEFAULT = 'B'
37     UNIT_SIZE_DICT = {'B': 1, 'kB': 1000, 'KiB': 1024, 'MB': 1000000,
38                       'MiB': 1048576, 'GB': 1000000000,
39                       'GiB': 1073741824, 'TB': 1000000000000,
40                       'TiB': 1099511627776}
41
42     @staticmethod
43     def convert_unit_size_to_num(size, unit=None):
44         """Convert given size to a number representing given unit.
45
46         If unit is None, convert to a number representing UNIT_SIZE_DEFAULT
47         :param size: unit size e.g. 1 TB
48         :param unit: unit to be converted to e.g GB
49         :return: converted number e.g. 1000 for 1 TB size and unit GB
50         """
51         if unit:
52             unit = MemoryUnit.validate_unit(unit)
53         else:
54             unit = MemoryUnit.UNIT_SIZE_DEFAULT
55             log.info(_('A memory unit is not provided for size; using the '
56                        'default unit %(default)s.') % {'default': 'B'})
57         regex = re.compile('(\d*)\s*(\w*)')
58         result = regex.match(str(size)).groups()
59         if result[1]:
60             unit_size = MemoryUnit.validate_unit(result[1])
61             converted = int(str_to_num(result[0])
62                             * MemoryUnit.UNIT_SIZE_DICT[unit_size]
63                             * math.pow(MemoryUnit.UNIT_SIZE_DICT
64                                        [unit], -1))
65             log.info(_('Given size %(size)s is converted to %(num)s '
66                        '%(unit)s.') % {'size': size,
67                      'num': converted, 'unit': unit})
68         else:
69             converted = (str_to_num(result[0]))
70         return converted
71
72     @staticmethod
73     def validate_unit(unit):
74         if unit in MemoryUnit.UNIT_SIZE_DICT.keys():
75             return unit
76         else:
77             for key in MemoryUnit.UNIT_SIZE_DICT.keys():
78                 if key.upper() == unit.upper():
79                     return key
80
81             msg = _('Provided unit "{0}" is not valid. The valid units are'
82                     ' {1}').format(unit, MemoryUnit.UNIT_SIZE_DICT.keys())
83             log.error(msg)
84             raise ValueError(msg)
85
86
87 class CompareUtils(object):
88
89     MISMATCH_VALUE1_LABEL = "<Expected>"
90     MISMATCH_VALUE2_LABEL = "<Provided>"
91     ORDERLESS_LIST_KEYS = ['allowed_values', 'depends_on']
92
93     @staticmethod
94     def compare_dicts(dict1, dict2):
95         """Return False if not equal, True if both are equal."""
96
97         if dict1 is None and dict2 is None:
98             return True
99         if dict1 is None or dict2 is None:
100             return False
101
102         both_equal = True
103         for dict1_item, dict2_item in zip(dict1.items(), dict2.items()):
104             if dict1_item != dict2_item:
105                 msg = (_("%(label1)s: %(item1)s \n is not equal to \n:"
106                          "%(label2)s: %(item2)s")
107                        % {'label1': CompareUtils.MISMATCH_VALUE2_LABEL,
108                           'item1': dict1_item,
109                           'label2': CompareUtils.MISMATCH_VALUE1_LABEL,
110                           'item2': dict2_item})
111                 log.warning(msg)
112                 both_equal = False
113                 break
114         return both_equal
115
116     @staticmethod
117     def compare_hot_yamls(generated_yaml, expected_yaml):
118         hot_translated_dict = YAML_ORDER_PARSER(generated_yaml)
119         hot_expected_dict = YAML_ORDER_PARSER(expected_yaml)
120         return CompareUtils.compare_dicts(hot_translated_dict,
121                                           hot_expected_dict)
122
123     @staticmethod
124     def reorder(dic):
125         '''Canonicalize list items in the dictionary for ease of comparison.
126
127         For properties whose value is a list in which the order does not
128         matter, some pre-processing is required to bring those lists into a
129         canonical format. We use sorting just to make sure such differences
130         in ordering would not cause to a mismatch.
131         '''
132
133         if type(dic) is not dict:
134             return None
135
136         reordered = {}
137         for key in dic.keys():
138             value = dic[key]
139             if type(value) is dict:
140                 reordered[key] = CompareUtils.reorder(value)
141             elif type(value) is list \
142                 and key in CompareUtils.ORDERLESS_LIST_KEYS:
143                 reordered[key] = sorted(value)
144             else:
145                 reordered[key] = value
146         return reordered
147
148     @staticmethod
149     def diff_dicts(dict1, dict2, reorder=True):
150         '''Compares two dictionaries and returns their differences.
151
152         Returns a dictionary of mismatches between the two dictionaries.
153         An empty dictionary is returned if two dictionaries are equivalent.
154         The reorder parameter indicates whether reordering is required
155         before comparison or not.
156         '''
157
158         if reorder:
159             dict1 = CompareUtils.reorder(dict1)
160             dict2 = CompareUtils.reorder(dict2)
161
162         if dict1 is None and dict2 is None:
163             return {}
164         if dict1 is None or dict2 is None:
165             return {CompareUtils.MISMATCH_VALUE1_LABEL: dict1,
166                     CompareUtils.MISMATCH_VALUE2_LABEL: dict2}
167
168         diff = {}
169         keys1 = set(dict1.keys())
170         keys2 = set(dict2.keys())
171         for key in keys1.union(keys2):
172             if key in keys1 and key not in keys2:
173                 diff[key] = {CompareUtils.MISMATCH_VALUE1_LABEL: dict1[key],
174                              CompareUtils.MISMATCH_VALUE2_LABEL: None}
175             elif key not in keys1 and key in keys2:
176                 diff[key] = {CompareUtils.MISMATCH_VALUE1_LABEL: None,
177                              CompareUtils.MISMATCH_VALUE2_LABEL: dict2[key]}
178             else:
179                 val1 = dict1[key]
180                 val2 = dict2[key]
181                 if val1 != val2:
182                     if type(val1) is dict and type(val2) is dict:
183                         diff[key] = CompareUtils.diff_dicts(val1, val2, False)
184                     else:
185                         diff[key] = {CompareUtils.MISMATCH_VALUE1_LABEL: val1,
186                                      CompareUtils.MISMATCH_VALUE2_LABEL: val2}
187         return diff
188
189
190 class YamlUtils(object):
191
192     @staticmethod
193     def get_dict(yaml_file):
194         '''Returns the dictionary representation of the given YAML spec.'''
195         try:
196             return yaml.load(open(yaml_file))
197         except IOError:
198             return None
199
200     @staticmethod
201     def compare_yamls(yaml1_file, yaml2_file):
202         '''Returns true if two dictionaries are equivalent, false otherwise.'''
203         dict1 = YamlUtils.get_dict(yaml1_file)
204         dict2 = YamlUtils.get_dict(yaml2_file)
205         return CompareUtils.compare_dicts(dict1, dict2)
206
207     @staticmethod
208     def compare_yaml_dict(yaml_file, dic):
209         '''Returns true if yaml matches the dictionary, false otherwise.'''
210         return CompareUtils.compare_dicts(YamlUtils.get_dict(yaml_file), dic)
211
212
213 class TranslationUtils(object):
214
215     @staticmethod
216     def compare_tosca_translation_with_hot(tosca_file, hot_file, params):
217         '''Verify tosca translation against the given hot specification.
218
219         inputs:
220         tosca_file: relative local path or URL to the tosca input file
221         hot_file: relative path to expected hot output
222         params: dictionary of parameter name value pairs
223
224         Returns as a dictionary the difference between the HOT translation
225         of the given tosca_file and the given hot_file.
226         '''
227
228         from toscaparser.tosca_template import ToscaTemplate
229         from translator.hot.tosca_translator import TOSCATranslator
230
231         tosca_tpl = os.path.normpath(os.path.join(
232             os.path.dirname(os.path.abspath(__file__)), tosca_file))
233         a_file = os.path.isfile(tosca_tpl)
234         if not a_file:
235             tosca_tpl = tosca_file
236
237         expected_hot_tpl = os.path.join(
238             os.path.dirname(os.path.abspath(__file__)), hot_file)
239
240         tosca = ToscaTemplate(tosca_tpl, params, a_file)
241         translate = TOSCATranslator(tosca, params)
242
243         output = translate.translate()
244         output_dict = toscaparser.utils.yamlparser.simple_parse(output)
245         expected_output_dict = YamlUtils.get_dict(expected_hot_tpl)
246         return CompareUtils.diff_dicts(output_dict, expected_output_dict)
247
248
249 class UrlUtils(object):
250
251     @staticmethod
252     def validate_url(path):
253         """Validates whether the given path is a URL or not.
254
255         If the given path includes a scheme (http, https, ftp, ...) and a net
256         location (a domain name such as www.github.com) it is validated as a
257         URL.
258         """
259         parsed = urlparse(path)
260         return bool(parsed.scheme) and bool(parsed.netloc)
261
262
263 def str_to_num(value):
264     """Convert a string representation of a number into a numeric type."""
265     if isinstance(value, numbers.Number):
266         return value
267     try:
268         return int(value)
269     except ValueError:
270         return float(value)
271
272
273 def check_for_env_variables():
274     return set(ENV_VARIABLES) < set(os.environ.keys())
275
276
277 def get_ks_access_dict():
278     tenant_name = os.getenv('OS_TENANT_NAME')
279     username = os.getenv('OS_USERNAME')
280     password = os.getenv('OS_PASSWORD')
281     auth_url = os.getenv('OS_AUTH_URL')
282
283     auth_dict = {
284         "auth": {
285             "tenantName": tenant_name,
286             "passwordCredentials": {
287                 "username": username,
288                 "password": password
289             }
290         }
291     }
292     headers = {'Content-Type': 'application/json'}
293     try:
294         keystone_response = requests.post(auth_url + '/tokens',
295                                           data=json.dumps(auth_dict),
296                                           headers=headers)
297         if keystone_response.status_code != 200:
298             return None
299         return json.loads(keystone_response.content)
300     except Exception:
301         return None
302
303
304 def get_url_for(access_dict, service_type):
305     if access_dict is None:
306         return None
307     service_catalog = access_dict['access']['serviceCatalog']
308     service_url = ''
309     for service in service_catalog:
310         if service['type'] == service_type:
311             service_url = service['endpoints'][0]['publicURL']
312             break
313     return service_url
314
315
316 def get_token_id(access_dict):
317     if access_dict is None:
318         return None
319     return access_dict['access']['token']['id']