diff --git a/network/illumos/__init__.py b/network/illumos/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/network/illumos/dladm_etherstub.py b/network/illumos/dladm_etherstub.py new file mode 100644 index 00000000000..72b2e6759ff --- /dev/null +++ b/network/illumos/dladm_etherstub.py @@ -0,0 +1,171 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Števko +# +# 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: dladm_etherstub +short_description: Manage etherstubs on Solaris/illumos systems. +description: + - Create or delete etherstubs on Solaris/illumos systems. +version_added: "2.2" +author: Adam Števko (@xen0l) +options: + name: + description: + - Etherstub name. + required: true + temporary: + description: + - Specifies that the etherstub is temporary. Temporary etherstubs + do not persist across reboots. + required: false + default: false + choices: [ "true", "false" ] + state: + description: + - Create or delete Solaris/illumos etherstub. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +# Create 'stub0' etherstub +dladm_etherstub: name=stub0 state=present + +# Remove 'stub0 etherstub +dladm_etherstub: name=stub0 state=absent +''' + +RETURN = ''' +name: + description: etherstub name + returned: always + type: string + sample: "switch0" +state: + description: state of the target + returned: always + type: string + sample: "present" +temporary: + description: etherstub's persistence + returned: always + type: boolean + sample: "True" +''' + + +class Etherstub(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def etherstub_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-etherstub') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_etherstub(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-etherstub') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_etherstub(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-etherstub') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + supports_check_mode=True + ) + + etherstub = Etherstub(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = etherstub.name + result['state'] = etherstub.state + result['temporary'] = etherstub.temporary + + if etherstub.state == 'absent': + if etherstub.etherstub_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = etherstub.delete_etherstub() + if rc != 0: + module.fail_json(name=etherstub.name, msg=err, rc=rc) + elif etherstub.state == 'present': + if not etherstub.etherstub_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = etherstub.create_etherstub() + + if rc is not None and rc != 0: + module.fail_json(name=etherstub.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + +from ansible.module_utils.basic import * +main() diff --git a/network/illumos/dladm_vnic.py b/network/illumos/dladm_vnic.py new file mode 100644 index 00000000000..b57edc00a9c --- /dev/null +++ b/network/illumos/dladm_vnic.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Števko +# +# 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: dladm_vnic +short_description: Manage VNICs on Solaris/illumos systems. +description: + - Create or delete VNICs on Solaris/illumos systems. +version_added: "2.2" +author: Adam Števko (@xen0l) +options: + name: + description: + - VNIC name. + required: true + link: + description: + - VNIC underlying link name. + required: true + temporary: + description: + - Specifies that the VNIC is temporary. Temporary VNICs + do not persist across reboots. + required: false + default: false + choices: [ "true", "false" ] + mac: + description: + - Sets the VNIC's MAC address. Must be valid unicast MAC address. + required: false + default: false + aliases: [ "macaddr" ] + vlan: + description: + - Enable VLAN tagging for this VNIC. The VLAN tag will have id + I(vlan). + required: false + default: false + aliases: [ "vlan_id" ] + state: + description: + - Create or delete Solaris/illumos VNIC. + required: false + default: "present" + choices: [ "present", "absent" ] +''' + +EXAMPLES = ''' +# Create 'vnic0' VNIC over 'bnx0' link +dladm_vnic: name=vnic0 link=bnx0 state=present + +# Create VNIC with specified MAC and VLAN tag over 'aggr0' +dladm_vnic: name=vnic1 link=aggr0 mac=2:33:af:12:ab:cd vlan=4 + +# Remove 'vnic0' VNIC +dladm_vnic: name=vnic0 link=bnx0 state=absent +''' + +RETURN = ''' +name: + description: VNIC name + returned: always + type: string + sample: "vnic0" +link: + description: VNIC underlying link name + returned: always + type: string + sample: "igb0" +state: + description: state of the target + returned: always + type: string + sample: "present" +temporary: + description: VNIC's persistence + returned: always + type: boolean + sample: "True" +mac: + description: MAC address to use for VNIC + returned: if mac is specified + type: string + sample: "00:aa:bc:fe:11:22" +vlan: + description: VLAN to use for VNIC + returned: success + type: int + sample: 42 +''' + +import re + + +class VNIC(object): + + UNICAST_MAC_REGEX = r'^[a-f0-9][2-9a-f0]:([a-f0-9]{2}:){4}[a-f0-9]{2}$' + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.mac = module.params['mac'] + self.vlan = module.params['vlan'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def vnic_exists(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('show-vnic') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def create_vnic(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('create-vnic') + + if self.temporary: + cmd.append('-t') + + if self.mac: + cmd.append('-m') + cmd.append(self.mac) + + if self.vlan: + cmd.append('-v') + cmd.append(self.vlan) + + cmd.append('-l') + cmd.append(self.link) + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_vnic(self): + cmd = [self.module.get_bin_path('dladm', True)] + + cmd.append('delete-vnic') + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def is_valid_unicast_mac(self): + + mac_re = re.match(self.UNICAST_MAC_REGEX, self.mac) + + return mac_re is None + + def is_valid_vlan_id(self): + + return 0 <= self.vlan <= 4095 + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + link=dict(required=True), + mac=dict(default=None, aliases=['macaddr']), + vlan=dict(default=None, aliases=['vlan_id']), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', 'present']), + ), + supports_check_mode=True + ) + + vnic = VNIC(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = vnic.name + result['link'] = vnic.link + result['state'] = vnic.state + result['temporary'] = vnic.temporary + + if vnic.mac is not None: + if vnic.is_valid_unicast_mac(): + module.fail_json(msg='Invalid unicast MAC address', + mac=vnic.mac, + name=vnic.name, + state=vnic.state, + link=vnic.link, + vlan=vnic.vlan) + result['mac'] = vnic.mac + + if vnic.vlan is not None: + if vnic.is_valid_vlan_id(): + module.fail_json(msg='Invalid VLAN tag', + mac=vnic.mac, + name=vnic.name, + state=vnic.state, + link=vnic.link, + vlan=vnic.vlan) + result['vlan'] = vnic.vlan + + if vnic.state == 'absent': + if vnic.vnic_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vnic.delete_vnic() + if rc != 0: + module.fail_json(name=vnic.name, msg=err, rc=rc) + elif vnic.state == 'present': + if not vnic.vnic_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = vnic.create_vnic() + + if rc is not None and rc != 0: + module.fail_json(name=vnic.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + +from ansible.module_utils.basic import * +main() diff --git a/network/illumos/flowadm.py b/network/illumos/flowadm.py new file mode 100644 index 00000000000..73cc91af442 --- /dev/null +++ b/network/illumos/flowadm.py @@ -0,0 +1,503 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Adam Števko +# +# 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: flowadm +short_description: Manage bandwidth resource control and priority for protocols, services and zones. +description: + - Create/modify/remove networking bandwidth and associated resources for a type of traffic on a particular link. +version_added: "2.2" +author: Adam Števko (@xen0l) +options: + name: + description: > + - A flow is defined as a set of attributes based on Layer 3 and Layer 4 + headers, which can be used to identify a protocol, service, or a zone. + required: true + aliases: [ 'flow' ] + link: + description: + - Specifiies a link to configure flow on. + required: false + local_ip: + description: + - Identifies a network flow by the local IP address. + required: false + remove_ip: + description: + - Identifies a network flow by the remote IP address. + required: false + transport: + description: > + - Specifies a Layer 4 protocol to be used. It is typically used in combination with I(local_port) to + identify the service that needs special attention. + required: false + local_port: + description: + - Identifies a service specified by the local port. + required: false + dsfield: + description: > + - Identifies the 8-bit differentiated services field (as defined in + RFC 2474). The optional dsfield_mask is used to state the bits of interest in + the differentiated services field when comparing with the dsfield + value. Both values must be in hexadecimal. + required: false + maxbw: + description: > + - Sets the full duplex bandwidth for the flow. The bandwidth is + specified as an integer with one of the scale suffixes(K, M, or G + for Kbps, Mbps, and Gbps). If no units are specified, the input + value will be read as Mbps. + required: false + priority: + description: + - Sets the relative priority for the flow. + required: false + default: 'medium' + choices: [ 'low', 'medium', 'high' ] + temporary: + description: + - Specifies that the configured flow is temporary. Temporary + flows do not persist across reboots. + required: false + default: false + choices: [ "true", "false" ] + state: + description: + - Create/delete/enable/disable an IP address on the network interface. + required: false + default: present + choices: [ 'absent', 'present', 'resetted' ] +''' + +EXAMPLES = ''' +# Limit SSH traffic to 100M via vnic0 interface +flowadm: link=vnic0 flow=ssh_out transport=tcp local_port=22 maxbw=100M state=present + +# Reset flow properties +flowadm: name=dns state=resetted + +# Configure policy for EF PHB (DSCP value of 101110 from RFC 2598) with a bandwidth of 500 Mbps and a high priority. +flowadm: link=bge0 dsfield=0x2e:0xfc maxbw=500M priority=high flow=efphb-flow state=present +''' + +RETURN = ''' +name: + description: flow name + returned: always + type: string + sample: "http_drop" +link: + description: flow's link + returned: if link is defined + type: string + sample: "vnic0" +state: + description: state of the target + returned: always + type: string + sample: "present" +temporary: + description: flow's persistence + returned: always + type: boolean + sample: "True" +priority: + description: flow's priority + returned: if priority is defined + type: string + sample: "low" +transport: + description: flow's transport + returned: if transport is defined + type: string + sample: "tcp" +maxbw: + description: flow's maximum bandwidth + returned: if maxbw is defined + type: string + sample: "100M" +local_Ip: + description: flow's local IP address + returned: if local_ip is defined + type: string + sample: "10.0.0.42" +local_port: + description: flow's local port + returned: if local_port is defined + type: int + sample: 1337 +remote_Ip: + description: flow's remote IP address + returned: if remote_ip is defined + type: string + sample: "10.0.0.42" +dsfield: + description: flow's differentiated services value + returned: if dsfield is defined + type: string + sample: "0x2e:0xfc" +''' + + +import socket + +SUPPORTED_TRANSPORTS = ['tcp', 'udp', 'sctp', 'icmp', 'icmpv6'] +SUPPORTED_PRIORITIES = ['low', 'medium', 'high'] + +SUPPORTED_ATTRIBUTES = ['local_ip', 'remote_ip', 'transport', 'local_port', 'dsfield'] +SUPPORTPED_PROPERTIES = ['maxbw', 'priority'] + + +class Flow(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.link = module.params['link'] + self.local_ip = module.params['local_ip'] + self.remote_ip = module.params['remote_ip'] + self.transport = module.params['transport'] + self.local_port = module.params['local_port'] + self.dsfield = module.params['dsfield'] + self.maxbw = module.params['maxbw'] + self.priority = module.params['priority'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + self._needs_updating = { + 'maxbw': False, + 'priority': False, + } + + @classmethod + def is_valid_port(cls, port): + return 1 <= int(port) <= 65535 + + @classmethod + def is_valid_address(cls, ip): + + if ip.count('/') == 1: + ip_address, netmask = ip.split('/') + else: + ip_address = ip + + if len(ip_address.split('.')) == 4: + try: + socket.inet_pton(socket.AF_INET, ip_address) + except socket.error: + return False + + if not 0 <= netmask <= 32: + return False + else: + try: + socket.inet_pton(socket.AF_INET6, ip_address) + except socket.error: + return False + + if not 0 <= netmask <= 128: + return False + + return True + + @classmethod + def is_hex(cls, number): + try: + int(number, 16) + except ValueError: + return False + + return True + + @classmethod + def is_valid_dsfield(cls, dsfield): + + dsmask = None + + if dsfield.count(':') == 1: + dsval = dsfield.split(':')[0] + else: + dsval, dsmask = dsfield.split(':') + + if dsmask and not 0x01 <= int(dsmask, 16) <= 0xff and not 0x01 <= int(dsval, 16) <= 0xff: + return False + elif not 0x01 <= int(dsval, 16) <= 0xff: + return False + + return True + + def flow_exists(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('show-flow') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + return False + + def delete_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('remove-flow') + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def create_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('add-flow') + cmd.append('-l') + cmd.append(self.link) + + if self.local_ip: + cmd.append('-a') + cmd.append('local_ip=' + self.local_ip) + + if self.remote_ip: + cmd.append('-a') + cmd.append('remote_ip=' + self.remote_ip) + + if self.transport: + cmd.append('-a') + cmd.append('transport=' + self.transport) + + if self.local_port: + cmd.append('-a') + cmd.append('local_port=' + self.local_port) + + if self.dsfield: + cmd.append('-a') + cmd.append('dsfield=' + self.dsfield) + + if self.maxbw: + cmd.append('-p') + cmd.append('maxbw=' + self.maxbw) + + if self.priority: + cmd.append('-p') + cmd.append('priority=' + self.priority) + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def _query_flow_props(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('show-flowprop') + cmd.append('-c') + cmd.append('-o') + cmd.append('property,possible') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def flow_needs_udpating(self): + (rc, out, err) = self._query_flow_props() + + NEEDS_UPDATING = False + + if rc == 0: + properties = (line.split(':') for line in out.rstrip().split('\n')) + for prop, value in properties: + if prop == 'maxbw' and self.maxbw != value: + self._needs_updating.update({prop: True}) + NEEDS_UPDATING = True + + elif prop == 'priority' and self.priority != value: + self._needs_updating.update({prop: True}) + NEEDS_UPDATING = True + + return NEEDS_UPDATING + else: + self.module.fail_json(msg='Error while checking flow properties: %s' % err, + stderr=err, + rc=rc) + + def update_flow(self): + cmd = [self.module.get_bin_path('flowadm')] + + cmd.append('set-flowprop') + + if self.maxbw and self._needs_updating['maxbw']: + cmd.append('-p') + cmd.append('maxbw=' + self.maxbw) + + if self.priority and self._needs_updating['priority']: + cmd.append('-p') + cmd.append('priority=' + self.priority) + + if self.temporary: + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=['flow']), + link=dict(required=False), + local_ip=dict(required=False), + remote_ip=dict(required=False), + transport=dict(required=False, choices=SUPPORTED_TRANSPORTS), + local_port=dict(required=False), + dsfield=dict(required=False), + maxbw=dict(required=False), + priority=dict(required=False, + default='medium', + choices=SUPPORTED_PRIORITIES), + temporary=dict(default=False, type='bool'), + state=dict(required=False, + default='present', + choices=['absent', 'present', 'resetted']), + ), + mutually_exclusive=[ + ('local_ip', 'remote_ip'), + ('local_ip', 'transport'), + ('local_ip', 'local_port'), + ('local_ip', 'dsfield'), + ('remote_ip', 'transport'), + ('remote_ip', 'local_port'), + ('remote_ip', 'dsfield'), + ('transport', 'dsfield'), + ('local_port', 'dsfield'), + ], + supports_check_mode=True + ) + + flow = Flow(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = flow.name + result['state'] = flow.state + result['temporary'] = flow.temporary + + if flow.link: + result['link'] = flow.link + + if flow.maxbw: + result['maxbw'] = flow.maxbw + + if flow.priority: + result['priority'] = flow.priority + + if flow.local_ip: + if flow.is_valid_address(flow.local_ip): + result['local_ip'] = flow.local_ip + + if flow.remote_ip: + if flow.is_valid_address(flow.remote_ip): + result['remote_ip'] = flow.remote_ip + + if flow.transport: + result['transport'] = flow.transport + + if flow.local_port: + if flow.is_valid_port(flow.local_port): + result['local_port'] = flow.local_port + else: + module.fail_json(msg='Invalid port: %s' % flow.local_port, + rc=1) + + if flow.dsfield: + if flow.is_valid_dsfield(flow.dsfield): + result['dsfield'] = flow.dsfield + else: + module.fail_json(msg='Invalid dsfield: %s' % flow.dsfield, + rc=1) + + if flow.state == 'absent': + if flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.delete_flow() + if rc != 0: + module.fail_json(msg='Error while deleting flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + elif flow.state == 'present': + if not flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.create_flow() + if rc != 0: + module.fail_json(msg='Error while creating flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + else: + if flow.flow_needs_udpating(): + (rc, out, err) = flow.update_flow() + if rc != 0: + module.fail_json(msg='Error while updating flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + elif flow.state == 'resetted': + if flow.flow_exists(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = flow.reset_flow() + if rc != 0: + module.fail_json(msg='Error while resetting flow: "%s"' % err, + name=flow.name, + stderr=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +from ansible.module_utils.basic import * +main() diff --git a/network/illumos/ipadm_if.py b/network/illumos/ipadm_if.py new file mode 100644 index 00000000000..c7419848fc0 --- /dev/null +++ b/network/illumos/ipadm_if.py @@ -0,0 +1,222 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Števko +# +# 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: ipadm_if +short_description: Manage IP interfaces on Solaris/illumos systems. +description: + - Create, delete, enable or disable IP interfaces on Solaris/illumos + systems. +version_added: "2.2" +author: Adam Števko (@xen0l) +options: + name: + description: + - IP interface name. + required: true + temporary: + description: + - Specifies that the IP interface is temporary. Temporary IP + interfaces do not persist across reboots. + required: false + default: false + choices: [ "true", "false" ] + state: + description: + - Create or delete Solaris/illumos IP interfaces. + required: false + default: "present" + choices: [ "present", "absent", "enabled", "disabled" ] +''' + +EXAMPLES = ''' +# Create vnic0 interface +ipadm_if: name=vnic0 state=enabled + +# Disable vnic0 interface +ipadm_if: name=vnic0 state=disabled +''' + +RETURN = ''' +name: + description: IP interface name + returned: always + type: string + sample: "vnic0" +state: + description: state of the target + returned: always + type: string + sample: "present" +temporary: + description: persistence of a IP interface + returned: always + type: boolean + sample: "True" +''' + + +class IPInterface(object): + + def __init__(self, module): + self.module = module + + self.name = module.params['name'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def interface_exists(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('show-if') + cmd.append(self.name) + + (rc, _, _) = self.module.run_command(cmd) + if rc == 0: + return True + else: + return False + + def interface_is_disabled(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('show-if') + cmd.append('-o') + cmd.append('state') + cmd.append(self.name) + + (rc, out, err) = self.module.run_command(cmd) + if rc != 0: + self.module.fail_json(name=self.name, rc=rc, msg=err) + + return 'disabled' in out + + def create_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('create-if') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.name) + + return self.module.run_command(cmd) + + def delete_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('delete-if') + + if self.temporary: + cmd.append('-t') + + cmd.append(self.name) + + return self.module.run_command(cmd) + + def enable_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('enable-if') + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + def disable_interface(self): + cmd = [self.module.get_bin_path('ipadm', True)] + + cmd.append('disable-if') + cmd.append('-t') + cmd.append(self.name) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + temporary=dict(default=False, type='bool'), + state=dict(default='present', choices=['absent', + 'present', + 'enabled', + 'disabled']), + ), + supports_check_mode=True + ) + + interface = IPInterface(module) + + rc = None + out = '' + err = '' + result = {} + result['name'] = interface.name + result['state'] = interface.state + result['temporary'] = interface.temporary + + if interface.state == 'absent': + if interface.interface_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = interface.delete_interface() + if rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + elif interface.state == 'present': + if not interface.interface_exists(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = interface.create_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + elif interface.state == 'enabled': + if interface.interface_is_disabled(): + (rc, out, err) = interface.enable_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + elif interface.state == 'disabled': + if not interface.interface_is_disabled(): + (rc, out, err) = interface.disable_interface() + + if rc is not None and rc != 0: + module.fail_json(name=interface.name, msg=err, rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + +from ansible.module_utils.basic import * +main() diff --git a/network/illumos/ipadm_prop.py b/network/illumos/ipadm_prop.py new file mode 100644 index 00000000000..5399189ad35 --- /dev/null +++ b/network/illumos/ipadm_prop.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Adam Števko +# +# 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: ipadm_prop +short_description: Manage protocol properties on Solaris/illumos systems. +description: + - Modify protocol properties on Solaris/illumos systems. +version_added: "2.2" +author: Adam Števko (@xen0l) +options: + protocol: + description: + - Specifies the procotol for which we want to manage properties. + required: true + property: + description: + - Specifies the name of property we want to manage. + required: true + value: + description: + - Specifies the value we want to set for the property. + required: false + temporary: + description: + - Specifies that the property value is temporary. Temporary + property values do not persist across reboots. + required: false + default: false + choices: [ "true", "false" ] + state: + description: + - Set or reset the property value. + required: false + default: present + choices: [ "present", "absent", "reset" ] +''' + +EXAMPLES = ''' +# Set TCP receive buffer size +ipadm_prop: protocol=tcp property=recv_buf value=65536 + +# Reset UDP send buffer size to the default value +ipadm_prop: protocol=udp property=send_buf state=reset +''' + +RETURN = ''' +protocol: + description: property's protocol + returned: always + type: string + sample: "TCP" +property: + description: name of the property + returned: always + type: string + sample: "recv_maxbuf" +state: + description: state of the target + returned: always + type: string + sample: "present" +temporary: + description: property's persistence + returned: always + type: boolean + sample: "True" +value: + description: value of the property + returned: always + type: int/string (depends on property) + sample: 1024/never +''' + +SUPPORTED_PROTOCOLS = ['ipv4', 'ipv6', 'icmp', 'tcp', 'udp', 'sctp'] + + +class Prop(object): + + def __init__(self, module): + self.module = module + + self.protocol = module.params['protocol'] + self.property = module.params['property'] + self.value = module.params['value'] + self.temporary = module.params['temporary'] + self.state = module.params['state'] + + def property_exists(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, _, _) = self.module.run_command(cmd) + + if rc == 0: + return True + else: + self.module.fail_json(msg='Unknown property "%s" for protocol %s' % + (self.property, self.protocol), + protocol=self.protocol, + property=self.property) + + def property_is_modified(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current,default') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + (value, default) = out.split(':') + + if rc == 0 and value == default: + return True + else: + return False + + def property_is_set(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('show-prop') + cmd.append('-c') + cmd.append('-o') + cmd.append('current') + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + (rc, out, _) = self.module.run_command(cmd) + + out = out.rstrip() + + if rc == 0 and self.value == out: + return True + else: + return False + + def set_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('set-prop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property + "=" + self.value) + cmd.append(self.protocol) + + return self.module.run_command(cmd) + + def reset_property(self): + cmd = [self.module.get_bin_path('ipadm')] + + cmd.append('reset-prop') + + if self.temporary: + cmd.append('-t') + + cmd.append('-p') + cmd.append(self.property) + cmd.append(self.protocol) + + return self.module.run_command(cmd) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + protocol=dict(required=True, choices=SUPPORTED_PROTOCOLS), + property=dict(required=True), + value=dict(required=False), + temporary=dict(default=False, type='bool'), + state=dict( + default='present', choices=['absent', 'present', 'reset']), + ), + supports_check_mode=True + ) + + prop = Prop(module) + + rc = None + out = '' + err = '' + result = {} + result['protocol'] = prop.protocol + result['property'] = prop.property + result['state'] = prop.state + result['temporary'] = prop.temporary + if prop.value: + result['value'] = prop.value + + if prop.state == 'absent' or prop.state == 'reset': + if prop.property_exists(): + if not prop.property_is_modified(): + if module.check_mode: + module.exit_json(changed=True) + (rc, out, err) = prop.reset_property() + if rc != 0: + module.fail_json(protocol=prop.protocol, + property=prop.property, + msg=err, + rc=rc) + + elif prop.state == 'present': + if prop.value is None: + module.fail_json(msg='Value is mandatory with state "present"') + + if prop.property_exists(): + if not prop.property_is_set(): + if module.check_mode: + module.exit_json(changed=True) + + (rc, out, err) = prop.set_property() + if rc != 0: + module.fail_json(protocol=prop.protocol, + property=prop.property, + msg=err, + rc=rc) + + if rc is None: + result['changed'] = False + else: + result['changed'] = True + + if out: + result['stdout'] = out + if err: + result['stderr'] = err + + module.exit_json(**result) + + +from ansible.module_utils.basic import * +main()