1#!/usr/bin/python 2 3# (c) 2018 Piotr Olczak <piotr.olczak@redhat.com> 4# (c) 2018-2019, NetApp, Inc 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['deprecated'], 12 'supported_by': 'certified'} 13 14DOCUMENTATION = ''' 15module: na_ontap_gather_facts 16deprecated: 17 removed_in: '2.13' 18 why: Deprecated in favour of C(_info) module. 19 alternative: Use M(na_ontap_info) instead. 20author: Piotr Olczak (@dprts) <polczak@redhat.com> 21extends_documentation_fragment: 22 - netapp.na_ontap 23short_description: NetApp information gatherer 24description: 25 - This module allows you to gather various information about ONTAP configuration 26version_added: "2.7" 27requirements: 28 - netapp_lib 29options: 30 state: 31 description: 32 - Returns "info" 33 default: "info" 34 choices: ['info'] 35 gather_subset: 36 description: 37 - When supplied, this argument will restrict the facts collected 38 to a given subset. Possible values for this argument include 39 "aggregate_info", "cluster_node_info", "igroup_info", "lun_info", "net_dns_info", 40 "net_ifgrp_info", 41 "net_interface_info", "net_port_info", "nvme_info", "nvme_interface_info", 42 "nvme_namespace_info", "nvme_subsystem_info", "ontap_version", 43 "qos_adaptive_policy_info", "qos_policy_info", "security_key_manager_key_info", 44 "security_login_account_info", "storage_failover_info", "volume_info", 45 "vserver_info", "vserver_login_banner_info", "vserver_motd_info", "vserver_nfs_info" 46 Can specify a list of values to include a larger subset. Values can also be used 47 with an initial C(M(!)) to specify that a specific subset should 48 not be collected. 49 - nvme is supported with ONTAP 9.4 onwards. 50 - use "help" to get a list of supported facts for your system. 51 default: "all" 52 version_added: 2.8 53''' 54 55EXAMPLES = ''' 56- name: Get NetApp info (Password Authentication) 57 na_ontap_gather_facts: 58 state: info 59 hostname: "na-vsim" 60 username: "admin" 61 password: "admins_password" 62- debug: 63 var: ontap_facts 64- name: Limit Fact Gathering to Aggregate Information 65 na_ontap_gather_facts: 66 state: info 67 hostname: "na-vsim" 68 username: "admin" 69 password: "admins_password" 70 gather_subset: "aggregate_info" 71- name: Limit Fact Gathering to Volume and Lun Information 72 na_ontap_gather_facts: 73 state: info 74 hostname: "na-vsim" 75 username: "admin" 76 password: "admins_password" 77 gather_subset: 78 - volume_info 79 - lun_info 80- name: Gather all facts except for volume and lun information 81 na_ontap_gather_facts: 82 state: info 83 hostname: "na-vsim" 84 username: "admin" 85 password: "admins_password" 86 gather_subset: 87 - "!volume_info" 88 - "!lun_info" 89''' 90 91RETURN = ''' 92ontap_facts: 93 description: Returns various information about NetApp cluster configuration 94 returned: always 95 type: dict 96 sample: '{ 97 "ontap_facts": { 98 "aggregate_info": {...}, 99 "cluster_node_info": {...}, 100 "net_dns_info": {...}, 101 "net_ifgrp_info": {...}, 102 "net_interface_info": {...}, 103 "net_port_info": {...}, 104 "security_key_manager_key_info": {...}, 105 "security_login_account_info": {...}, 106 "volume_info": {...}, 107 "lun_info": {...}, 108 "storage_failover_info": {...}, 109 "vserver_login_banner_info": {...}, 110 "vserver_motd_info": {...}, 111 "vserver_info": {...}, 112 "vserver_nfs_info": {...}, 113 "ontap_version": {...}, 114 "igroup_info": {...}, 115 "qos_policy_info": {...}, 116 "qos_adaptive_policy_info": {...} 117 }' 118''' 119 120import traceback 121from ansible.module_utils.basic import AnsibleModule 122from ansible.module_utils._text import to_native 123import ansible.module_utils.netapp as netapp_utils 124 125try: 126 import xmltodict 127 HAS_XMLTODICT = True 128except ImportError: 129 HAS_XMLTODICT = False 130 131try: 132 import json 133 HAS_JSON = True 134except ImportError: 135 HAS_JSON = False 136 137HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() 138 139 140class NetAppONTAPGatherFacts(object): 141 '''Class with gather facts methods''' 142 143 def __init__(self, module): 144 self.module = module 145 self.netapp_info = dict() 146 147 # thanks to coreywan (https://github.com/ansible/ansible/pull/47016) 148 # for starting this 149 # min_version identifies the ontapi version which supports this ZAPI 150 # use 0 if it is supported since 9.1 151 self.fact_subsets = { 152 'net_dns_info': { 153 'method': self.get_generic_get_iter, 154 'kwargs': { 155 'call': 'net-dns-get-iter', 156 'attribute': 'net-dns-info', 157 'field': 'vserver-name', 158 'query': {'max-records': '1024'}, 159 }, 160 'min_version': '0', 161 }, 162 'net_interface_info': { 163 'method': self.get_generic_get_iter, 164 'kwargs': { 165 'call': 'net-interface-get-iter', 166 'attribute': 'net-interface-info', 167 'field': 'interface-name', 168 'query': {'max-records': '1024'}, 169 }, 170 'min_version': '0', 171 }, 172 'net_port_info': { 173 'method': self.get_generic_get_iter, 174 'kwargs': { 175 'call': 'net-port-get-iter', 176 'attribute': 'net-port-info', 177 'field': ('node', 'port'), 178 'query': {'max-records': '1024'}, 179 }, 180 'min_version': '0', 181 }, 182 'cluster_node_info': { 183 'method': self.get_generic_get_iter, 184 'kwargs': { 185 'call': 'cluster-node-get-iter', 186 'attribute': 'cluster-node-info', 187 'field': 'node-name', 188 'query': {'max-records': '1024'}, 189 }, 190 'min_version': '0', 191 }, 192 'security_login_account_info': { 193 'method': self.get_generic_get_iter, 194 'kwargs': { 195 'call': 'security-login-get-iter', 196 'attribute': 'security-login-account-info', 197 'field': ('vserver', 'user-name', 'application', 'authentication-method'), 198 'query': {'max-records': '1024'}, 199 }, 200 'min_version': '0', 201 }, 202 'aggregate_info': { 203 'method': self.get_generic_get_iter, 204 'kwargs': { 205 'call': 'aggr-get-iter', 206 'attribute': 'aggr-attributes', 207 'field': 'aggregate-name', 208 'query': {'max-records': '1024'}, 209 }, 210 'min_version': '0', 211 }, 212 'volume_info': { 213 'method': self.get_generic_get_iter, 214 'kwargs': { 215 'call': 'volume-get-iter', 216 'attribute': 'volume-attributes', 217 'field': ('name', 'owning-vserver-name'), 218 'query': {'max-records': '1024'}, 219 }, 220 'min_version': '0', 221 }, 222 'lun_info': { 223 'method': self.get_generic_get_iter, 224 'kwargs': { 225 'call': 'lun-get-iter', 226 'attribute': 'lun-info', 227 'field': 'path', 228 'query': {'max-records': '1024'}, 229 }, 230 'min_version': '0', 231 }, 232 'storage_failover_info': { 233 'method': self.get_generic_get_iter, 234 'kwargs': { 235 'call': 'cf-get-iter', 236 'attribute': 'storage-failover-info', 237 'field': 'node', 238 'query': {'max-records': '1024'}, 239 }, 240 'min_version': '0', 241 }, 242 'vserver_motd_info': { 243 'method': self.get_generic_get_iter, 244 'kwargs': { 245 'call': 'vserver-motd-get-iter', 246 'attribute': 'vserver-motd-info', 247 'field': 'vserver', 248 'query': {'max-records': '1024'}, 249 }, 250 'min_version': '0', 251 }, 252 'vserver_login_banner_info': { 253 'method': self.get_generic_get_iter, 254 'kwargs': { 255 'call': 'vserver-login-banner-get-iter', 256 'attribute': 'vserver-login-banner-info', 257 'field': 'vserver', 258 'query': {'max-records': '1024'}, 259 }, 260 'min_version': '0', 261 }, 262 'security_key_manager_key_info': { 263 'method': self.get_generic_get_iter, 264 'kwargs': { 265 'call': 'security-key-manager-key-get-iter', 266 'attribute': 'security-key-manager-key-info', 267 'field': ('node', 'key-id'), 268 'query': {'max-records': '1024'}, 269 }, 270 'min_version': '0', 271 }, 272 'vserver_info': { 273 'method': self.get_generic_get_iter, 274 'kwargs': { 275 'call': 'vserver-get-iter', 276 'attribute': 'vserver-info', 277 'field': 'vserver-name', 278 'query': {'max-records': '1024'}, 279 }, 280 'min_version': '0', 281 }, 282 'vserver_nfs_info': { 283 'method': self.get_generic_get_iter, 284 'kwargs': { 285 'call': 'nfs-service-get-iter', 286 'attribute': 'nfs-info', 287 'field': 'vserver', 288 'query': {'max-records': '1024'}, 289 }, 290 'min_version': '0', 291 }, 292 'net_ifgrp_info': { 293 'method': self.get_ifgrp_info, 294 'kwargs': {}, 295 'min_version': '0', 296 }, 297 'ontap_version': { 298 'method': self.ontapi, 299 'kwargs': {}, 300 'min_version': '0', 301 }, 302 'system_node_info': { 303 'method': self.get_generic_get_iter, 304 'kwargs': { 305 'call': 'system-node-get-iter', 306 'attribute': 'node-details-info', 307 'field': 'node', 308 'query': {'max-records': '1024'}, 309 }, 310 'min_version': '0', 311 }, 312 'igroup_info': { 313 'method': self.get_generic_get_iter, 314 'kwargs': { 315 'call': 'igroup-get-iter', 316 'attribute': 'initiator-group-info', 317 'field': ('vserver', 'initiator-group-name'), 318 'query': {'max-records': '1024'}, 319 }, 320 'min_version': '0', 321 }, 322 'qos_policy_info': { 323 'method': self.get_generic_get_iter, 324 'kwargs': { 325 'call': 'qos-policy-group-get-iter', 326 'attribute': 'qos-policy-group-info', 327 'field': 'policy-group', 328 'query': {'max-records': '1024'}, 329 }, 330 'min_version': '0', 331 }, 332 # supported in ONTAP 9.3 and onwards 333 'qos_adaptive_policy_info': { 334 'method': self.get_generic_get_iter, 335 'kwargs': { 336 'call': 'qos-adaptive-policy-group-get-iter', 337 'attribute': 'qos-adaptive-policy-group-info', 338 'field': 'policy-group', 339 'query': {'max-records': '1024'}, 340 }, 341 'min_version': '130', 342 }, 343 # supported in ONTAP 9.4 and onwards 344 'nvme_info': { 345 'method': self.get_generic_get_iter, 346 'kwargs': { 347 'call': 'nvme-get-iter', 348 'attribute': 'nvme-target-service-info', 349 'field': 'vserver', 350 'query': {'max-records': '1024'}, 351 }, 352 'min_version': '140', 353 }, 354 'nvme_interface_info': { 355 'method': self.get_generic_get_iter, 356 'kwargs': { 357 'call': 'nvme-interface-get-iter', 358 'attribute': 'nvme-interface-info', 359 'field': 'vserver', 360 'query': {'max-records': '1024'}, 361 }, 362 'min_version': '140', 363 }, 364 'nvme_subsystem_info': { 365 'method': self.get_generic_get_iter, 366 'kwargs': { 367 'call': 'nvme-subsystem-get-iter', 368 'attribute': 'nvme-subsystem-info', 369 'field': 'subsystem', 370 'query': {'max-records': '1024'}, 371 }, 372 'min_version': '140', 373 }, 374 'nvme_namespace_info': { 375 'method': self.get_generic_get_iter, 376 'kwargs': { 377 'call': 'nvme-namespace-get-iter', 378 'attribute': 'nvme-namespace-info', 379 'field': 'path', 380 'query': {'max-records': '1024'}, 381 }, 382 'min_version': '140', 383 }, 384 } 385 386 if HAS_NETAPP_LIB is False: 387 self.module.fail_json(msg="the python NetApp-Lib module is required") 388 else: 389 self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) 390 391 def ontapi(self): 392 '''Method to get ontapi version''' 393 394 api = 'system-get-ontapi-version' 395 api_call = netapp_utils.zapi.NaElement(api) 396 try: 397 results = self.server.invoke_successfully(api_call, enable_tunneling=False) 398 ontapi_version = results.get_child_content('minor-version') 399 return ontapi_version if ontapi_version is not None else '0' 400 except netapp_utils.zapi.NaApiError as error: 401 self.module.fail_json(msg="Error calling API %s: %s" % 402 (api, to_native(error)), exception=traceback.format_exc()) 403 404 def call_api(self, call, query=None): 405 '''Main method to run an API call''' 406 407 api_call = netapp_utils.zapi.NaElement(call) 408 result = None 409 410 if query: 411 for key, val in query.items(): 412 # Can val be nested? 413 api_call.add_new_child(key, val) 414 try: 415 result = self.server.invoke_successfully(api_call, enable_tunneling=False) 416 return result 417 except netapp_utils.zapi.NaApiError as error: 418 if call in ['security-key-manager-key-get-iter']: 419 return result 420 else: 421 self.module.fail_json(msg="Error calling API %s: %s" 422 % (call, to_native(error)), exception=traceback.format_exc()) 423 424 def get_ifgrp_info(self): 425 '''Method to get network port ifgroups info''' 426 427 try: 428 net_port_info = self.netapp_info['net_port_info'] 429 except KeyError: 430 net_port_info_calls = self.fact_subsets['net_port_info'] 431 net_port_info = net_port_info_calls['method'](**net_port_info_calls['kwargs']) 432 interfaces = net_port_info.keys() 433 434 ifgrps = [] 435 for ifn in interfaces: 436 if net_port_info[ifn]['port_type'] == 'if_group': 437 ifgrps.append(ifn) 438 439 net_ifgrp_info = dict() 440 for ifgrp in ifgrps: 441 query = dict() 442 query['node'], query['ifgrp-name'] = ifgrp.split(':') 443 444 tmp = self.get_generic_get_iter('net-port-ifgrp-get', field=('node', 'ifgrp-name'), 445 attribute='net-ifgrp-info', query=query) 446 net_ifgrp_info = net_ifgrp_info.copy() 447 net_ifgrp_info.update(tmp) 448 return net_ifgrp_info 449 450 def get_generic_get_iter(self, call, attribute=None, field=None, query=None): 451 '''Method to run a generic get-iter call''' 452 453 generic_call = self.call_api(call, query) 454 455 if call == 'net-port-ifgrp-get': 456 children = 'attributes' 457 else: 458 children = 'attributes-list' 459 460 if generic_call is None: 461 return None 462 463 if field is None: 464 out = [] 465 else: 466 out = {} 467 468 attributes_list = generic_call.get_child_by_name(children) 469 470 if attributes_list is None: 471 return None 472 473 for child in attributes_list.get_children(): 474 dic = xmltodict.parse(child.to_string(), xml_attribs=False) 475 476 if attribute is not None: 477 dic = dic[attribute] 478 479 if isinstance(field, str): 480 unique_key = _finditem(dic, field) 481 out = out.copy() 482 out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))}) 483 elif isinstance(field, tuple): 484 unique_key = ':'.join([_finditem(dic, el) for el in field]) 485 out = out.copy() 486 out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))}) 487 else: 488 out.append(convert_keys(json.loads(json.dumps(dic)))) 489 490 return out 491 492 def get_all(self, gather_subset): 493 '''Method to get all subsets''' 494 495 results = netapp_utils.get_cserver(self.server) 496 cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) 497 netapp_utils.ems_log_event("na_ontap_gather_facts", cserver) 498 499 self.netapp_info['ontap_version'] = self.ontapi() 500 501 run_subset = self.get_subset(gather_subset, self.netapp_info['ontap_version']) 502 if 'help' in gather_subset: 503 self.netapp_info['help'] = sorted(run_subset) 504 else: 505 for subset in run_subset: 506 call = self.fact_subsets[subset] 507 self.netapp_info[subset] = call['method'](**call['kwargs']) 508 509 return self.netapp_info 510 511 def get_subset(self, gather_subset, version): 512 '''Method to get a single subset''' 513 514 runable_subsets = set() 515 exclude_subsets = set() 516 usable_subsets = [key for key in self.fact_subsets.keys() if version >= self.fact_subsets[key]['min_version']] 517 if 'help' in gather_subset: 518 return usable_subsets 519 for subset in gather_subset: 520 if subset == 'all': 521 runable_subsets.update(usable_subsets) 522 return runable_subsets 523 if subset.startswith('!'): 524 subset = subset[1:] 525 if subset == 'all': 526 return set() 527 exclude = True 528 else: 529 exclude = False 530 531 if subset not in usable_subsets: 532 if subset not in self.fact_subsets.keys(): 533 self.module.fail_json(msg='Bad subset: %s' % subset) 534 self.module.fail_json(msg='Remote system at version %s does not support %s' % 535 (version, subset)) 536 537 if exclude: 538 exclude_subsets.add(subset) 539 else: 540 runable_subsets.add(subset) 541 542 if not runable_subsets: 543 runable_subsets.update(usable_subsets) 544 545 runable_subsets.difference_update(exclude_subsets) 546 547 return runable_subsets 548 549 550# https://stackoverflow.com/questions/14962485/finding-a-key-recursively-in-a-dictionary 551def __finditem(obj, key): 552 553 if key in obj: 554 return obj[key] 555 for dummy, val in obj.items(): 556 if isinstance(val, dict): 557 item = __finditem(val, key) 558 if item is not None: 559 return item 560 return None 561 562 563def _finditem(obj, key): 564 565 value = __finditem(obj, key) 566 if value is not None: 567 return value 568 raise KeyError(key) 569 570 571def convert_keys(d_param): 572 '''Method to convert hyphen to underscore''' 573 574 out = {} 575 if isinstance(d_param, dict): 576 for key, val in d_param.items(): 577 val = convert_keys(val) 578 out[key.replace('-', '_')] = val 579 else: 580 return d_param 581 return out 582 583 584def main(): 585 '''Execute action''' 586 587 argument_spec = netapp_utils.na_ontap_host_argument_spec() 588 argument_spec.update(dict( 589 state=dict(default='info', choices=['info']), 590 gather_subset=dict(default=['all'], type='list'), 591 )) 592 593 module = AnsibleModule( 594 argument_spec=argument_spec, 595 supports_check_mode=True 596 ) 597 598 if not HAS_XMLTODICT: 599 module.fail_json(msg="xmltodict missing") 600 601 if not HAS_JSON: 602 module.fail_json(msg="json missing") 603 604 state = module.params['state'] 605 gather_subset = module.params['gather_subset'] 606 if gather_subset is None: 607 gather_subset = ['all'] 608 gf_obj = NetAppONTAPGatherFacts(module) 609 gf_all = gf_obj.get_all(gather_subset) 610 result = {'state': state, 'changed': False} 611 module.exit_json(ansible_facts={'ontap_facts': gf_all}, **result) 612 613 614if __name__ == '__main__': 615 main() 616