From 0833a1e8c06186ff75e94b8b4bf09159861e5951 Mon Sep 17 00:00:00 2001 From: Tim Rupp Date: Fri, 19 Aug 2016 13:07:38 -0700 Subject: [PATCH] Another bootstrapping module, this module allows for one to manage route domains on a BIG-IP. Tests for this module can be found here https://github.com/F5Networks/f5-ansible/blob/master/roles/__bigip_routedomain/tasks/main.yaml Platforms this was tested on are 11.6.0 12.0.0 12.1.0 12.1.0 HF1 --- network/f5/bigip_routedomain.py | 509 ++++++++++++++++++++++++++++++++ 1 file changed, 509 insertions(+) create mode 100644 network/f5/bigip_routedomain.py diff --git a/network/f5/bigip_routedomain.py b/network/f5/bigip_routedomain.py new file mode 100644 index 00000000000..bb71f850f57 --- /dev/null +++ b/network/f5/bigip_routedomain.py @@ -0,0 +1,509 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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 . + +DOCUMENTATION = ''' +--- +module: bigip_routedomain +short_description: Manage route domains on a BIG-IP +description: + - Manage route domains on a BIG-IP +version_added: "2.2" +options: + bwc_policy: + description: + - The bandwidth controller for the route domain. + connection_limit: + description: + - The maximum number of concurrent connections allowed for the + route domain. Setting this to C(0) turns off connection limits. + description: + description: + - Specifies descriptive text that identifies the route domain. + flow_eviction_policy: + description: + - The eviction policy to use with this route domain. Apply an eviction + policy to provide customized responses to flow overflows and slow + flows on the route domain. + id: + description: + - The unique identifying integer representing the route domain. + required: true + parent: + description: | + Specifies the route domain the system searches when it cannot + find a route in the configured domain. + routing_protocol: + description: + - Dynamic routing protocols for the system to use in the route domain. + choices: + - BFD + - BGP + - IS-IS + - OSPFv2 + - OSPFv3 + - PIM + - RIP + - RIPng + service_policy: + description: + - Service policy to associate with the route domain. + state: + description: + - Whether the route domain should exist or not. + required: false + default: present + choices: + - present + - absent + strict: + description: + - Specifies whether the system enforces cross-routing restrictions + or not. + choices: + - enabled + - disabled + vlans: + description: + - VLANs for the system to use in the route domain +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: Create a route domain + bigip_routedomain: + id: "1234" + password: "secret" + server: "lb.mydomain.com" + state: "present" + user: "admin" + delegate_to: localhost + +- name: Set VLANs on the route domain + bigip_routedomain: + id: "1234" + password: "secret" + server: "lb.mydomain.com" + state: "present" + user: "admin" + vlans: + - net1 + - foo + delegate_to: localhost +''' + +RETURN = ''' +id: + description: The ID of the route domain that was changed + returned: changed + type: int + sample: 2 +description: + description: The description of the route domain + returned: changed + type: string + sample: "route domain foo" +strict: + description: The new strict isolation setting + returned: changed + type: string + sample: "enabled" +parent: + description: The new parent route domain + returned: changed + type: int + sample: 0 +vlans: + description: List of new VLANs the route domain is applied to + returned: changed + type: list + sample: ['/Common/http-tunnel', '/Common/socks-tunnel'] +routing_protocol: + description: List of routing protocols applied to the route domain + returned: changed + type: list + sample: ['bfd', 'bgp'] +bwc_policy: + description: The new bandwidth controller + returned: changed + type: string + sample: /Common/foo +connection_limit: + description: The new connection limit for the route domain + returned: changed + type: integer + sample: 100 +flow_eviction_policy: + description: The new eviction policy to use with this route domain + returned: changed + type: string + sample: /Common/default-eviction-policy +service_policy: + description: The new service policy to use with this route domain + returned: changed + type: string + sample: /Common-my-service-policy +''' + +try: + from f5.bigip import ManagementRoot + from icontrol.session import iControlUnexpectedHTTPError + HAS_F5SDK = True +except ImportError: + HAS_F5SDK = False + +PROTOCOLS = [ + 'BFD', 'BGP', 'IS-IS', 'OSPFv2', 'OSPFv3', 'PIM', 'RIP', 'RIPng' +] + +STRICTS = ['enabled', 'disabled'] + + +class BigIpRouteDomain(object): + def __init__(self, *args, **kwargs): + if not HAS_F5SDK: + raise F5ModuleError("The python f5-sdk module is required") + + # The params that change in the module + self.cparams = dict() + + kwargs['name'] = str(kwargs['id']) + + # Stores the params that are sent to the module + self.params = kwargs + self.api = ManagementRoot(kwargs['server'], + kwargs['user'], + kwargs['password'], + port=kwargs['server_port']) + + def absent(self): + if not self.exists(): + return False + + if self.params['check_mode']: + return True + + rd = self.api.tm.net.route_domains.route_domain.load( + name=self.params['name'] + ) + rd.delete() + + if self.exists(): + raise F5ModuleError("Failed to delete the route domain") + else: + return True + + def present(self): + if self.exists(): + return self.update() + else: + if self.params['check_mode']: + return True + return self.create() + + def read(self): + """Read information and transform it + + The values that are returned by BIG-IP in the f5-sdk can have encoding + attached to them as well as be completely missing in some cases. + + Therefore, this method will transform the data from the BIG-IP into a + format that is more easily consumable by the rest of the class and the + parameters that are supported by the module. + """ + p = dict() + r = self.api.tm.net.route_domains.route_domain.load( + name=self.params['name'] + ) + + p['id'] = int(r.id) + p['name'] = str(r.name) + + if hasattr(r, 'connectionLimit'): + p['connection_limit'] = int(r.connectionLimit) + if hasattr(r, 'description'): + p['description'] = str(r.description) + if hasattr(r, 'strict'): + p['strict'] = str(r.strict) + if hasattr(r, 'parent'): + p['parent'] = r.parent + if hasattr(r, 'vlans'): + p['vlans'] = list(set([str(x) for x in r.vlans])) + if hasattr(r, 'routingProtocol'): + p['routing_protocol'] = list(set([str(x) for x in r.routingProtocol])) + if hasattr(r, 'flowEvictionPolicy'): + p['flow_eviction_policy'] = str(r.flowEvictionPolicy) + if hasattr(r, 'bwcPolicy'): + p['bwc_policy'] = str(r.bwcPolicy) + if hasattr(r, 'servicePolicy'): + p['service_policy'] = str(r.servicePolicy) + return p + + def create(self): + params = dict() + params['id'] = self.params['id'] + params['name'] = self.params['name'] + + partition = self.params['partition'] + description = self.params['description'] + strict = self.params['strict'] + parent = self.params['parent'] + bwc_policy = self.params['bwc_policy'] + vlans = self.params['vlans'] + routing_protocol = self.params['routing_protocol'] + connection_limit = self.params['connection_limit'] + flow_eviction_policy = self.params['flow_eviction_policy'] + service_policy = self.params['service_policy'] + + if description is not None: + params['description'] = description + + if strict is not None: + params['strict'] = strict + + if parent is not None: + parent = '/%s/%s' % (partition, parent) + if parent in self.domains: + params['parent'] = parent + else: + raise F5ModuleError( + "The parent route domain was not found" + ) + + if bwc_policy is not None: + policy = '/%s/%s' % (partition, bwc_policy) + params['bwcPolicy'] = policy + + if vlans is not None: + params['vlans'] = [] + for vlan in vlans: + vname = '/%s/%s' % (partition, vlan) + params['vlans'].append(vname) + + if routing_protocol is not None: + params['routingProtocol'] = [] + for protocol in routing_protocol: + if protocol in PROTOCOLS: + params['routingProtocol'].append(protocol) + else: + raise F5ModuleError( + "routing_protocol must be one of: %s" % (PROTOCOLS) + ) + + if connection_limit is not None: + params['connectionLimit'] = connection_limit + + if flow_eviction_policy is not None: + policy = '/%s/%s' % (partition, flow_eviction_policy) + params['flowEvictionPolicy'] = policy + + if service_policy is not None: + policy = '/%s/%s' % (partition, service_policy) + params['servicePolicy'] = policy + + self.api.tm.net.route_domains.route_domain.create(**params) + exists = self.api.tm.net.route_domains.route_domain.exists( + name=self.params['name'] + ) + + if exists: + return True + else: + raise F5ModuleError( + "An error occurred while creating the route domain" + ) + + def update(self): + changed = False + params = dict() + current = self.read() + + check_mode = self.params['check_mode'] + partition = self.params['partition'] + description = self.params['description'] + strict = self.params['strict'] + parent = self.params['parent'] + bwc_policy = self.params['bwc_policy'] + vlans = self.params['vlans'] + routing_protocol = self.params['routing_protocol'] + connection_limit = self.params['connection_limit'] + flow_eviction_policy = self.params['flow_eviction_policy'] + service_policy = self.params['service_policy'] + + if description is not None: + if 'description' in current: + if description != current['description']: + params['description'] = description + else: + params['description'] = description + + if strict is not None: + if strict != current['strict']: + params['strict'] = strict + + if parent is not None: + parent = '/%s/%s' % (partition, parent) + if 'parent' in current: + if parent != current['parent']: + params['parent'] = parent + else: + params['parent'] = parent + + if bwc_policy is not None: + policy = '/%s/%s' % (partition, bwc_policy) + if 'bwc_policy' in current: + if policy != current['bwc_policy']: + params['bwcPolicy'] = policy + else: + params['bwcPolicy'] = policy + + if vlans is not None: + tmp = set() + for vlan in vlans: + vname = '/%s/%s' % (partition, vlan) + tmp.add(vname) + tmp = list(tmp) + if 'vlans' in current: + if tmp != current['vlans']: + params['vlans'] = tmp + else: + params['vlans'] = tmp + + if routing_protocol is not None: + tmp = set() + for protocol in routing_protocol: + if protocol in PROTOCOLS: + tmp.add(protocol) + else: + raise F5ModuleError( + "routing_protocol must be one of: %s" % (PROTOCOLS) + ) + tmp = list(tmp) + if 'routing_protocol' in current: + if tmp != current['routing_protocol']: + params['routingProtocol'] = tmp + else: + params['routingProtocol'] = tmp + + if connection_limit is not None: + if connection_limit != current['connection_limit']: + params['connectionLimit'] = connection_limit + + if flow_eviction_policy is not None: + policy = '/%s/%s' % (partition, flow_eviction_policy) + if 'flow_eviction_policy' in current: + if policy != current['flow_eviction_policy']: + params['flowEvictionPolicy'] = policy + else: + params['flowEvictionPolicy'] = policy + + if service_policy is not None: + policy = '/%s/%s' % (partition, service_policy) + if 'service_policy' in current: + if policy != current['service_policy']: + params['servicePolicy'] = policy + else: + params['servicePolicy'] = policy + + if params: + changed = True + self.cparams = camel_dict_to_snake_dict(params) + if check_mode: + return changed + else: + return changed + + try: + rd = self.api.tm.net.route_domains.route_domain.load( + name=self.params['name'] + ) + rd.update(**params) + rd.refresh() + except iControlUnexpectedHTTPError as e: + raise F5ModuleError(e) + + return True + + def exists(self): + return self.api.tm.net.route_domains.route_domain.exists( + name=self.params['name'] + ) + + def flush(self): + result = dict() + state = self.params['state'] + + if self.params['check_mode']: + if value == current: + changed = False + else: + changed = True + else: + if state == "present": + changed = self.present() + current = self.read() + result.update(current) + elif state == "absent": + changed = self.absent() + + result.update(dict(changed=changed)) + return result + + +def main(): + argument_spec = f5_argument_spec() + + meta_args = dict( + id=dict(required=True, type='int'), + description=dict(required=False, default=None), + strict=dict(required=False, default=None, choices=STRICTS), + parent=dict(required=False, type='int', default=None), + vlans=dict(required=False, default=None, type='list'), + routing_protocol=dict(required=False, default=None, type='list'), + bwc_policy=dict(required=False, type='str', default=None), + connection_limit=dict(required=False, type='int', default=None), + flow_eviction_policy=dict(required=False, type='str', default=None), + service_policy=dict(required=False, type='str', default=None) + ) + argument_spec.update(meta_args) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + try: + obj = BigIpRouteDomain(check_mode=module.check_mode, **module.params) + result = obj.flush() + + 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()