1# -*- coding: utf-8 -*- 2# Copyright (c), Google Inc, 2017 3# Simplified BSD License (see licenses/simplified_bsd.txt or 4# https://opensource.org/licenses/BSD-2-Clause) 5 6from __future__ import (absolute_import, division, print_function) 7__metaclass__ = type 8 9import re 10import time 11import traceback 12 13THIRD_LIBRARIES_IMP_ERR = None 14try: 15 from keystoneauth1.adapter import Adapter 16 from keystoneauth1.identity import v3 17 from keystoneauth1 import session 18 HAS_THIRD_LIBRARIES = True 19except ImportError: 20 THIRD_LIBRARIES_IMP_ERR = traceback.format_exc() 21 HAS_THIRD_LIBRARIES = False 22 23from ansible.module_utils.basic import (AnsibleModule, env_fallback, 24 missing_required_lib) 25from ansible.module_utils.common.text.converters import to_text 26 27 28class HwcModuleException(Exception): 29 def __init__(self, message): 30 super(HwcModuleException, self).__init__() 31 32 self._message = message 33 34 def __str__(self): 35 return "[HwcClientException] message=%s" % self._message 36 37 38class HwcClientException(Exception): 39 def __init__(self, code, message): 40 super(HwcClientException, self).__init__() 41 42 self._code = code 43 self._message = message 44 45 def __str__(self): 46 msg = " code=%s," % str(self._code) if self._code != 0 else "" 47 return "[HwcClientException]%s message=%s" % ( 48 msg, self._message) 49 50 51class HwcClientException404(HwcClientException): 52 def __init__(self, message): 53 super(HwcClientException404, self).__init__(404, message) 54 55 def __str__(self): 56 return "[HwcClientException404] message=%s" % self._message 57 58 59def session_method_wrapper(f): 60 def _wrap(self, url, *args, **kwargs): 61 try: 62 url = self.endpoint + url 63 r = f(self, url, *args, **kwargs) 64 except Exception as ex: 65 raise HwcClientException( 66 0, "Sending request failed, error=%s" % ex) 67 68 result = None 69 if r.content: 70 try: 71 result = r.json() 72 except Exception as ex: 73 raise HwcClientException( 74 0, "Parsing response to json failed, error: %s" % ex) 75 76 code = r.status_code 77 if code not in [200, 201, 202, 203, 204, 205, 206, 207, 208, 226]: 78 msg = "" 79 for i in ['message', 'error.message']: 80 try: 81 msg = navigate_value(result, i) 82 break 83 except Exception: 84 pass 85 else: 86 msg = str(result) 87 88 if code == 404: 89 raise HwcClientException404(msg) 90 91 raise HwcClientException(code, msg) 92 93 return result 94 95 return _wrap 96 97 98class _ServiceClient(object): 99 def __init__(self, client, endpoint, product): 100 self._client = client 101 self._endpoint = endpoint 102 self._default_header = { 103 'User-Agent': "Huawei-Ansible-MM-%s" % product, 104 'Accept': 'application/json', 105 } 106 107 @property 108 def endpoint(self): 109 return self._endpoint 110 111 @endpoint.setter 112 def endpoint(self, e): 113 self._endpoint = e 114 115 @session_method_wrapper 116 def get(self, url, body=None, header=None, timeout=None): 117 return self._client.get(url, json=body, timeout=timeout, 118 headers=self._header(header)) 119 120 @session_method_wrapper 121 def post(self, url, body=None, header=None, timeout=None): 122 return self._client.post(url, json=body, timeout=timeout, 123 headers=self._header(header)) 124 125 @session_method_wrapper 126 def delete(self, url, body=None, header=None, timeout=None): 127 return self._client.delete(url, json=body, timeout=timeout, 128 headers=self._header(header)) 129 130 @session_method_wrapper 131 def put(self, url, body=None, header=None, timeout=None): 132 return self._client.put(url, json=body, timeout=timeout, 133 headers=self._header(header)) 134 135 def _header(self, header): 136 if header and isinstance(header, dict): 137 for k, v in self._default_header.items(): 138 if k not in header: 139 header[k] = v 140 else: 141 header = self._default_header 142 143 return header 144 145 146class Config(object): 147 def __init__(self, module, product): 148 self._project_client = None 149 self._domain_client = None 150 self._module = module 151 self._product = product 152 self._endpoints = {} 153 154 self._validate() 155 self._gen_provider_client() 156 157 @property 158 def module(self): 159 return self._module 160 161 def client(self, region, service_type, service_level): 162 c = self._project_client 163 if service_level == "domain": 164 c = self._domain_client 165 166 e = self._get_service_endpoint(c, service_type, region) 167 168 return _ServiceClient(c, e, self._product) 169 170 def _gen_provider_client(self): 171 m = self._module 172 p = { 173 "auth_url": m.params['identity_endpoint'], 174 "password": m.params['password'], 175 "username": m.params['user'], 176 "project_name": m.params['project'], 177 "user_domain_name": m.params['domain'], 178 "reauthenticate": True 179 } 180 181 self._project_client = Adapter( 182 session.Session(auth=v3.Password(**p)), 183 raise_exc=False) 184 185 p.pop("project_name") 186 self._domain_client = Adapter( 187 session.Session(auth=v3.Password(**p)), 188 raise_exc=False) 189 190 def _get_service_endpoint(self, client, service_type, region): 191 k = "%s.%s" % (service_type, region if region else "") 192 193 if k in self._endpoints: 194 return self._endpoints.get(k) 195 196 url = None 197 try: 198 url = client.get_endpoint(service_type=service_type, 199 region_name=region, interface="public") 200 except Exception as ex: 201 raise HwcClientException( 202 0, "Getting endpoint failed, error=%s" % ex) 203 204 if url == "": 205 raise HwcClientException( 206 0, "Can not find the enpoint for %s" % service_type) 207 208 if url[-1] != "/": 209 url += "/" 210 211 self._endpoints[k] = url 212 return url 213 214 def _validate(self): 215 if not HAS_THIRD_LIBRARIES: 216 self.module.fail_json( 217 msg=missing_required_lib('keystoneauth1'), 218 exception=THIRD_LIBRARIES_IMP_ERR) 219 220 221class HwcModule(AnsibleModule): 222 def __init__(self, *args, **kwargs): 223 arg_spec = kwargs.setdefault('argument_spec', {}) 224 225 arg_spec.update( 226 dict( 227 identity_endpoint=dict( 228 required=True, type='str', 229 fallback=(env_fallback, ['ANSIBLE_HWC_IDENTITY_ENDPOINT']), 230 ), 231 user=dict( 232 required=True, type='str', 233 fallback=(env_fallback, ['ANSIBLE_HWC_USER']), 234 ), 235 password=dict( 236 required=True, type='str', no_log=True, 237 fallback=(env_fallback, ['ANSIBLE_HWC_PASSWORD']), 238 ), 239 domain=dict( 240 required=True, type='str', 241 fallback=(env_fallback, ['ANSIBLE_HWC_DOMAIN']), 242 ), 243 project=dict( 244 required=True, type='str', 245 fallback=(env_fallback, ['ANSIBLE_HWC_PROJECT']), 246 ), 247 region=dict( 248 type='str', 249 fallback=(env_fallback, ['ANSIBLE_HWC_REGION']), 250 ), 251 id=dict(type='str') 252 ) 253 ) 254 255 super(HwcModule, self).__init__(*args, **kwargs) 256 257 258class _DictComparison(object): 259 ''' This class takes in two dictionaries `a` and `b`. 260 These are dictionaries of arbitrary depth, but made up of standard 261 Python types only. 262 This differ will compare all values in `a` to those in `b`. 263 If value in `a` is None, always returns True, indicating 264 this value is no need to compare. 265 Note: On all lists, order does matter. 266 ''' 267 268 def __init__(self, request): 269 self.request = request 270 271 def __eq__(self, other): 272 return self._compare_dicts(self.request, other.request) 273 274 def __ne__(self, other): 275 return not self.__eq__(other) 276 277 def _compare_dicts(self, dict1, dict2): 278 if dict1 is None: 279 return True 280 281 if set(dict1.keys()) != set(dict2.keys()): 282 return False 283 284 for k in dict1: 285 if not self._compare_value(dict1.get(k), dict2.get(k)): 286 return False 287 288 return True 289 290 def _compare_lists(self, list1, list2): 291 """Takes in two lists and compares them.""" 292 if list1 is None: 293 return True 294 295 if len(list1) != len(list2): 296 return False 297 298 for i in range(len(list1)): 299 if not self._compare_value(list1[i], list2[i]): 300 return False 301 302 return True 303 304 def _compare_value(self, value1, value2): 305 """ 306 return: True: value1 is same as value2, otherwise False. 307 """ 308 if value1 is None: 309 return True 310 311 if not (value1 and value2): 312 return (not value1) and (not value2) 313 314 # Can assume non-None types at this point. 315 if isinstance(value1, list) and isinstance(value2, list): 316 return self._compare_lists(value1, value2) 317 318 elif isinstance(value1, dict) and isinstance(value2, dict): 319 return self._compare_dicts(value1, value2) 320 321 # Always use to_text values to avoid unicode issues. 322 return (to_text(value1, errors='surrogate_or_strict') == to_text( 323 value2, errors='surrogate_or_strict')) 324 325 326def wait_to_finish(target, pending, refresh, timeout, min_interval=1, delay=3): 327 is_last_time = False 328 not_found_times = 0 329 wait = 0 330 331 time.sleep(delay) 332 333 end = time.time() + timeout 334 while not is_last_time: 335 if time.time() > end: 336 is_last_time = True 337 338 obj, status = refresh() 339 340 if obj is None: 341 not_found_times += 1 342 343 if not_found_times > 10: 344 raise HwcModuleException( 345 "not found the object for %d times" % not_found_times) 346 else: 347 not_found_times = 0 348 349 if status in target: 350 return obj 351 352 if pending and status not in pending: 353 raise HwcModuleException( 354 "unexpect status(%s) occured" % status) 355 356 if not is_last_time: 357 wait *= 2 358 if wait < min_interval: 359 wait = min_interval 360 elif wait > 10: 361 wait = 10 362 363 time.sleep(wait) 364 365 raise HwcModuleException("asycn wait timeout after %d seconds" % timeout) 366 367 368def navigate_value(data, index, array_index=None): 369 if array_index and (not isinstance(array_index, dict)): 370 raise HwcModuleException("array_index must be dict") 371 372 d = data 373 for n in range(len(index)): 374 if d is None: 375 return None 376 377 if not isinstance(d, dict): 378 raise HwcModuleException( 379 "can't navigate value from a non-dict object") 380 381 i = index[n] 382 if i not in d: 383 raise HwcModuleException( 384 "navigate value failed: key(%s) is not exist in dict" % i) 385 d = d[i] 386 387 if not array_index: 388 continue 389 390 k = ".".join(index[: (n + 1)]) 391 if k not in array_index: 392 continue 393 394 if d is None: 395 return None 396 397 if not isinstance(d, list): 398 raise HwcModuleException( 399 "can't navigate value from a non-list object") 400 401 j = array_index.get(k) 402 if j >= len(d): 403 raise HwcModuleException( 404 "navigate value failed: the index is out of list") 405 d = d[j] 406 407 return d 408 409 410def build_path(module, path, kv=None): 411 if kv is None: 412 kv = dict() 413 414 v = {} 415 for p in re.findall(r"{[^/]*}", path): 416 n = p[1:][:-1] 417 418 if n in kv: 419 v[n] = str(kv[n]) 420 421 else: 422 if n in module.params: 423 v[n] = str(module.params.get(n)) 424 else: 425 v[n] = "" 426 427 return path.format(**v) 428 429 430def get_region(module): 431 if module.params['region']: 432 return module.params['region'] 433 434 return module.params['project'].split("_")[0] 435 436 437def is_empty_value(v): 438 return (not v) 439 440 441def are_different_dicts(dict1, dict2): 442 return _DictComparison(dict1) != _DictComparison(dict2) 443