Adds bigip_gtm_facts module (#3232)

This patch adds support for querying the GTM(DNS) facts from a
BIG-IP. This completes a previous PR that was requested but not
finished.

Tests for this module can be found here

https://github.com/F5Networks/f5-ansible/blob/master/roles/bigip_gtm_facts/tasks/main.yaml

Platforms this was tested on are

11.6.0
12.1.0 HF1
This commit is contained in:
Tim Rupp 2016-10-19 10:30:49 -07:00 committed by Matt Clay
parent 50b31a2343
commit 5d3dafc02e

View file

@ -0,0 +1,491 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2016 F5 Networks Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: bigip_gtm_facts
short_description: Collect facts from F5 BIG-IP GTM devices.
description:
- Collect facts from F5 BIG-IP GTM devices.
version_added: "2.3"
options:
include:
description:
- Fact category to collect
required: true
choices:
- pool
- wide_ip
- virtual_server
filter:
description:
- Perform regex filter of response. Filtering is done on the name of
the resource. Valid filters are anything that can be provided to
Python's C(re) module.
required: false
default: None
notes:
- Requires the f5-sdk Python package on the host. This is as easy as
pip install f5-sdk
extends_documentation_fragment: f5
requirements:
- f5-sdk
author:
- Tim Rupp (@caphrim007)
'''
EXAMPLES = '''
- name: Get pool facts
bigip_gtm_facts:
server: "lb.mydomain.com"
user: "admin"
password: "secret"
include: "pool"
filter: "my_pool"
delegate_to: localhost
'''
RETURN = '''
wide_ip:
description:
Contains the lb method for the wide ip and the pools
that are within the wide ip.
returned: changed
type: dict
sample:
wide_ip:
- enabled: "True"
failure_rcode: "noerror"
failure_rcode_response: "disabled"
failure_rcode_ttl: "0"
full_path: "/Common/foo.ok.com"
last_resort_pool: ""
minimal_response: "enabled"
name: "foo.ok.com"
partition: "Common"
persist_cidr_ipv4: "32"
persist_cidr_ipv6: "128"
persistence: "disabled"
pool_lb_mode: "round-robin"
pools:
- name: "d3qw"
order: "0"
partition: "Common"
ratio: "1"
ttl_persistence: "3600"
type: "naptr"
pool:
description: Contains the pool object status and enabled status.
returned: changed
type: dict
sample:
pool:
- alternate_mode: "round-robin"
dynamic_ratio: "disabled"
enabled: "True"
fallback_mode: "return-to-dns"
full_path: "/Common/d3qw"
load_balancing_mode: "round-robin"
manual_resume: "disabled"
max_answers_returned: "1"
members:
- disabled: "True"
flags: "a"
full_path: "ok3.com"
member_order: "0"
name: "ok3.com"
order: "10"
preference: "10"
ratio: "1"
service: "80"
name: "d3qw"
partition: "Common"
qos_hit_ratio: "5"
qos_hops: "0"
qos_kilobytes_second: "3"
qos_lcs: "30"
qos_packet_rate: "1"
qos_rtt: "50"
qos_topology: "0"
qos_vs_capacity: "0"
qos_vs_score: "0"
ttl: "30"
type: "naptr"
verify_member_availability: "disabled"
virtual_server:
description:
Contains the virtual server enabled and availability
status, and address
returned: changed
type: dict
sample:
virtual_server:
- addresses:
- device_name: "/Common/qweqwe"
name: "10.10.10.10"
translation: "none"
datacenter: "/Common/xfxgh"
enabled: "True"
expose_route_domains: "no"
full_path: "/Common/qweqwe"
iq_allow_path: "yes"
iq_allow_service_check: "yes"
iq_allow_snmp: "yes"
limit_cpu_usage: "0"
limit_cpu_usage_status: "disabled"
limit_max_bps: "0"
limit_max_bps_status: "disabled"
limit_max_connections: "0"
limit_max_connections_status: "disabled"
limit_max_pps: "0"
limit_max_pps_status: "disabled"
limit_mem_avail: "0"
limit_mem_avail_status: "disabled"
link_discovery: "disabled"
monitor: "/Common/bigip "
name: "qweqwe"
partition: "Common"
product: "single-bigip"
virtual_server_discovery: "disabled"
virtual_servers:
- destination: "10.10.10.10:0"
enabled: "True"
full_path: "jsdfhsd"
limit_max_bps: "0"
limit_max_bps_status: "disabled"
limit_max_connections: "0"
limit_max_connections_status: "disabled"
limit_max_pps: "0"
limit_max_pps_status: "disabled"
name: "jsdfhsd"
translation_address: "none"
translation_port: "0"
'''
try:
from distutils.version import LooseVersion
from f5.bigip.contexts import TransactionContextManager
from f5.bigip import ManagementRoot
from icontrol.session import iControlUnexpectedHTTPError
HAS_F5SDK = True
except ImportError:
HAS_F5SDK = False
import re
class BigIpGtmFactsCommon(object):
def __init__(self):
self.api = None
self.attributes_to_remove = [
'kind', 'generation', 'selfLink', '_meta_data',
'membersReference', 'datacenterReference',
'virtualServersReference', 'nameReference'
]
self.gtm_types = dict(
a_s='a',
aaaas='aaaa',
cnames='cname',
mxs='mx',
naptrs='naptr',
srvs='srv'
)
self.request_params = dict(
params='expandSubcollections=true'
)
def is_version_less_than_12(self):
version = self.api.tmos_version
if LooseVersion(version) < LooseVersion('12.0.0'):
return True
else:
return False
def format_string_facts(self, parameters):
result = dict()
for attribute in self.attributes_to_remove:
parameters.pop(attribute, None)
for key, val in parameters.iteritems():
result[key] = str(val)
return result
def filter_matches_name(self, name):
if not self.params['filter']:
return True
matches = re.match(self.params['filter'], str(name))
if matches:
return True
else:
return False
def get_facts_from_collection(self, collection, collection_type=None):
results = []
for item in collection:
if not self.filter_matches_name(item.name):
continue
facts = self.format_facts(item, collection_type)
results.append(facts)
return results
def connect_to_bigip(self, **kwargs):
return ManagementRoot(kwargs['server'],
kwargs['user'],
kwargs['password'],
port=kwargs['server_port'])
class BigIpGtmFactsPools(BigIpGtmFactsCommon):
def __init__(self, *args, **kwargs):
super(BigIpGtmFactsPools, self).__init__()
self.params = kwargs
def get_facts(self):
self.api = self.connect_to_bigip(**self.params)
return self.get_facts_from_device()
def get_facts_from_device(self):
try:
if self.is_version_less_than_12():
return self.get_facts_without_types()
else:
return self.get_facts_with_types()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
def get_facts_with_types(self):
result = []
for key, type in self.gtm_types.iteritems():
facts = self.get_all_facts_by_type(key, type)
if facts:
result.append(facts)
return result
def get_facts_without_types(self):
pools = self.api.tm.gtm.pools.get_collection(**self.request_params)
return self.get_facts_from_collection(pools)
def get_all_facts_by_type(self, key, type):
collection = getattr(self.api.tm.gtm.pools, key)
pools = collection.get_collection(**self.request_params)
return self.get_facts_from_collection(pools, type)
def format_facts(self, pool, collection_type):
result = dict()
pool_dict = pool.to_dict()
result.update(self.format_string_facts(pool_dict))
result.update(self.format_member_facts(pool))
if collection_type:
result['type'] = collection_type
return camel_dict_to_snake_dict(result)
def format_member_facts(self, pool):
result = []
if not 'items' in pool.membersReference:
return dict(members=[])
for member in pool.membersReference['items']:
member_facts = self.format_string_facts(member)
result.append(member_facts)
return dict(members=result)
class BigIpGtmFactsWideIps(BigIpGtmFactsCommon):
def __init__(self, *args, **kwargs):
super(BigIpGtmFactsWideIps, self).__init__()
self.params = kwargs
def get_facts(self):
self.api = self.connect_to_bigip(**self.params)
return self.get_facts_from_device()
def get_facts_from_device(self):
try:
if self.is_version_less_than_12():
return self.get_facts_without_types()
else:
return self.get_facts_with_types()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
def get_facts_with_types(self):
result = []
for key, type in self.gtm_types.iteritems():
facts = self.get_all_facts_by_type(key, type)
if facts:
result.append(facts)
return result
def get_facts_without_types(self):
wideips = self.api.tm.gtm.wideips.get_collection(
**self.request_params
)
return self.get_facts_from_collection(wideips)
def get_all_facts_by_type(self, key, type):
collection = getattr(self.api.tm.gtm.wideips, key)
wideips = collection.get_collection(**self.request_params)
return self.get_facts_from_collection(wideips, type)
def format_facts(self, wideip, collection_type):
result = dict()
wideip_dict = wideip.to_dict()
result.update(self.format_string_facts(wideip_dict))
result.update(self.format_pool_facts(wideip))
if collection_type:
result['type'] = collection_type
return camel_dict_to_snake_dict(result)
def format_pool_facts(self, wideip):
result = []
if not hasattr(wideip, 'pools'):
return dict(pools=[])
for pool in wideip.pools:
pool_facts = self.format_string_facts(pool)
result.append(pool_facts)
return dict(pools=result)
class BigIpGtmFactsVirtualServers(BigIpGtmFactsCommon):
def __init__(self, *args, **kwargs):
super(BigIpGtmFactsVirtualServers, self).__init__()
self.params = kwargs
def get_facts(self):
try:
self.api = self.connect_to_bigip(**self.params)
return self.get_facts_from_device()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
def get_facts_from_device(self):
servers = self.api.tm.gtm.servers.get_collection(
**self.request_params
)
return self.get_facts_from_collection(servers)
def format_facts(self, server, collection_type=None):
result = dict()
server_dict = server.to_dict()
result.update(self.format_string_facts(server_dict))
result.update(self.format_address_facts(server))
result.update(self.format_virtual_server_facts(server))
return camel_dict_to_snake_dict(result)
def format_address_facts(self, server):
result = []
if not hasattr(server, 'addresses'):
return dict(addresses=[])
for address in server.addresses:
address_facts = self.format_string_facts(address)
result.append(address_facts)
return dict(addresses=result)
def format_virtual_server_facts(self, server):
result = []
if not 'items' in server.virtualServersReference:
return dict(virtual_servers=[])
for server in server.virtualServersReference['items']:
server_facts = self.format_string_facts(server)
result.append(server_facts)
return dict(virtual_servers=result)
class BigIpGtmFactsManager(object):
def __init__(self, *args, **kwargs):
self.params = kwargs
self.api = None
def get_facts(self):
result = dict()
facts = dict()
if 'pool' in self.params['include']:
facts['pool'] = self.get_pool_facts()
if 'wide_ip' in self.params['include']:
facts['wide_ip'] = self.get_wide_ip_facts()
if 'virtual_server' in self.params['include']:
facts['virtual_server'] = self.get_virtual_server_facts()
result.update(**facts)
result.update(dict(changed=True))
return result
def get_pool_facts(self):
pools = BigIpGtmFactsPools(**self.params)
return pools.get_facts()
def get_wide_ip_facts(self):
wide_ips = BigIpGtmFactsWideIps(**self.params)
return wide_ips.get_facts()
def get_virtual_server_facts(self):
wide_ips = BigIpGtmFactsVirtualServers(**self.params)
return wide_ips.get_facts()
class BigIpGtmFactsModuleConfig(object):
def __init__(self):
self.argument_spec = dict()
self.meta_args = dict()
self.supports_check_mode = False
self.valid_includes = ['pool', 'wide_ip', 'virtual_server']
self.initialize_meta_args()
self.initialize_argument_spec()
def initialize_meta_args(self):
args = dict(
include=dict(type='list', required=True),
filter=dict(type='str', required=False)
)
self.meta_args = args
def initialize_argument_spec(self):
self.argument_spec = f5_argument_spec()
self.argument_spec.update(self.meta_args)
def create(self):
return AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=self.supports_check_mode
)
def main():
if not HAS_F5SDK:
raise F5ModuleError("The python f5-sdk module is required")
config = BigIpGtmFactsModuleConfig()
module = config.create()
try:
obj = BigIpGtmFactsManager(
check_mode=module.check_mode, **module.params
)
result = obj.get_facts()
module.exit_json(**result)
except F5ModuleError as e:
module.fail_json(msg=str(e))
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
from ansible.module_utils.f5 import *
if __name__ == '__main__':
main()