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