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