#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2017, <meiliu@fusionlayer.com> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = r''' module: infinity short_description: Manage Infinity IPAM using Rest API description: - Manage Infinity IPAM using REST API. author: - Meirong Liu (@MeganLiu) options: server_ip: description: - Infinity server_ip with IP address. type: str required: true username: description: - Username to access Infinity. - The user must have REST API privileges. type: str required: true password: description: - Infinity password. type: str required: true action: description: - Action to perform type: str required: true choices: [add_network, delete_network, get_network, get_network_id, release_ip, release_network, reserve_network, reserve_next_available_ip ] network_id: description: - Network ID. type: str default: '' ip_address: description: - IP Address for a reservation or a release. type: str default: '' network_address: description: - Network address with CIDR format (e.g., 192.168.310.0). type: str default: '' network_size: description: - Network bitmask (e.g. 255.255.255.220) or CIDR format (e.g., /26). type: str default: '' network_name: description: - The name of a network. type: str default: '' network_location: description: - The parent network id for a given network. type: int default: -1 network_type: description: - Network type defined by Infinity type: str choices: [ lan, shared_lan, supernet ] default: lan network_family: description: - Network family defined by Infinity, e.g. IPv4, IPv6 and Dual stack type: str choices: [ '4', '6', dual ] default: '4' ''' EXAMPLES = r''' --- - hosts: localhost connection: local strategy: debug tasks: - name: Reserve network into Infinity IPAM community.general.infinity: server_ip: 80.75.107.12 username: username password: password action: reserve_network network_name: reserve_new_ansible_network network_family: 4 network_type: lan network_id: 1201 network_size: /28 register: infinity ''' RETURN = r''' network_id: description: id for a given network returned: success type: str sample: '1501' ip_info: description: when reserve next available ip address from a network, the ip address info ) is returned. returned: success type: str sample: '{"address": "192.168.10.3", "hostname": "", "FQDN": "", "domainname": "", "id": 3229}' network_info: description: when reserving a LAN network from a Infinity supernet by providing network_size, the information about the reserved network is returned. returned: success type: str sample: {"network_address": "192.168.10.32/28","network_family": "4", "network_id": 3102, "network_size": null,"description": null,"network_location": "3085", "ranges": { "id": 0, "name": null,"first_ip": null,"type": null,"last_ip": null}, "network_type": "lan","network_name": "'reserve_new_ansible_network'"} ''' from ansible.module_utils.basic import AnsibleModule, json from ansible.module_utils.urls import open_url class Infinity(object): """ Class for manage REST API calls with the Infinity. """ def __init__(self, module, server_ip, username, password): self.module = module self.auth_user = username self.auth_pass = password self.base_url = "https://%s/rest/v1/" % (str(server_ip)) def _get_api_call_ansible_handler( self, method='get', resource_url='', stat_codes=None, params=None, payload_data=None): """ Perform the HTTPS request by using ansible get/delete method """ stat_codes = [200] if stat_codes is None else stat_codes request_url = str(self.base_url) + str(resource_url) response = None headers = {'Content-Type': 'application/json'} if not request_url: self.module.exit_json( msg="When sending Rest api call , the resource URL is empty, please check.") if payload_data and not isinstance(payload_data, str): payload_data = json.dumps(payload_data) response_raw = open_url( str(request_url), method=method, timeout=20, headers=headers, url_username=self.auth_user, url_password=self.auth_pass, validate_certs=False, force_basic_auth=True, data=payload_data) response = response_raw.read() payload = '' if response_raw.code not in stat_codes: self.module.exit_json( changed=False, meta=" openurl response_raw.code show error and error code is %r" % (response_raw.code)) else: if isinstance(response, str) and len(response) > 0: payload = response elif method.lower() == 'delete' and response_raw.code == 204: payload = 'Delete is done.' if isinstance(payload, dict) and "text" in payload: self.module.exit_json( changed=False, meta="when calling rest api, returned data is not json ") raise Exception(payload["text"]) return payload # --------------------------------------------------------------------------- # get_network() # --------------------------------------------------------------------------- def get_network(self, network_id, network_name, limit=-1): """ Search network_name inside Infinity by using rest api Network id or network_name needs to be provided return the details of a given with given network_id or name """ if network_name is None and network_id is None: self.module.exit_json( msg="You must specify one of the options 'network_name' or 'network_id'.") method = "get" resource_url = '' params = {} response = None if network_id: resource_url = "networks/" + str(network_id) response = self._get_api_call_ansible_handler(method, resource_url) if network_id is None and network_name: method = "get" resource_url = "search" params = {"query": json.dumps( {"name": network_name, "type": "network"})} response = self._get_api_call_ansible_handler( method, resource_url, payload_data=json.dumps(params)) if response and isinstance(response, str): response = json.loads(response) if response and isinstance(response, list) and len( response) > 1 and limit == 1: response = response[0] response = json.dumps(response) return response # --------------------------------------------------------------------------- # get_network_id() # --------------------------------------------------------------------------- def get_network_id(self, network_name="", network_type='lan'): """ query network_id from Infinity via rest api based on given network_name """ method = 'get' resource_url = 'search' response = None if network_name is None: self.module.exit_json( msg="You must specify the option 'network_name'") params = {"query": json.dumps( {"name": network_name, "type": "network"})} response = self._get_api_call_ansible_handler( method, resource_url, payload_data=json.dumps(params)) network_id = "" if response and isinstance(response, str): response = json.loads(response) if response and isinstance(response, list): response = response[0] network_id = response['id'] return network_id # --------------------------------------------------------------------------- # reserve_next_available_ip() # --------------------------------------------------------------------------- def reserve_next_available_ip(self, network_id=""): """ Reserve ip address via Infinity by using rest api network_id: the id of the network that users would like to reserve network from return the next available ip address from that given network """ method = "post" resource_url = '' response = None ip_info = '' if not network_id: self.module.exit_json( msg="You must specify the option 'network_id'.") if network_id: resource_url = "networks/" + str(network_id) + "/reserve_ip" response = self._get_api_call_ansible_handler(method, resource_url) if response and response.find( "[") >= 0 and response.find("]") >= 0: start_pos = response.find("{") end_pos = response.find("}") ip_info = response[start_pos: (end_pos + 1)] return ip_info # ------------------------- # release_ip() # ------------------------- def release_ip(self, network_id="", ip_address=""): """ Reserve ip address via Infinity by using rest api """ method = "get" resource_url = '' response = None if ip_address is None or network_id is None: self.module.exit_json( msg="You must specify those two options: 'network_id' and 'ip_address'.") resource_url = "networks/" + str(network_id) + "/children" response = self._get_api_call_ansible_handler(method, resource_url) if not response: self.module.exit_json( msg="There is an error in release ip %s from network %s." % (ip_address, network_id)) ip_list = json.loads(response) ip_idlist = [] for ip_item in ip_list: ip_id = ip_item['id'] ip_idlist.append(ip_id) deleted_ip_id = '' for ip_id in ip_idlist: ip_response = '' resource_url = "ip_addresses/" + str(ip_id) ip_response = self._get_api_call_ansible_handler( method, resource_url, stat_codes=[200]) if ip_response and json.loads( ip_response)['address'] == str(ip_address): deleted_ip_id = ip_id break if deleted_ip_id: method = 'delete' resource_url = "ip_addresses/" + str(deleted_ip_id) response = self._get_api_call_ansible_handler( method, resource_url, stat_codes=[204]) else: self.module.exit_json( msg=" When release ip, could not find the ip address %r from the given network %r' ." % (ip_address, network_id)) return response # ------------------- # delete_network() # ------------------- def delete_network(self, network_id="", network_name=""): """ delete network from Infinity by using rest api """ method = 'delete' resource_url = '' response = None if network_id is None and network_name is None: self.module.exit_json( msg="You must specify one of those options: 'network_id','network_name' .") if network_id is None and network_name: network_id = self.get_network_id(network_name=network_name) if network_id: resource_url = "networks/" + str(network_id) response = self._get_api_call_ansible_handler( method, resource_url, stat_codes=[204]) return response # reserve_network() # --------------------------------------------------------------------------- def reserve_network(self, network_id="", reserved_network_name="", reserved_network_description="", reserved_network_size="", reserved_network_family='4', reserved_network_type='lan', reserved_network_address="",): """ Reserves the first available network of specified size from a given supernet <dt>network_name (required)</dt><dd>Name of the network</dd> <dt>description (optional)</dt><dd>Free description</dd> <dt>network_family (required)</dt><dd>Address family of the network. One of '4', '6', 'IPv4', 'IPv6', 'dual'</dd> <dt>network_address (optional)</dt><dd>Address of the new network. If not given, the first network available will be created.</dd> <dt>network_size (required)</dt><dd>Size of the new network in /<prefix> notation.</dd> <dt>network_type (required)</dt><dd>Type of network. One of 'supernet', 'lan', 'shared_lan'</dd> """ method = 'post' resource_url = '' network_info = None if network_id is None or reserved_network_name is None or reserved_network_size is None: self.module.exit_json( msg="You must specify those options: 'network_id', 'reserved_network_name' and 'reserved_network_size'") if network_id: resource_url = "networks/" + str(network_id) + "/reserve_network" if not reserved_network_family: reserved_network_family = '4' if not reserved_network_type: reserved_network_type = 'lan' payload_data = { "network_name": reserved_network_name, 'description': reserved_network_description, 'network_size': reserved_network_size, 'network_family': reserved_network_family, 'network_type': reserved_network_type, 'network_location': int(network_id)} if reserved_network_address: payload_data.update({'network_address': reserved_network_address}) network_info = self._get_api_call_ansible_handler( method, resource_url, stat_codes=[200, 201], payload_data=payload_data) return network_info # --------------------------------------------------------------------------- # release_network() # --------------------------------------------------------------------------- def release_network( self, network_id="", released_network_name="", released_network_type='lan'): """ Release the network with name 'released_network_name' from the given supernet network_id """ method = 'get' response = None if network_id is None or released_network_name is None: self.module.exit_json( msg="You must specify those options 'network_id', 'reserved_network_name' and 'reserved_network_size'") matched_network_id = "" resource_url = "networks/" + str(network_id) + "/children" response = self._get_api_call_ansible_handler(method, resource_url) if not response: self.module.exit_json( msg=" there is an error in releasing network %r from network %s." % (network_id, released_network_name)) if response: response = json.loads(response) for child_net in response: if child_net['network'] and child_net['network']['network_name'] == released_network_name: matched_network_id = child_net['network']['network_id'] break response = None if matched_network_id: method = 'delete' resource_url = "networks/" + str(matched_network_id) response = self._get_api_call_ansible_handler( method, resource_url, stat_codes=[204]) else: self.module.exit_json( msg=" When release network , could not find the network %r from the given superent %r' " % (released_network_name, network_id)) return response # --------------------------------------------------------------------------- # add_network() # --------------------------------------------------------------------------- def add_network( self, network_name="", network_address="", network_size="", network_family='4', network_type='lan', network_location=-1): """ add a new LAN network into a given supernet Fusionlayer Infinity via rest api or default supernet required fields=['network_name', 'network_family', 'network_type', 'network_address','network_size' ] """ method = 'post' resource_url = 'networks' response = None if network_name is None or network_address is None or network_size is None: self.module.exit_json( msg="You must specify those options 'network_name', 'network_address' and 'network_size'") if not network_family: network_family = '4' if not network_type: network_type = 'lan' if not network_location: network_location = -1 payload_data = { "network_name": network_name, 'network_address': network_address, 'network_size': network_size, 'network_family': network_family, 'network_type': network_type, 'network_location': network_location} response = self._get_api_call_ansible_handler( method='post', resource_url=resource_url, stat_codes=[200], payload_data=payload_data) return response def main(): module = AnsibleModule( argument_spec=dict( server_ip=dict(type='str', required=True), username=dict(type='str', required=True), password=dict(type='str', required=True, no_log=True), network_id=dict(type='str'), ip_address=dict(type='str'), network_name=dict(type='str'), network_location=dict(type='int', default=-1), network_family=dict(type='str', default='4', choices=['4', '6', 'dual']), network_type=dict(type='str', default='lan', choices=['lan', 'shared_lan', 'supernet']), network_address=dict(type='str'), network_size=dict(type='str'), action=dict(type='str', required=True, choices=[ 'add_network', 'delete_network', 'get_network', 'get_network_id', 'release_ip', 'release_network', 'reserve_network', 'reserve_next_available_ip', ],), ), required_together=( ['username', 'password'], ), ) server_ip = module.params["server_ip"] username = module.params["username"] password = module.params["password"] action = module.params["action"] network_id = module.params["network_id"] released_ip = module.params["ip_address"] network_name = module.params["network_name"] network_family = module.params["network_family"] network_type = module.params["network_type"] network_address = module.params["network_address"] network_size = module.params["network_size"] network_location = module.params["network_location"] my_infinity = Infinity(module, server_ip, username, password) result = '' if action == "reserve_next_available_ip": if network_id: result = my_infinity.reserve_next_available_ip(network_id) if not result: result = 'There is an error in calling method of reserve_next_available_ip' module.exit_json(changed=False, meta=result) module.exit_json(changed=True, meta=result) elif action == "release_ip": if network_id and released_ip: result = my_infinity.release_ip( network_id=network_id, ip_address=released_ip) module.exit_json(changed=True, meta=result) elif action == "delete_network": result = my_infinity.delete_network( network_id=network_id, network_name=network_name) module.exit_json(changed=True, meta=result) elif action == "get_network_id": result = my_infinity.get_network_id( network_name=network_name, network_type=network_type) module.exit_json(changed=True, meta=result) elif action == "get_network": result = my_infinity.get_network( network_id=network_id, network_name=network_name) module.exit_json(changed=True, meta=result) elif action == "reserve_network": result = my_infinity.reserve_network( network_id=network_id, reserved_network_name=network_name, reserved_network_size=network_size, reserved_network_family=network_family, reserved_network_type=network_type, reserved_network_address=network_address) module.exit_json(changed=True, meta=result) elif action == "release_network": result = my_infinity.release_network( network_id=network_id, released_network_name=network_name, released_network_type=network_type) module.exit_json(changed=True, meta=result) elif action == "add_network": result = my_infinity.add_network( network_name=network_name, network_location=network_location, network_address=network_address, network_size=network_size, network_family=network_family, network_type=network_type) module.exit_json(changed=True, meta=result) if __name__ == '__main__': main()