mirror of https://github.com/ansible/ansible.git
Adding files to ansible core modules.
parent
d3b826dda2
commit
d9a071089b
@ -0,0 +1,468 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_bond
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Configures a bond port on Cumulus Linux
|
||||
description:
|
||||
- Configures a bond interface on Cumulus Linux To configure a bridge port
|
||||
use the cl_bridge module. To configure any other type of interface use the
|
||||
cl_interface module. Follow the guidelines for bonding found in the
|
||||
Cumulus User Guide at http://docs.cumulusnetworks.com
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the interface
|
||||
required: true
|
||||
alias_name:
|
||||
description:
|
||||
- add a port description
|
||||
ipv4:
|
||||
description:
|
||||
- list of IPv4 addresses to configure on the interface.
|
||||
use X.X.X.X/YY syntax.
|
||||
ipv6:
|
||||
description:
|
||||
- list of IPv6 addresses to configure on the interface.
|
||||
use X:X:X::X/YYY syntax
|
||||
addr_method:
|
||||
description:
|
||||
- configures the port to use DHCP.
|
||||
To enable this feature use the option 'dhcp'
|
||||
choices: ['dhcp']
|
||||
mtu:
|
||||
description:
|
||||
- set MTU. Configure Jumbo Frame by setting MTU to 9000.
|
||||
virtual_ip:
|
||||
description:
|
||||
- define IPv4 virtual IP used by the Cumulus Linux VRR feature
|
||||
virtual_mac:
|
||||
description:
|
||||
- define Ethernet mac associated with Cumulus Linux VRR feature
|
||||
vids:
|
||||
description:
|
||||
- in vlan aware mode, lists vlans defined under the interface
|
||||
mstpctl_bpduguard:
|
||||
description:
|
||||
- Enables BPDU Guard on a port in vlan-aware mode
|
||||
mstpctl_portnetwork:
|
||||
description:
|
||||
- Enables bridge assurance in vlan-aware mode
|
||||
mstpctl_portadminedge:
|
||||
description:
|
||||
- Enables admin edge port
|
||||
clag_id:
|
||||
description:
|
||||
- specify a unique clag_id for every dual connected bond on each
|
||||
peer switch. The value must be between 1 and 65535 and must be the
|
||||
same on both peer switches in order for the bond to be considered
|
||||
dual-connected
|
||||
pvid:
|
||||
description:
|
||||
- in vlan aware mode, defines vlan that is the untagged vlan
|
||||
miimon:
|
||||
description:
|
||||
- mii link monitoring interval
|
||||
default: 100
|
||||
mode:
|
||||
description:
|
||||
- bond mode. as of Cumulus Linux 2.5 only LACP bond mode is
|
||||
supported
|
||||
default: '802.3ad'
|
||||
min_links:
|
||||
description:
|
||||
- minimum number of links
|
||||
default: 1
|
||||
lacp_bypass_allow:
|
||||
description:
|
||||
- Enable LACP bypass.
|
||||
lacp_bypass_period:
|
||||
description:
|
||||
- Period for enabling LACP bypass. Max value is 900.
|
||||
lacp_bypass_priority:
|
||||
description:
|
||||
- List of ports and priorities. Example "swp1=10, swp2=20"
|
||||
lacp_bypass_all_active:
|
||||
description:
|
||||
- Activate all interfaces for bypass.
|
||||
It is recommended to configure all_active instead
|
||||
of using bypass_priority.
|
||||
lacp_rate:
|
||||
description:
|
||||
- lacp rate
|
||||
default: 1
|
||||
slaves:
|
||||
description:
|
||||
- bond members
|
||||
required: True
|
||||
xmit_hash_policy:
|
||||
description:
|
||||
- transmit load balancing algorithm. As of Cumulus Linux 2.5 only
|
||||
layer3+4 policy is supported
|
||||
default: layer3+4
|
||||
location:
|
||||
description:
|
||||
- interface directory location
|
||||
default:
|
||||
- /etc/network/interfaces.d
|
||||
|
||||
requirements: [ Alternate Debian network interface manager - \
|
||||
ifupdown2 @ github.com/CumulusNetworks/ifupdown2 ]
|
||||
notes:
|
||||
- because the module writes the interface directory location. Ensure that
|
||||
``/etc/network/interfaces`` has a 'source /etc/network/interfaces.d/*' or
|
||||
whatever path is mentioned in the ``location`` attribute.
|
||||
|
||||
- For the config to be activated, i.e installed in the kernel,
|
||||
"service networking reload" needs be be executed. See EXAMPLES section.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Options ['virtual_mac', 'virtual_ip'] are required together
|
||||
# configure a bond interface with IP address
|
||||
cl_bond: name=bond0 slaves="swp4-5" ipv4=10.1.1.1/24
|
||||
notify: reload networking
|
||||
|
||||
# configure bond as a dual-connected clag bond
|
||||
cl_bond: name=bond1 slaves="swp1s0 swp2s0" clag_id=1
|
||||
notify: reload networking
|
||||
|
||||
# define cl_bond once in tasks file
|
||||
# then write inteface config in variables file
|
||||
# with just the options you want.
|
||||
cl_bond:
|
||||
name: "{{ item.key }}"
|
||||
slaves: "{{ item.value.slaves }}"
|
||||
clag_id: "{{ item.value.clag_id|default(omit) }}"
|
||||
ipv4: "{{ item.value.ipv4|default(omit) }}"
|
||||
ipv6: "{{ item.value.ipv6|default(omit) }}"
|
||||
alias_name: "{{ item.value.alias_name|default(omit) }}"
|
||||
addr_method: "{{ item.value.addr_method|default(omit) }}"
|
||||
mtu: "{{ item.value.mtu|default(omit) }}"
|
||||
vids: "{{ item.value.vids|default(omit) }}"
|
||||
virtual_ip: "{{ item.value.virtual_ip|default(omit) }}"
|
||||
virtual_mac: "{{ item.value.virtual_mac|default(omit) }}"
|
||||
mstpctl_portnetwork: "{{ item.value.mstpctl_portnetwork|default('no') }}"
|
||||
mstpctl_portadminedge: "{{ item.value.mstpctl_portadminedge|default('no') }}"
|
||||
mstpctl_bpduguard: "{{ item.value.mstpctl_bpduguard|default('no') }}"
|
||||
with_dict: cl_bonds
|
||||
notify: reload networking
|
||||
|
||||
# In vars file
|
||||
# ============
|
||||
cl_bonds:
|
||||
bond0:
|
||||
alias_name: 'uplink to isp'
|
||||
slaves: ['swp1', 'swp3']
|
||||
ipv4: '10.1.1.1/24'
|
||||
bond2:
|
||||
vids: [1, 50]
|
||||
clag_id: 1
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
|
||||
# handy helper for calling system calls.
|
||||
# calls AnsibleModule.run_command and prints a more appropriate message
|
||||
# exec_path - path to file to execute, with all its arguments.
|
||||
# E.g "/sbin/ip -o link show"
|
||||
# failure_msg - what message to print on failure
|
||||
def run_cmd(module, exec_path):
|
||||
(_rc, out, _err) = module.run_command(exec_path)
|
||||
if _rc > 0:
|
||||
if re.search('cannot find interface', _err):
|
||||
return '[{}]'
|
||||
failure_msg = "Failed; %s Error: %s" % (exec_path, _err)
|
||||
module.fail_json(msg=failure_msg)
|
||||
else:
|
||||
return out
|
||||
|
||||
|
||||
def current_iface_config(module):
|
||||
# due to a bug in ifquery, have to check for presence of interface file
|
||||
# and not rely solely on ifquery. when bug is fixed, this check can be
|
||||
# removed
|
||||
_ifacename = module.params.get('name')
|
||||
_int_dir = module.params.get('location')
|
||||
module.custom_current_config = {}
|
||||
if os.path.exists(_int_dir + '/' + _ifacename):
|
||||
_cmd = "/sbin/ifquery -o json %s" % (module.params.get('name'))
|
||||
module.custom_current_config = module.from_json(
|
||||
run_cmd(module, _cmd))[0]
|
||||
|
||||
|
||||
def build_address(module):
|
||||
# if addr_method == 'dhcp', dont add IP address
|
||||
if module.params.get('addr_method') == 'dhcp':
|
||||
return
|
||||
_ipv4 = module.params.get('ipv4')
|
||||
_ipv6 = module.params.get('ipv6')
|
||||
_addresslist = []
|
||||
if _ipv4 and len(_ipv4) > 0:
|
||||
_addresslist += _ipv4
|
||||
|
||||
if _ipv6 and len(_ipv6) > 0:
|
||||
_addresslist += _ipv6
|
||||
if len(_addresslist) > 0:
|
||||
module.custom_desired_config['config']['address'] = ' '.join(
|
||||
_addresslist)
|
||||
|
||||
|
||||
def build_vids(module):
|
||||
_vids = module.params.get('vids')
|
||||
if _vids and len(_vids) > 0:
|
||||
module.custom_desired_config['config']['bridge-vids'] = ' '.join(_vids)
|
||||
|
||||
|
||||
def build_pvid(module):
|
||||
_pvid = module.params.get('pvid')
|
||||
if _pvid:
|
||||
module.custom_desired_config['config']['bridge-pvid'] = str(_pvid)
|
||||
|
||||
|
||||
def conv_bool_to_str(_value):
|
||||
if isinstance(_value, bool):
|
||||
if _value is True:
|
||||
return 'yes'
|
||||
else:
|
||||
return 'no'
|
||||
return _value
|
||||
|
||||
|
||||
def conv_array_to_str(_value):
|
||||
if isinstance(_value, list):
|
||||
return ' '.join(_value)
|
||||
return _value
|
||||
|
||||
def build_generic_attr(module, _attr):
|
||||
_value = module.params.get(_attr)
|
||||
_value = conv_bool_to_str(_value)
|
||||
_value = conv_array_to_str(_value)
|
||||
if _value:
|
||||
module.custom_desired_config['config'][
|
||||
re.sub('_', '-', _attr)] = str(_value)
|
||||
|
||||
|
||||
def build_alias_name(module):
|
||||
alias_name = module.params.get('alias_name')
|
||||
if alias_name:
|
||||
module.custom_desired_config['config']['alias'] = alias_name
|
||||
|
||||
|
||||
def build_addr_method(module):
|
||||
_addr_method = module.params.get('addr_method')
|
||||
if _addr_method:
|
||||
module.custom_desired_config['addr_family'] = 'inet'
|
||||
module.custom_desired_config['addr_method'] = _addr_method
|
||||
|
||||
|
||||
def build_vrr(module):
|
||||
_virtual_ip = module.params.get('virtual_ip')
|
||||
_virtual_mac = module.params.get('virtual_mac')
|
||||
vrr_config = []
|
||||
if _virtual_ip:
|
||||
vrr_config.append(_virtual_mac)
|
||||
vrr_config.append(_virtual_ip)
|
||||
module.custom_desired_config.get('config')['address-virtual'] = \
|
||||
' '.join(vrr_config)
|
||||
|
||||
|
||||
def add_glob_to_array(_bondmems):
|
||||
"""
|
||||
goes through each bond member if it sees a dash add glob
|
||||
before it
|
||||
"""
|
||||
result = []
|
||||
if isinstance(_bondmems, list):
|
||||
for _entry in _bondmems:
|
||||
if re.search('-', _entry):
|
||||
_entry = 'glob ' + _entry
|
||||
result.append(_entry)
|
||||
return ' '.join(result)
|
||||
return _bondmems
|
||||
|
||||
|
||||
def build_bond_attr(module, _attr):
|
||||
_value = module.params.get(_attr)
|
||||
_value = conv_bool_to_str(_value)
|
||||
_value = add_glob_to_array(_value)
|
||||
if _value:
|
||||
module.custom_desired_config['config'][
|
||||
'bond-' + re.sub('_', '-', _attr)] = str(_value)
|
||||
|
||||
|
||||
def build_desired_iface_config(module):
|
||||
"""
|
||||
take parameters defined and build ifupdown2 compatible hash
|
||||
"""
|
||||
module.custom_desired_config = {
|
||||
'addr_family': None,
|
||||
'auto': True,
|
||||
'config': {},
|
||||
'name': module.params.get('name')
|
||||
}
|
||||
|
||||
for _attr in ['slaves', 'mode', 'xmit_hash_policy',
|
||||
'miimon', 'lacp_rate', 'lacp_bypass_allow',
|
||||
'lacp_bypass_period', 'lacp_bypass_all_active',
|
||||
'min_links']:
|
||||
build_bond_attr(module, _attr)
|
||||
|
||||
build_addr_method(module)
|
||||
build_address(module)
|
||||
build_vids(module)
|
||||
build_pvid(module)
|
||||
build_alias_name(module)
|
||||
build_vrr(module)
|
||||
|
||||
for _attr in ['mtu', 'mstpctl_portnetwork', 'mstpctl_portadminedge'
|
||||
'mstpctl_bpduguard', 'clag_id',
|
||||
'lacp_bypass_priority']:
|
||||
build_generic_attr(module, _attr)
|
||||
|
||||
|
||||
def config_dict_changed(module):
|
||||
"""
|
||||
return true if 'config' dict in hash is different
|
||||
between desired and current config
|
||||
"""
|
||||
current_config = module.custom_current_config.get('config')
|
||||
desired_config = module.custom_desired_config.get('config')
|
||||
return current_config != desired_config
|
||||
|
||||
|
||||
def config_changed(module):
|
||||
"""
|
||||
returns true if config has changed
|
||||
"""
|
||||
if config_dict_changed(module):
|
||||
return True
|
||||
# check if addr_method is changed
|
||||
return module.custom_desired_config.get('addr_method') != \
|
||||
module.custom_current_config.get('addr_method')
|
||||
|
||||
|
||||
def replace_config(module):
|
||||
temp = tempfile.NamedTemporaryFile()
|
||||
desired_config = module.custom_desired_config
|
||||
# by default it will be something like /etc/network/interfaces.d/swp1
|
||||
final_location = module.params.get('location') + '/' + \
|
||||
module.params.get('name')
|
||||
final_text = ''
|
||||
_fh = open(final_location, 'w')
|
||||
# make sure to put hash in array or else ifquery will fail
|
||||
# write to temp file
|
||||
try:
|
||||
temp.write(module.jsonify([desired_config]))
|
||||
# need to seek to 0 so that data is written to tempfile.
|
||||
temp.seek(0)
|
||||
_cmd = "/sbin/ifquery -a -i %s -t json" % (temp.name)
|
||||
final_text = run_cmd(module, _cmd)
|
||||
finally:
|
||||
temp.close()
|
||||
|
||||
try:
|
||||
_fh.write(final_text)
|
||||
finally:
|
||||
_fh.close()
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
slaves=dict(required=True, type='list'),
|
||||
name=dict(required=True, type='str'),
|
||||
ipv4=dict(type='list'),
|
||||
ipv6=dict(type='list'),
|
||||
alias_name=dict(type='str'),
|
||||
addr_method=dict(type='str',
|
||||
choices=['', 'dhcp']),
|
||||
mtu=dict(type='str'),
|
||||
virtual_ip=dict(type='str'),
|
||||
virtual_mac=dict(type='str'),
|
||||
vids=dict(type='list'),
|
||||
pvid=dict(type='str'),
|
||||
mstpctl_portnetwork=dict(type='bool', choices=BOOLEANS),
|
||||
mstpctl_portadminedge=dict(type='bool', choices=BOOLEANS),
|
||||
mstpctl_bpduguard=dict(type='bool', choices=BOOLEANS),
|
||||
clag_id=dict(type='str'),
|
||||
min_links=dict(type='int', default=1),
|
||||
mode=dict(type='str', default='802.3ad'),
|
||||
miimon=dict(type='int', default=100),
|
||||
xmit_hash_policy=dict(type='str', default='layer3+4'),
|
||||
lacp_rate=dict(type='int', default=1),
|
||||
lacp_bypass_allow=dict(type='int', choices=[0, 1]),
|
||||
lacp_bypass_all_active=dict(type='int', choices=[0, 1]),
|
||||
lacp_bypass_priority=dict(type='list'),
|
||||
lacp_bypass_period=dict(type='int'),
|
||||
location=dict(type='str',
|
||||
default='/etc/network/interfaces.d')
|
||||
),
|
||||
mutually_exclusive=[['lacp_bypass_priority', 'lacp_bypass_all_active']],
|
||||
required_together=[['virtual_ip', 'virtual_mac']]
|
||||
)
|
||||
|
||||
# if using the jinja default filter, this resolves to
|
||||
# create an list with an empty string ['']. The following
|
||||
# checks all lists and removes it, so that functions expecting
|
||||
# an empty list, get this result. May upstream this fix into
|
||||
# the AnsibleModule code to have it check for this.
|
||||
for k, _param in module.params.iteritems():
|
||||
if isinstance(_param, list):
|
||||
module.params[k] = [x for x in _param if x]
|
||||
|
||||
_location = module.params.get('location')
|
||||
if not os.path.exists(_location):
|
||||
_msg = "%s does not exist." % (_location)
|
||||
module.fail_json(msg=_msg)
|
||||
return # for testing purposes only
|
||||
|
||||
ifacename = module.params.get('name')
|
||||
_changed = False
|
||||
_msg = "interface %s config not changed" % (ifacename)
|
||||
current_iface_config(module)
|
||||
build_desired_iface_config(module)
|
||||
if config_changed(module):
|
||||
replace_config(module)
|
||||
_msg = "interface %s config updated" % (ifacename)
|
||||
_changed = True
|
||||
|
||||
module.exit_json(changed=_changed, msg=_msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
import tempfile
|
||||
import os
|
||||
import re
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,404 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_bridge
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Configures a bridge port on Cumulus Linux
|
||||
description:
|
||||
- Configures a bridge interface on Cumulus Linux To configure a bond port
|
||||
use the cl_bond module. To configure any other type of interface use the
|
||||
cl_interface module. Follow the guidelines for bridging found in the
|
||||
Cumulus User Guide at http://docs.cumulusnetworks.com
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the interface
|
||||
required: true
|
||||
alias_name:
|
||||
description:
|
||||
- add a port description
|
||||
ipv4:
|
||||
description:
|
||||
- list of IPv4 addresses to configure on the interface.
|
||||
use X.X.X.X/YY syntax.
|
||||
ipv6:
|
||||
description:
|
||||
- list of IPv6 addresses to configure on the interface.
|
||||
use X:X:X::X/YYY syntax
|
||||
addr_method:
|
||||
description:
|
||||
- configures the port to use DHCP.
|
||||
To enable this feature use the option 'dhcp'
|
||||
choices: ['dhcp']
|
||||
mtu:
|
||||
description:
|
||||
- set MTU. Configure Jumbo Frame by setting MTU to 9000.
|
||||
virtual_ip:
|
||||
description:
|
||||
- define IPv4 virtual IP used by the Cumulus Linux VRR feature
|
||||
virtual_mac:
|
||||
description:
|
||||
- define Ethernet mac associated with Cumulus Linux VRR feature
|
||||
vids:
|
||||
description:
|
||||
- in vlan aware mode, lists vlans defined under the interface
|
||||
pvid:
|
||||
description:
|
||||
- in vlan aware mode, defines vlan that is the untagged vlan
|
||||
stp:
|
||||
description:
|
||||
- enables spanning tree. As of Cumulus Linux 2.5 the default
|
||||
bridging mode, only per vlan RSTP or 802.1d is supported. For the
|
||||
vlan aware mode, only common instance STP is supported
|
||||
default: 'yes'
|
||||
ports:
|
||||
description:
|
||||
- list of bridge members
|
||||
required: True
|
||||
vlan_aware:
|
||||
description:
|
||||
- enables vlan aware mode.
|
||||
mstpctl_treeprio:
|
||||
description:
|
||||
- set spanning tree root priority. Must be a multiple of 4096
|
||||
location:
|
||||
description:
|
||||
- interface directory location
|
||||
default:
|
||||
- /etc/network/interfaces.d
|
||||
|
||||
|
||||
requirements: [ Alternate Debian network interface manager
|
||||
ifupdown2 @ github.com/CumulusNetworks/ifupdown2 ]
|
||||
notes:
|
||||
- because the module writes the interface directory location. Ensure that
|
||||
``/etc/network/interfaces`` has a 'source /etc/network/interfaces.d/*' or
|
||||
whatever path is mentioned in the ``location`` attribute.
|
||||
|
||||
- For the config to be activated, i.e installed in the kernel,
|
||||
"service networking reload" needs be be executed. See EXAMPLES section.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Options ['virtual_mac', 'virtual_ip'] are required together
|
||||
# configure a bridge vlan aware bridge.
|
||||
cl_bridge: name=br0 ports='swp1-12' vlan_aware='yes'
|
||||
notify: reload networking
|
||||
|
||||
# configure bridge interface to define a default set of vlans
|
||||
cl_bridge: name=bridge ports='swp1-12' vlan_aware='yes' vids='1-100'
|
||||
notify: reload networking
|
||||
|
||||
# define cl_bridge once in tasks file
|
||||
# then write inteface config in variables file
|
||||
# with just the options you want.
|
||||
cl_bridge:
|
||||
name: "{{ item.key }}"
|
||||
ports: "{{ item.value.ports }}"
|
||||
vlan_aware: "{{ item.value.vlan_aware|default(omit) }}"
|
||||
ipv4: "{{ item.value.ipv4|default(omit) }}"
|
||||
ipv6: "{{ item.value.ipv6|default(omit) }}"
|
||||
alias_name: "{{ item.value.alias_name|default(omit) }}"
|
||||
addr_method: "{{ item.value.addr_method|default(omit) }}"
|
||||
mtu: "{{ item.value.mtu|default(omit) }}"
|
||||
vids: "{{ item.value.vids|default(omit) }}"
|
||||
virtual_ip: "{{ item.value.virtual_ip|default(omit) }}"
|
||||
virtual_mac: "{{ item.value.virtual_mac|default(omit) }}"
|
||||
mstpctl_treeprio: "{{ item.value.mstpctl_treeprio|default(omit) }}"
|
||||
with_dict: cl_bridges
|
||||
notify: reload networking
|
||||
|
||||
# In vars file
|
||||
# ============
|
||||
cl_bridge:
|
||||
br0:
|
||||
alias_name: 'vlan aware bridge'
|
||||
ports: ['swp1', 'swp3']
|
||||
vlan_aware: true
|
||||
vids: ['1-100']
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
|
||||
# handy helper for calling system calls.
|
||||
# calls AnsibleModule.run_command and prints a more appropriate message
|
||||
# exec_path - path to file to execute, with all its arguments.
|
||||
# E.g "/sbin/ip -o link show"
|
||||
# failure_msg - what message to print on failure
|
||||
def run_cmd(module, exec_path):
|
||||
(_rc, out, _err) = module.run_command(exec_path)
|
||||
if _rc > 0:
|
||||
if re.search('cannot find interface', _err):
|
||||
return '[{}]'
|
||||
failure_msg = "Failed; %s Error: %s" % (exec_path, _err)
|
||||
module.fail_json(msg=failure_msg)
|
||||
else:
|
||||
return out
|
||||
|
||||
|
||||
def current_iface_config(module):
|
||||
# due to a bug in ifquery, have to check for presence of interface file
|
||||
# and not rely solely on ifquery. when bug is fixed, this check can be
|
||||
# removed
|
||||
_ifacename = module.params.get('name')
|
||||
_int_dir = module.params.get('location')
|
||||
module.custom_current_config = {}
|
||||
if os.path.exists(_int_dir + '/' + _ifacename):
|
||||
_cmd = "/sbin/ifquery -o json %s" % (module.params.get('name'))
|
||||
module.custom_current_config = module.from_json(
|
||||
run_cmd(module, _cmd))[0]
|
||||
|
||||
|
||||
def build_address(module):
|
||||
# if addr_method == 'dhcp', dont add IP address
|
||||
if module.params.get('addr_method') == 'dhcp':
|
||||
return
|
||||
_ipv4 = module.params.get('ipv4')
|
||||
_ipv6 = module.params.get('ipv6')
|
||||
_addresslist = []
|
||||
if _ipv4 and len(_ipv4) > 0:
|
||||
_addresslist += _ipv4
|
||||
|
||||
if _ipv6 and len(_ipv6) > 0:
|
||||
_addresslist += _ipv6
|
||||
if len(_addresslist) > 0:
|
||||
module.custom_desired_config['config']['address'] = ' '.join(
|
||||
_addresslist)
|
||||
|
||||
|
||||
def build_vids(module):
|
||||
_vids = module.params.get('vids')
|
||||
if _vids and len(_vids) > 0:
|
||||
module.custom_desired_config['config']['bridge-vids'] = ' '.join(_vids)
|
||||
|
||||
|
||||
def build_pvid(module):
|
||||
_pvid = module.params.get('pvid')
|
||||
if _pvid:
|
||||
module.custom_desired_config['config']['bridge-pvid'] = str(_pvid)
|
||||
|
||||
|
||||
def conv_bool_to_str(_value):
|
||||
if isinstance(_value, bool):
|
||||
if _value is True:
|
||||
return 'yes'
|
||||
else:
|
||||
return 'no'
|
||||
return _value
|
||||
|
||||
|
||||
def build_generic_attr(module, _attr):
|
||||
_value = module.params.get(_attr)
|
||||
_value = conv_bool_to_str(_value)
|
||||
if _value:
|
||||
module.custom_desired_config['config'][
|
||||
re.sub('_', '-', _attr)] = str(_value)
|
||||
|
||||
|
||||
def build_alias_name(module):
|
||||
alias_name = module.params.get('alias_name')
|
||||
if alias_name:
|
||||
module.custom_desired_config['config']['alias'] = alias_name
|
||||
|
||||
|
||||
def build_addr_method(module):
|
||||
_addr_method = module.params.get('addr_method')
|
||||
if _addr_method:
|
||||
module.custom_desired_config['addr_family'] = 'inet'
|
||||
module.custom_desired_config['addr_method'] = _addr_method
|
||||
|
||||
|
||||
def build_vrr(module):
|
||||
_virtual_ip = module.params.get('virtual_ip')
|
||||
_virtual_mac = module.params.get('virtual_mac')
|
||||
vrr_config = []
|
||||
if _virtual_ip:
|
||||
vrr_config.append(_virtual_mac)
|
||||
vrr_config.append(_virtual_ip)
|
||||
module.custom_desired_config.get('config')['address-virtual'] = \
|
||||
' '.join(vrr_config)
|
||||
|
||||
|
||||
def add_glob_to_array(_bridgemems):
|
||||
"""
|
||||
goes through each bridge member if it sees a dash add glob
|
||||
before it
|
||||
"""
|
||||
result = []
|
||||
if isinstance(_bridgemems, list):
|
||||
for _entry in _bridgemems:
|
||||
if re.search('-', _entry):
|
||||
_entry = 'glob ' + _entry
|
||||
result.append(_entry)
|
||||
return ' '.join(result)
|
||||
return _bridgemems
|
||||
|
||||
|
||||
def build_bridge_attr(module, _attr):
|
||||
_value = module.params.get(_attr)
|
||||
_value = conv_bool_to_str(_value)
|
||||
_value = add_glob_to_array(_value)
|
||||
if _value:
|
||||
module.custom_desired_config['config'][
|
||||
'bridge-' + re.sub('_', '-', _attr)] = str(_value)
|
||||
|
||||
|
||||
def build_desired_iface_config(module):
|
||||
"""
|
||||
take parameters defined and build ifupdown2 compatible hash
|
||||
"""
|
||||
module.custom_desired_config = {
|
||||
'addr_family': None,
|
||||
'auto': True,
|
||||
'config': {},
|
||||
'name': module.params.get('name')
|
||||
}
|
||||
|
||||
for _attr in ['vlan_aware', 'pvid', 'ports', 'stp']:
|
||||
build_bridge_attr(module, _attr)
|
||||
|
||||
build_addr_method(module)
|
||||
build_address(module)
|
||||
build_vids(module)
|
||||
build_alias_name(module)
|
||||
build_vrr(module)
|
||||
for _attr in ['mtu', 'mstpctl_treeprio']:
|
||||
build_generic_attr(module, _attr)
|
||||
|
||||
|
||||
def config_dict_changed(module):
|
||||
"""
|
||||
return true if 'config' dict in hash is different
|
||||
between desired and current config
|
||||
"""
|
||||
current_config = module.custom_current_config.get('config')
|
||||
desired_config = module.custom_desired_config.get('config')
|
||||
return current_config != desired_config
|
||||
|
||||
|
||||
def config_changed(module):
|
||||
"""
|
||||
returns true if config has changed
|
||||
"""
|
||||
if config_dict_changed(module):
|
||||
return True
|
||||
# check if addr_method is changed
|
||||
return module.custom_desired_config.get('addr_method') != \
|
||||
module.custom_current_config.get('addr_method')
|
||||
|
||||
|
||||
def replace_config(module):
|
||||
temp = tempfile.NamedTemporaryFile()
|
||||
desired_config = module.custom_desired_config
|
||||
# by default it will be something like /etc/network/interfaces.d/swp1
|
||||
final_location = module.params.get('location') + '/' + \
|
||||
module.params.get('name')
|
||||
final_text = ''
|
||||
_fh = open(final_location, 'w')
|
||||
# make sure to put hash in array or else ifquery will fail
|
||||
# write to temp file
|
||||
try:
|
||||
temp.write(module.jsonify([desired_config]))
|
||||
# need to seek to 0 so that data is written to tempfile.
|
||||
temp.seek(0)
|
||||
_cmd = "/sbin/ifquery -a -i %s -t json" % (temp.name)
|
||||
final_text = run_cmd(module, _cmd)
|
||||
finally:
|
||||
temp.close()
|
||||
|
||||
try:
|
||||
_fh.write(final_text)
|
||||
finally:
|
||||
_fh.close()
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
ports=dict(required=True, type='list'),
|
||||
name=dict(required=True, type='str'),
|
||||
ipv4=dict(type='list'),
|
||||
ipv6=dict(type='list'),
|
||||
alias_name=dict(type='str'),
|
||||
addr_method=dict(type='str',
|
||||
choices=['', 'dhcp']),
|
||||
mtu=dict(type='str'),
|
||||
virtual_ip=dict(type='str'),
|
||||
virtual_mac=dict(type='str'),
|
||||
vids=dict(type='list'),
|
||||
pvid=dict(type='str'),
|
||||
mstpctl_treeprio=dict(type='str'),
|
||||
vlan_aware=dict(type='bool', choices=BOOLEANS),
|
||||
stp=dict(type='bool', default='yes', choices=BOOLEANS),
|
||||
location=dict(type='str',
|
||||
default='/etc/network/interfaces.d')
|
||||
),
|
||||
required_together=[
|
||||
['virtual_ip', 'virtual_mac']
|
||||
]
|
||||
)
|
||||
|
||||
# if using the jinja default filter, this resolves to
|
||||
# create an list with an empty string ['']. The following
|
||||
# checks all lists and removes it, so that functions expecting
|
||||
# an empty list, get this result. May upstream this fix into
|
||||
# the AnsibleModule code to have it check for this.
|
||||
for k, _param in module.params.iteritems():
|
||||
if isinstance(_param, list):
|
||||
module.params[k] = [x for x in _param if x]
|
||||
|
||||
_location = module.params.get('location')
|
||||
if not os.path.exists(_location):
|
||||
_msg = "%s does not exist." % (_location)
|
||||
module.fail_json(msg=_msg)
|
||||
return # for testing purposes only
|
||||
|
||||
ifacename = module.params.get('name')
|
||||
_changed = False
|
||||
_msg = "interface %s config not changed" % (ifacename)
|
||||
current_iface_config(module)
|
||||
build_desired_iface_config(module)
|
||||
if config_changed(module):
|
||||
replace_config(module)
|
||||
_msg = "interface %s config updated" % (ifacename)
|
||||
_changed = True
|
||||
|
||||
module.exit_json(changed=_changed, msg=_msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
import tempfile
|
||||
import os
|
||||
import re
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,312 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_img_install
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusLinux)"
|
||||
short_description: Install a different Cumulus Linux version.
|
||||
description:
|
||||
- install a different version of Cumulus Linux in the inactive slot. For
|
||||
more details go the Image Management User Guide @
|
||||
http://docs.cumulusnetworks.com/
|
||||
options:
|
||||
src:
|
||||
description:
|
||||
- full path to the Cumulus Linux binary image. Can be a local path,
|
||||
http or https URL. If the code version is in the name of the file,
|
||||
the module will assume this is the version of code you wish to
|
||||
install.
|
||||
required: true
|
||||
version:
|
||||
description:
|
||||
- inform the module of the exact version one is installing. This
|
||||
overrides the automatic check of version in the file name. For
|
||||
example, if the binary file name is called CumulusLinux-2.2.3.bin,
|
||||
and version is set to '2.5.0', then the module will assume it is
|
||||
installing '2.5.0' not '2.2.3'. If version is not included, then
|
||||
the module will assume '2.2.3' is the version to install.
|
||||
switch_slot:
|
||||
description:
|
||||
- Switch slots after installing the image.
|
||||
To run the installed code, reboot the switch
|
||||
choices: ['yes', 'no']
|
||||
default: 'no'
|
||||
|
||||
requirements: ["Cumulus Linux OS"]
|
||||
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
Example playbook entries using the cl_img_install module
|
||||
|
||||
## Download and install the image from a webserver.
|
||||
|
||||
- name: install image using using http url. Switch slots so the subsequent
|
||||
will load the new version
|
||||
cl_img_install: version=2.0.1
|
||||
src='http://10.1.1.1/CumulusLinux-2.0.1.bin'
|
||||
switch_slot=yes
|
||||
|
||||
## Copy the software from the ansible server to the switch.
|
||||
## The module will get the code version from the filename
|
||||
## The code will be installed in the alternate slot but the slot will not be primary
|
||||
## A subsequent reload will not run the new code
|
||||
|
||||
- name: download cumulus linux to local system
|
||||
get_url: src=ftp://cumuluslinux.bin dest=/root/CumulusLinux-2.0.1.bin
|
||||
|
||||
- name: install image from local filesystem. Get version from the filename
|
||||
cl_img_install: src='/root/CumulusLinux-2.0.1.bin'
|
||||
|
||||
|
||||
## If the image name has been changed from the original name, use the `version` option
|
||||
## to inform the module exactly what code version is been installed
|
||||
|
||||
- name: download cumulus linux to local system
|
||||
get_url: src=ftp://CumulusLinux-2.0.1.bin dest=/root/image.bin
|
||||
|
||||
- name: install image and switch slots. only reboot needed
|
||||
cl_img_install: version=2.0.1 src=/root/image.bin switch_slot=yes'
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
|
||||
def check_url(module, url):
|
||||
parsed_url = urlparse(url)
|
||||
if len(parsed_url.path) > 0:
|
||||
sch = parsed_url.scheme
|
||||
if (sch == 'http' or sch == 'https' or len(parsed_url.scheme) == 0):
|
||||
return True
|
||||
module.fail_json(msg="Image Path URL. Wrong Format %s" % (url))
|
||||
return False
|
||||
|
||||
|
||||
def run_cl_cmd(module, cmd, check_rc=True):
|
||||
try:
|
||||
(rc, out, err) = module.run_command(cmd, check_rc=check_rc)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=e.strerror)
|
||||
# trim last line as it is always empty
|
||||
ret = out.splitlines()
|
||||
return ret
|
||||
|
||||
|
||||
def get_slot_info(module):
|
||||
slots = {}
|
||||
slots['1'] = {}
|
||||
slots['2'] = {}
|
||||
active_slotnum = get_active_slot(module)
|
||||
primary_slotnum = get_primary_slot_num(module)
|
||||
for _num in range(1, 3):
|
||||
slot = slots[str(_num)]
|
||||
slot['version'] = get_slot_version(module, str(_num))
|
||||
if _num == int(active_slotnum):
|
||||
slot['active'] = True
|
||||
if _num == int(primary_slotnum):
|
||||
slot['primary'] = True
|
||||
return slots
|
||||
|
||||
|
||||
def get_slot_version(module, slot_num):
|
||||
lsb_release = check_mnt_root_lsb_release(slot_num)
|
||||
switch_firm_ver = check_fw_print_env(module, slot_num)
|
||||
_version = module.sw_version
|
||||
if lsb_release == _version or switch_firm_ver == _version:
|
||||
return _version
|
||||
elif lsb_release:
|
||||
return lsb_release
|
||||
else:
|
||||
return switch_firm_ver
|
||||
|
||||
|
||||
def check_mnt_root_lsb_release(slot_num):
|
||||
_path = '/mnt/root-rw/config%s/etc/lsb-release' % (slot_num)
|
||||
try:
|
||||
lsb_release = open(_path)
|
||||
lines = lsb_release.readlines()
|
||||
for line in lines:
|
||||
_match = re.search('DISTRIB_RELEASE=([0-9a-zA-Z.]+)', line)
|
||||
if _match:
|
||||
return _match.group(1).split('-')[0]
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def check_fw_print_env(module, slot_num):
|
||||
cmd = None
|
||||
if platform.machine() == 'ppc':
|
||||
cmd = "/usr/sbin/fw_printenv -n cl.ver%s" % (slot_num)
|
||||
fw_output = run_cl_cmd(module, cmd)
|
||||
return fw_output[0].split('-')[0]
|
||||
elif platform.machine() == 'x86_64':
|
||||
cmd = "/usr/bin/grub-editenv list"
|
||||
grub_output = run_cl_cmd(module, cmd)
|
||||
for _line in grub_output:
|
||||
_regex_str = re.compile('cl.ver' + slot_num + '=([\w.]+)-')
|
||||
m0 = re.match(_regex_str, _line)
|
||||
if m0:
|
||||
return m0.group(1)
|
||||
|
||||
|
||||
|
||||
def get_primary_slot_num(module):
|
||||
cmd = None
|
||||
if platform.machine() == 'ppc':
|
||||
cmd = "/usr/sbin/fw_printenv -n cl.active"
|
||||
return ''.join(run_cl_cmd(module, cmd))
|
||||
elif platform.machine() == 'x86_64':
|
||||
cmd = "/usr/bin/grub-editenv list"
|
||||
grub_output = run_cl_cmd(module, cmd)
|
||||
for _line in grub_output:
|
||||
_regex_str = re.compile('cl.active=(\d)')
|
||||
m0 = re.match(_regex_str, _line)
|
||||
if m0:
|
||||
return m0.group(1)
|
||||
|
||||
|
||||
def get_active_slot(module):
|
||||
try:
|
||||
cmdline = open('/proc/cmdline').readline()
|
||||
except:
|
||||
module.fail_json(msg='Failed to open /proc/cmdline. ' +
|
||||
'Unable to determine active slot')
|
||||
|
||||
_match = re.search('active=(\d+)', cmdline)
|
||||
if _match:
|
||||
return _match.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def install_img(module):
|
||||
src = module.params.get('src')
|
||||
_version = module.sw_version
|
||||
app_path = '/usr/cumulus/bin/cl-img-install -f %s' % (src)
|
||||
run_cl_cmd(module, app_path)
|
||||
perform_switch_slot = module.params.get('switch_slot')
|
||||
if perform_switch_slot is True:
|
||||
check_sw_version(module)
|
||||
else:
|
||||
_changed = True
|
||||
_msg = "Cumulus Linux Version " + _version + " successfully" + \
|
||||
" installed in alternate slot"
|
||||
module.exit_json(changed=_changed, msg=_msg)
|
||||
|
||||
|
||||
def switch_slot(module, slotnum):
|
||||
_switch_slot = module.params.get('switch_slot')
|
||||
if _switch_slot is True:
|
||||
app_path = '/usr/cumulus/bin/cl-img-select %s' % (slotnum)
|
||||
run_cl_cmd(module, app_path)
|
||||
|
||||
|
||||
def determine_sw_version(module):
|
||||
_version = module.params.get('version')
|
||||
_filename = ''
|
||||
# Use _version if user defines it
|
||||
if _version:
|
||||
module.sw_version = _version
|
||||
return
|
||||
else:
|
||||
_filename = module.params.get('src').split('/')[-1]
|
||||
_match = re.search('\d+\W\d+\W\w+', _filename)
|
||||
if _match:
|
||||
module.sw_version = re.sub('\W', '.', _match.group())
|
||||
return
|
||||
_msg = 'Unable to determine version from file %s' % (_filename)
|
||||
module.exit_json(changed=False, msg=_msg)
|
||||
|
||||
|
||||
def check_sw_version(module):
|
||||
slots = get_slot_info(module)
|
||||
_version = module.sw_version
|
||||
perform_switch_slot = module.params.get('switch_slot')
|
||||
for _num, slot in slots.items():
|
||||
if slot['version'] == _version:
|
||||
if 'active' in slot:
|
||||
_msg = "Version %s is installed in the active slot" \
|
||||
% (_version)
|
||||
module.exit_json(changed=False, msg=_msg)
|
||||
else:
|
||||
_msg = "Version " + _version + \
|
||||
" is installed in the alternate slot. "
|
||||
if 'primary' not in slot:
|
||||
if perform_switch_slot is True:
|
||||
switch_slot(module, _num)
|
||||
_msg = _msg + \
|
||||
"cl-img-select has made the alternate " + \
|
||||
"slot the primary slot. " +\
|
||||
"Next reboot, switch will load " + _version + "."
|
||||
module.exit_json(changed=True, msg=_msg)
|
||||
else:
|
||||
_msg = _msg + \
|
||||
"Next reboot will not load " + _version + ". " + \
|
||||
"switch_slot keyword set to 'no'."
|
||||
module.exit_json(changed=False, msg=_msg)
|
||||
else:
|
||||
if perform_switch_slot is True:
|
||||
_msg = _msg + \
|
||||
"Next reboot, switch will load " + _version + "."
|
||||
module.exit_json(changed=False, msg=_msg)
|
||||
else:
|
||||
_msg = _msg + \
|
||||
'switch_slot set to "no". ' + \
|
||||
'No further action to take'
|
||||
module.exit_json(changed=False, msg=_msg)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
src=dict(required=True, type='str'),
|
||||
version=dict(type='str'),
|
||||
switch_slot=dict(type='bool', choices=BOOLEANS, default=False),
|
||||
),
|
||||
)
|
||||
|
||||
determine_sw_version(module)
|
||||
_url = module.params.get('src')
|
||||
|
||||
check_sw_version(module)
|
||||
|
||||
check_url(module, _url)
|
||||
|
||||
install_img(module)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
# incompatible with ansible 1.4.4 - ubuntu 12.04 version
|
||||
# from ansible.module_utils.urls import *
|
||||
from urlparse import urlparse
|
||||
import re
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,438 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_interface
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Configures a front panel port, loopback or
|
||||
management port on Cumulus Linux.
|
||||
description:
|
||||
- Configures a front panel, sub-interface, SVI, management or loopback port
|
||||
on a Cumulus Linux switch. For bridge ports use the cl_bridge module. For
|
||||
bond ports use the cl_bond module. When configuring bridge related
|
||||
features like the "vid" option, please follow the guidelines for
|
||||
configuring "vlan aware" bridging. For more details review the Layer2
|
||||
Interface Guide at http://docs.cumulusnetworks.com
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the interface
|
||||
required: true
|
||||
alias_name:
|
||||
description:
|
||||
- add a port description
|
||||
ipv4:
|
||||
description:
|
||||
- list of IPv4 addresses to configure on the interface.
|
||||
use X.X.X.X/YY syntax.
|
||||
ipv6:
|
||||
description:
|
||||
- list of IPv6 addresses to configure on the interface.
|
||||
use X:X:X::X/YYY syntax
|
||||
addr_method:
|
||||
description:
|
||||
- can be loopback for loopback interfaces or dhcp for dhcp
|
||||
interfaces.
|
||||
speed:
|
||||
description:
|
||||
- set speed of the swp(front panel) or management(eth0) interface.
|
||||
speed is in MB
|
||||
mtu:
|
||||
description:
|
||||
- set MTU. Configure Jumbo Frame by setting MTU to 9000.
|
||||
|
||||
virtual_ip:
|
||||
description:
|
||||
- define IPv4 virtual IP used by the Cumulus VRR feature
|
||||
virtual_mac:
|
||||
description:
|
||||
- define Ethernet mac associated with Cumulus VRR feature
|
||||
vids:
|
||||
description:
|
||||
- in vlan aware mode, lists vlans defined under the interface
|
||||
mstpctl_bpduguard:
|
||||
description:
|
||||
- Enables BPDU Guard on a port in vlan-aware mode
|
||||
mstpctl_portnetwork:
|
||||
description:
|
||||
- Enables bridge assurance in vlan-aware mode
|
||||
mstpctl_portadminedge:
|
||||
description:
|
||||
- Enables admin edge port
|
||||
clagd_enable:
|
||||
description:
|
||||
- Enables the clagd daemon. This command should only be applied to
|
||||
the clag peerlink interface
|
||||
clagd_priority:
|
||||
description:
|
||||
- Integer that changes the role the switch has in the clag domain.
|
||||
The lower priority switch will assume the primary role. The number
|
||||
can be between 0 and 65535
|
||||
clagd_peer_ip:
|
||||
description:
|
||||
- IP address of the directly connected peer switch interface
|
||||
clagd_sys_mac:
|
||||
description:
|
||||
- Clagd system mac address. Recommended to use the range starting
|
||||
with 44:38:39:ff. Needs to be the same between 2 Clag switches
|
||||
pvid:
|
||||
description:
|
||||
- in vlan aware mode, defines vlan that is the untagged vlan
|
||||
location:
|
||||
description:
|
||||
- interface directory location
|
||||
default:
|
||||
- /etc/network/interfaces.d
|
||||
|
||||
requirements: [ Alternate Debian network interface manager - \
|
||||
ifupdown2 @ github.com/CumulusNetworks/ifupdown2 ]
|
||||
notes:
|
||||
- because the module writes the interface directory location. Ensure that
|
||||
``/etc/network/interfaces`` has a 'source /etc/network/interfaces.d/*' or
|
||||
whatever path is mentioned in the ``location`` attribute.
|
||||
|
||||
- For the config to be activated, i.e installed in the kernel,
|
||||
"service networking reload" needs be be executed. See EXAMPLES section.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Options ['virtual_mac', 'virtual_ip'] are required together
|
||||
# configure a front panel port with an IP
|
||||
cl_interface: name=swp1 ipv4=10.1.1.1/24
|
||||
notify: reload networking
|
||||
|
||||
# configure front panel to use DHCP
|
||||
cl_interface: name=swp2 addr_family=dhcp
|
||||
notify: reload networking
|
||||
|
||||
# configure a SVI for vlan 100 interface with an IP
|
||||
cl_interface: name=bridge.100 ipv4=10.1.1.1/24
|
||||
notify: reload networking
|
||||
|
||||
# configure subinterface with an IP
|
||||
cl_interface: name=bond0.100 alias_name='my bond' ipv4=10.1.1.1/24
|
||||
notify: reload networking
|
||||
|
||||
# define cl_interfaces once in tasks
|
||||
# then write intefaces in variables file
|
||||
# with just the options you want.
|
||||
cl_interface:
|
||||
name: "{{ item.key }}"
|
||||
ipv4: "{{ item.value.ipv4|default(omit) }}"
|
||||
ipv6: "{{ item.value.ipv6|default(omit) }}"
|
||||
alias_name: "{{ item.value.alias_name|default(omit) }}"
|
||||
addr_method: "{{ item.value.addr_method|default(omit) }}"
|
||||
speed: "{{ item.value.link_speed|default(omit) }}"
|
||||
mtu: "{{ item.value.mtu|default(omit) }}"
|
||||
clagd_enable: "{{ item.value.clagd_enable|default(omit) }}"
|
||||
clagd_peer_ip: "{{ item.value.clagd_peer_ip|default(omit) }}"
|
||||
clagd_sys_mac: "{{ item.value.clagd_sys_mac|default(omit) }}"
|
||||
clagd_priority: "{{ item.value.clagd_priority|default(omit) }}"
|
||||
vids: "{{ item.value.vids|default(omit) }}"
|
||||
virtual_ip: "{{ item.value.virtual_ip|default(omit) }}"
|
||||
virtual_mac: "{{ item.value.virtual_mac|default(omit) }}"
|
||||
mstpctl_portnetwork: "{{ item.value.mstpctl_portnetwork|default('no') }}"
|
||||
mstpctl_portadminedge: "{{ item.value.mstpctl_portadminedge|default('no') }}"
|
||||
mstpctl_bpduguard: "{{ item.value.mstpctl_bpduguard|default('no') }}"
|
||||
with_dict: cl_interfaces
|
||||
notify: reload networking
|
||||
|
||||
|
||||
# In vars file
|
||||
# ============
|
||||
cl_interfaces:
|
||||
swp1:
|
||||
alias_name: 'uplink to isp'
|
||||
ipv4: '10.1.1.1/24'
|
||||
swp2:
|
||||
alias_name: 'l2 trunk connection'
|
||||
vids: [1, 50]
|
||||
swp3:
|
||||
speed: 1000
|
||||
alias_name: 'connects to 1G link'
|
||||
##########
|
||||
# br0 interface is configured by cl_bridge
|
||||
##########
|
||||
br0.100:
|
||||
alias_name: 'SVI for vlan 100'
|
||||
ipv4: '10.2.2.2/24'
|
||||
ipv6: '10:2:2::2/127'
|
||||
virtual_ip: '10.2.2.254'
|
||||
virtual_mac: '00:00:5E:00:10:10'
|
||||
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
|
||||
# handy helper for calling system calls.
|
||||
# calls AnsibleModule.run_command and prints a more appropriate message
|
||||
# exec_path - path to file to execute, with all its arguments.
|
||||
# E.g "/sbin/ip -o link show"
|
||||
# failure_msg - what message to print on failure
|
||||
def run_cmd(module, exec_path):
|
||||
(_rc, out, _err) = module.run_command(exec_path)
|
||||
if _rc > 0:
|
||||
if re.search('cannot find interface', _err):
|
||||
return '[{}]'
|
||||
failure_msg = "Failed; %s Error: %s" % (exec_path, _err)
|
||||
module.fail_json(msg=failure_msg)
|
||||
else:
|
||||
return out
|
||||
|
||||
|
||||
def current_iface_config(module):
|
||||
# due to a bug in ifquery, have to check for presence of interface file
|
||||
# and not rely solely on ifquery. when bug is fixed, this check can be
|
||||
# removed
|
||||
_ifacename = module.params.get('name')
|
||||
_int_dir = module.params.get('location')
|
||||
module.custom_current_config = {}
|
||||
if os.path.exists(_int_dir + '/' + _ifacename):
|
||||
_cmd = "/sbin/ifquery -o json %s" % (module.params.get('name'))
|
||||
module.custom_current_config = module.from_json(
|
||||
run_cmd(module, _cmd))[0]
|
||||
|
||||
|
||||
def build_address(module):
|
||||
# if addr_method == 'dhcp', dont add IP address
|
||||
if module.params.get('addr_method') == 'dhcp':
|
||||
return
|
||||
_ipv4 = module.params.get('ipv4')
|
||||
_ipv6 = module.params.get('ipv6')
|
||||
_addresslist = []
|
||||
if _ipv4 and len(_ipv4) > 0:
|
||||
_addresslist += _ipv4
|
||||
if _ipv6 and len(_ipv6) > 0:
|
||||
_addresslist += _ipv6
|
||||
if len(_addresslist) > 0:
|
||||
module.custom_desired_config['config']['address'] = ' '.join(
|
||||
_addresslist)
|
||||
|
||||
|
||||
def build_vids(module):
|
||||
_vids = module.params.get('vids')
|
||||
if _vids and len(_vids) > 0:
|
||||
module.custom_desired_config['config']['bridge-vids'] = ' '.join(_vids)
|
||||
|
||||
|
||||
def build_pvid(module):
|
||||
_pvid = module.params.get('pvid')
|
||||
if _pvid:
|
||||
module.custom_desired_config['config']['bridge-pvid'] = str(_pvid)
|
||||
|
||||
|
||||
def build_speed(module):
|
||||
_speed = module.params.get('speed')
|
||||
if _speed:
|
||||
module.custom_desired_config['config']['link-speed'] = str(_speed)
|
||||
module.custom_desired_config['config']['link-duplex'] = 'full'
|
||||
|
||||
|
||||
def conv_bool_to_str(_value):
|
||||
if isinstance(_value, bool):
|
||||
if _value is True:
|
||||
return 'yes'
|
||||
else:
|
||||
return 'no'
|
||||
return _value
|
||||
|
||||
|
||||
def build_generic_attr(module, _attr):
|
||||
_value = module.params.get(_attr)
|
||||
_value = conv_bool_to_str(_value)
|
||||
if _value:
|
||||
module.custom_desired_config['config'][
|
||||
re.sub('_', '-', _attr)] = str(_value)
|
||||
|
||||
|
||||
def build_alias_name(module):
|
||||
alias_name = module.params.get('alias_name')
|
||||
if alias_name:
|
||||
module.custom_desired_config['config']['alias'] = alias_name
|
||||
|
||||
|
||||
def build_addr_method(module):
|
||||
_addr_method = module.params.get('addr_method')
|
||||
if _addr_method:
|
||||
module.custom_desired_config['addr_family'] = 'inet'
|
||||
module.custom_desired_config['addr_method'] = _addr_method
|
||||
|
||||
|
||||
def build_vrr(module):
|
||||
_virtual_ip = module.params.get('virtual_ip')
|
||||
_virtual_mac = module.params.get('virtual_mac')
|
||||
vrr_config = []
|
||||
if _virtual_ip:
|
||||
vrr_config.append(_virtual_mac)
|
||||
vrr_config.append(_virtual_ip)
|
||||
module.custom_desired_config.get('config')['address-virtual'] = \
|
||||
' '.join(vrr_config)
|
||||
|
||||
|
||||
def build_desired_iface_config(module):
|
||||
"""
|
||||
take parameters defined and build ifupdown2 compatible hash
|
||||
"""
|
||||
module.custom_desired_config = {
|
||||
'addr_family': None,
|
||||
'auto': True,
|
||||
'config': {},
|
||||
'name': module.params.get('name')
|
||||
}
|
||||
|
||||
build_addr_method(module)
|
||||
build_address(module)
|
||||
build_vids(module)
|
||||
build_pvid(module)
|
||||
build_speed(module)
|
||||
build_alias_name(module)
|
||||
build_vrr(module)
|
||||
for _attr in ['mtu', 'mstpctl_portnetwork', 'mstpctl_portadminedge',
|
||||
'mstpctl_bpduguard', 'clagd_enable',
|
||||
'clagd_priority', 'clagd_peer_ip',
|
||||
'clagd_sys_mac', 'clagd_args']:
|
||||
build_generic_attr(module, _attr)
|
||||
|
||||
|
||||
def config_dict_changed(module):
|
||||
"""
|
||||
return true if 'config' dict in hash is different
|
||||
between desired and current config
|
||||
"""
|
||||
current_config = module.custom_current_config.get('config')
|
||||
desired_config = module.custom_desired_config.get('config')
|
||||
return current_config != desired_config
|
||||
|
||||
|
||||
def config_changed(module):
|
||||
"""
|
||||
returns true if config has changed
|
||||
"""
|
||||
if config_dict_changed(module):
|
||||
return True
|
||||
# check if addr_method is changed
|
||||
return module.custom_desired_config.get('addr_method') != \
|
||||
module.custom_current_config.get('addr_method')
|
||||
|
||||
|
||||
def replace_config(module):
|
||||
temp = tempfile.NamedTemporaryFile()
|
||||
desired_config = module.custom_desired_config
|
||||
# by default it will be something like /etc/network/interfaces.d/swp1
|
||||
final_location = module.params.get('location') + '/' + \
|
||||
module.params.get('name')
|
||||
final_text = ''
|
||||
_fh = open(final_location, 'w')
|
||||
# make sure to put hash in array or else ifquery will fail
|
||||
# write to temp file
|
||||
try:
|
||||
temp.write(module.jsonify([desired_config]))
|
||||
# need to seek to 0 so that data is written to tempfile.
|
||||
temp.seek(0)
|
||||
_cmd = "/sbin/ifquery -a -i %s -t json" % (temp.name)
|
||||
final_text = run_cmd(module, _cmd)
|
||||
finally:
|
||||
temp.close()
|
||||
|
||||
try:
|
||||
_fh.write(final_text)
|
||||
finally:
|
||||
_fh.close()
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(required=True, type='str'),
|
||||
ipv4=dict(type='list'),
|
||||
ipv6=dict(type='list'),
|
||||
alias_name=dict(type='str'),
|
||||
addr_method=dict(type='str',
|
||||
choices=['', 'loopback', 'dhcp']),
|
||||
speed=dict(type='str'),
|
||||
mtu=dict(type='str'),
|
||||
virtual_ip=dict(type='str'),
|
||||
virtual_mac=dict(type='str'),
|
||||
vids=dict(type='list'),
|
||||
pvid=dict(type='str'),
|
||||
mstpctl_portnetwork=dict(type='bool', choices=BOOLEANS),
|
||||
mstpctl_portadminedge=dict(type='bool', choices=BOOLEANS),
|
||||
mstpctl_bpduguard=dict(type='bool', choices=BOOLEANS),
|
||||
clagd_enable=dict(type='bool', choices=BOOLEANS),
|
||||
clagd_priority=dict(type='str'),
|
||||
clagd_peer_ip=dict(type='str'),
|
||||
clagd_sys_mac=dict(type='str'),
|
||||
clagd_args=dict(type='str'),
|
||||
location=dict(type='str',
|
||||
default='/etc/network/interfaces.d')
|
||||
),
|
||||
required_together=[
|
||||
['virtual_ip', 'virtual_mac'],
|
||||
['clagd_enable', 'clagd_priority',
|
||||
'clagd_peer_ip', 'clagd_sys_mac']
|
||||
]
|
||||
)
|
||||
|
||||
# if using the jinja default filter, this resolves to
|
||||
# create an list with an empty string ['']. The following
|
||||
# checks all lists and removes it, so that functions expecting
|
||||
# an empty list, get this result. May upstream this fix into
|
||||
# the AnsibleModule code to have it check for this.
|
||||
for k, _param in module.params.iteritems():
|
||||
if isinstance(_param, list):
|
||||
module.params[k] = [x for x in _param if x]
|
||||
|
||||
_location = module.params.get('location')
|
||||
if not os.path.exists(_location):
|
||||
_msg = "%s does not exist." % (_location)
|
||||
module.fail_json(msg=_msg)
|
||||
return # for testing purposes only
|
||||
|
||||
ifacename = module.params.get('name')
|
||||
_changed = False
|
||||
_msg = "interface %s config not changed" % (ifacename)
|
||||
current_iface_config(module)
|
||||
build_desired_iface_config(module)
|
||||
if config_changed(module):
|
||||
replace_config(module)
|
||||
_msg = "interface %s config updated" % (ifacename)
|
||||
_changed = True
|
||||
|
||||
module.exit_json(changed=_changed, msg=_msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,147 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_interface_policy
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Configure interface enforcement policy on Cumulus Linux
|
||||
description:
|
||||
- This module affects the configuration files located in the interfaces
|
||||
folder defined by ifupdown2. Interfaces port and port ranges listed in the
|
||||
"allowed" parameter define what interfaces will be available on the
|
||||
switch. If the user runs this module and has an interface configured on
|
||||
the switch, but not found in the "allowed" list, this interface will be
|
||||
unconfigured. By default this is `/etc/network/interface.d`
|
||||
For more details go the Configuring Interfaces at
|
||||
http://docs.cumulusnetworks.com
|
||||
notes:
|
||||
- lo must be included in the allowed list.
|
||||
- eth0 must be in allowed list if out of band management is done
|
||||
options:
|
||||
allowed:
|
||||
description:
|
||||
- list of ports to run initial run at 10G
|
||||
location:
|
||||
description:
|
||||
- folder to store interface files.
|
||||
default: '/etc/network/interfaces.d/'
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
Example playbook entries using the cl_interface_policy module.
|
||||
|
||||
- name: shows types of interface ranges supported
|
||||
cl_interface_policy:
|
||||
allowed: "lo eth0 swp1-9, swp11, swp12-13s0, swp12-30s1, swp12-30s2, bond0-12"
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
|
||||
# get list of interface files that are currently "configured".
|
||||
# doesn't mean actually applied to the system, but most likely are
|
||||
def read_current_int_dir(module):
|
||||
module.custom_currentportlist = os.listdir(module.params.get('location'))
|
||||
|
||||
|
||||
# take the allowed list and conver it to into a list
|
||||
# of ports.
|
||||
def convert_allowed_list_to_port_range(module):
|
||||
allowedlist = module.params.get('allowed')
|
||||
for portrange in allowedlist:
|
||||
module.custom_allowedportlist += breakout_portrange(portrange)
|
||||
|
||||
|
||||
def breakout_portrange(prange):
|
||||
_m0 = re.match(r'(\w+[a-z.])(\d+)?-?(\d+)?(\w+)?', prange.strip())
|
||||
# no range defined
|
||||
if _m0.group(3) is None:
|
||||
return [_m0.group(0)]
|
||||
else:
|
||||
portarray = []
|
||||
intrange = range(int(_m0.group(2)), int(_m0.group(3)) + 1)
|
||||
for _int in intrange:
|
||||
portarray.append(''.join([_m0.group(1),
|
||||
str(_int),
|
||||
str(_m0.group(4) or '')
|
||||
]
|
||||
)
|
||||
)
|
||||
return portarray
|
||||
|
||||
|
||||
# deletes the interface files
|
||||
def unconfigure_interfaces(module):
|
||||
currentportset = set(module.custom_currentportlist)
|
||||
allowedportset = set(module.custom_allowedportlist)
|
||||
remove_list = currentportset.difference(allowedportset)
|
||||
fileprefix = module.params.get('location')
|
||||
module.msg = "remove config for interfaces %s" % (', '.join(remove_list))
|
||||
for _file in remove_list:
|
||||
os.unlink(fileprefix + _file)
|
||||
|
||||
|
||||
# check to see if policy should be enforced
|
||||
# returns true if policy needs to be enforced
|
||||
# that is delete interface files
|
||||
def int_policy_enforce(module):
|
||||
currentportset = set(module.custom_currentportlist)
|
||||
allowedportset = set(module.custom_allowedportlist)
|
||||
return not currentportset.issubset(allowedportset)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
allowed=dict(type='list', required=True),
|
||||
location=dict(type='str', default='/etc/network/interfaces.d/')
|
||||
),
|
||||
)
|
||||
module.custom_currentportlist = []
|
||||
module.custom_allowedportlist = []
|
||||
module.changed = False
|
||||
module.msg = 'configured port list is part of allowed port list'
|
||||
read_current_int_dir(module)
|
||||
convert_allowed_list_to_port_range(module)
|
||||
if int_policy_enforce(module):
|
||||
module.changed = True
|
||||
unconfigure_interfaces(module)
|
||||
module.exit_json(changed=module.changed, msg=module.msg)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
# from ansible.module_utils.urls import *
|
||||
import os
|
||||
import shutil
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,139 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_license
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Install Cumulus Linux license
|
||||
description:
|
||||
- Installs a Cumulus Linux license. The module reports no change of status
|
||||
when a license is installed.
|
||||
For more details go the Cumulus Linux License Documentation @
|
||||
http://docs.cumulusnetwork.com and the Licensing KB Site @
|
||||
https://support.cumulusnetworks.com/hc/en-us/sections/200507688
|
||||
notes:
|
||||
- to activate a license for the FIRST time, the switchd service must be
|
||||
restarted. This action is disruptive. The license renewal process occurs
|
||||
via the Cumulus Networks Customer Portal -
|
||||
http://customers.cumulusnetworks.com.
|
||||
- A non-EULA license is REQUIRED for automation. Manually install the
|
||||
license on a test switch, using the command "cl-license -i <license_file>"
|
||||
to confirm the license is a Non-EULA license.
|
||||
See EXAMPLES, for the proper way to issue this notify action.
|
||||
options:
|
||||
src:
|
||||
description:
|
||||
- full path to the license. Can be local path or http url
|
||||
force:
|
||||
description:
|
||||
- force installation of a license. Typically not needed.
|
||||
It is recommended to manually run this command via the ansible
|
||||
command. A reload of switchd is not required. Running the force
|
||||
option in a playbook will break the idempotent state machine of
|
||||
the module and cause the switchd notification to kick in all the
|
||||
time, causing a disruption.
|
||||
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
Example playbook using the cl_license module to manage licenses on Cumulus Linux
|
||||
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: install license using http url
|
||||
cl_license: src='http://10.1.1.1/license.txt'
|
||||
notify: restart switchd
|
||||
|
||||
- name: Triggers switchd to be restarted right away, before play, or role
|
||||
is over. This is desired behaviour
|
||||
meta: flush_handlers
|
||||
|
||||
- name: configure interfaces
|
||||
template: src=interfaces.j2 dest=/etc/network/interfaces
|
||||
notify: restart networking
|
||||
|
||||
handlers:
|
||||
- name: restart switchd
|
||||
service: name=switchd state=restarted
|
||||
- name: restart networking
|
||||
service: name=networking state=reloaded
|
||||
|
||||
----
|
||||
|
||||
# Force all switches to accept a new license. Typically not needed
|
||||
ansible -m cl_license -a "src='http://10.1.1.1/new_lic' force=yes" -u root all
|
||||
|
||||
----
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
CL_LICENSE_PATH='/usr/cumulus/bin/cl-license'
|
||||
|
||||
def install_license(module):
|
||||
# license is not installed, install it
|
||||
_url = module.params.get('src')
|
||||
(_rc, out, _err) = module.run_command("%s -i %s" % (CL_LICENSE_PATH, _url))
|
||||
if _rc > 0:
|
||||
module.fail_json(msg=_err)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
src=dict(required=True, type='str'),
|
||||
force=dict(type='bool', choices=BOOLEANS,
|
||||
default=False)
|
||||
),
|
||||
)
|
||||
|
||||
# check if license is installed
|
||||
# if force is enabled then set return code to nonzero
|
||||
if module.params.get('force') is True:
|
||||
_rc = 10
|
||||
else:
|
||||
(_rc, out, _err) = module.run_command(CL_LICENSE_PATH)
|
||||
if _rc == 0:
|
||||
module.msg = "No change. License already installed"
|
||||
module.changed = False
|
||||
else:
|
||||
install_license(module)
|
||||
module.msg = "License installation completed"
|
||||
module.changed = True
|
||||
module.exit_json(changed=module.changed, msg=module.msg)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
# from ansible.module_utils.urls import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,210 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_ports
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Configure Cumulus Switch port attributes (ports.conf)
|
||||
description:
|
||||
- Set the initial port attribute defined in the Cumulus Linux ports.conf,
|
||||
file. This module does not do any error checking at the moment. Be careful
|
||||
to not include ports that do not exist on the switch. Carefully read the
|
||||
original ports.conf file for any exceptions or limitations.
|
||||
For more details go the Configure Switch Port Attribute Documentation at
|
||||
http://docs.cumulusnetworks.com
|
||||
options:
|
||||
speed_10g:
|
||||
description:
|
||||
- list of ports to run initial run at 10G
|
||||
speed_40g:
|
||||
description:
|
||||
- list of ports to run initial run at 40G
|
||||
speed_4_by_10g:
|
||||
description:
|
||||
- list of 40G ports that will be unganged to run as 4 10G ports.
|
||||
speed_40g_div_4:
|
||||
description:
|
||||
- list of 10G ports that will be ganged to form a 40G port
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
Example playbook entries using the cl_ports module to manage the switch
|
||||
attributes defined in the ports.conf file on Cumulus Linux
|
||||
|
||||
## Unganged port config using simple args
|
||||
- name: configure ports.conf setup
|
||||
cl_ports: speed_4_by_10g="swp1, swp32" speed_40g="swp2-31"
|
||||
notify: restart switchd
|
||||
|
||||
## Unganged port configuration on certain ports using complex args
|
||||
|
||||
- name: configure ports.conf setup
|
||||
cl_ports:
|
||||
speed_4_by_10g: ['swp1-3', 'swp6']
|
||||
speed_40g: ['swp4-5', 'swp7-32']
|
||||
notify: restart switchd
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
PORTS_CONF = '/etc/cumulus/ports.conf'
|
||||
|
||||
|
||||
def hash_existing_ports_conf(module):
|
||||
module.ports_conf_hash = {}
|
||||
if not os.path.exists(PORTS_CONF):
|
||||
return False
|
||||
|
||||
try:
|
||||
existing_ports_conf = open(PORTS_CONF).readlines()
|
||||
except IOError, error_msg:
|
||||
_msg = "Failed to open %s: %s" % (PORTS_CONF, error_msg)
|
||||
module.fail_json(msg=_msg)
|
||||
return # for testing only should return on module.fail_json
|
||||
|
||||
for _line in existing_ports_conf:
|
||||
_m0 = re.match(r'^(\d+)=(\w+)', _line)
|
||||
if _m0:
|
||||
_portnum = int(_m0.group(1))
|
||||
_speed = _m0.group(2)
|
||||
module.ports_conf_hash[_portnum] = _speed
|
||||
|
||||
|
||||
def generate_new_ports_conf_hash(module):
|
||||
new_ports_conf_hash = {}
|
||||
convert_hash = {
|
||||
'speed_40g_div_4': '40G/4',
|
||||
'speed_4_by_10g': '4x10G',
|
||||
'speed_10g': '10G',
|
||||
'speed_40g': '40G'
|
||||
}
|
||||
for k in module.params.keys():
|
||||
port_range = module.params[k]
|
||||
port_setting = convert_hash[k]
|
||||
if port_range:
|
||||
port_range = [x for x in port_range if x]
|
||||
for port_str in port_range:
|
||||
port_range_str = port_str.replace('swp', '').split('-')
|
||||
if len(port_range_str) == 1:
|
||||
new_ports_conf_hash[int(port_range_str[0])] = \
|
||||
port_setting
|
||||
else:
|
||||
int_range = map(int, port_range_str)
|
||||
portnum_range = range(int_range[0], int_range[1]+1)
|
||||
for i in portnum_range:
|
||||
new_ports_conf_hash[i] = port_setting
|
||||
module.new_ports_hash = new_ports_conf_hash
|
||||
|
||||
|
||||
def compare_new_and_old_port_conf_hash(module):
|
||||
ports_conf_hash_copy = module.ports_conf_hash.copy()
|
||||
module.ports_conf_hash.update(module.new_ports_hash)
|
||||
port_num_length = len(module.ports_conf_hash.keys())
|
||||
orig_port_num_length = len(ports_conf_hash_copy.keys())
|
||||
if port_num_length != orig_port_num_length:
|
||||
module.fail_json(msg="Port numbering is wrong. \
|
||||
Too many or two few ports configured")
|
||||
return False
|
||||
elif ports_conf_hash_copy == module.ports_conf_hash:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def make_copy_of_orig_ports_conf(module):
|
||||
if os.path.exists(PORTS_CONF + '.orig'):
|
||||
return
|
||||
|
||||
try:
|
||||
shutil.copyfile(PORTS_CONF, PORTS_CONF + '.orig')
|
||||
except IOError, error_msg:
|
||||
_msg = "Failed to save the original %s: %s" % (PORTS_CONF, error_msg)
|
||||
module.fail_json(msg=_msg)
|
||||
return # for testing only
|
||||
|
||||
def write_to_ports_conf(module):
|
||||
"""
|
||||
use tempfile to first write out config in temp file
|
||||
then write to actual location. may help prevent file
|
||||
corruption. Ports.conf is a critical file for Cumulus.
|
||||
Don't want to corrupt this file under any circumstance.
|
||||
"""
|
||||
temp = tempfile.NamedTemporaryFile()
|
||||
try:
|
||||
try:
|
||||
temp.write('# Managed By Ansible\n')
|
||||
for k in sorted(module.ports_conf_hash.keys()):
|
||||
port_setting = module.ports_conf_hash[k]
|
||||
_str = "%s=%s\n" % (k, port_setting)
|
||||
temp.write(_str)
|
||||
temp.seek(0)
|
||||
shutil.copyfile(temp.name, PORTS_CONF)
|
||||
except IOError, error_msg:
|
||||
module.fail_json(
|
||||
msg="Failed to write to %s: %s" % (PORTS_CONF, error_msg))
|
||||
finally:
|
||||
temp.close()
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
speed_40g_div_4=dict(type='list'),
|
||||
speed_4_by_10g=dict(type='list'),
|
||||
speed_10g=dict(type='list'),
|
||||
speed_40g=dict(type='list')
|
||||
),
|
||||
required_one_of=[['speed_40g_div_4',
|
||||
'speed_4_by_10g',
|
||||
'speed_10g',
|
||||
'speed_40g']]
|
||||
)
|
||||
|
||||
_changed = False
|
||||
hash_existing_ports_conf(module)
|
||||
generate_new_ports_conf_hash(module)
|
||||
if compare_new_and_old_port_conf_hash(module):
|
||||
make_copy_of_orig_ports_conf(module)
|
||||
write_to_ports_conf(module)
|
||||
_changed = True
|
||||
_msg = "/etc/cumulus/ports.conf changed"
|
||||
else:
|
||||
_msg = 'No change in /etc/ports.conf'
|
||||
module.exit_json(changed=_changed, msg=_msg)
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
# from ansible.module_utils.urls import *
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,214 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_prefix_check
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Check to see if route/prefix exists
|
||||
description:
|
||||
- Check to see if a route exists. This module can be used simply to check a
|
||||
route and return if its present or absent. A larger timeout can be
|
||||
provided to check if a route disappears. An example would be the user
|
||||
could change the OSPF cost of a node within the network then utilize
|
||||
cl_prefix_check of another (separate) node to verify the node (where the
|
||||
OSPF cost was changed) is not being use to route traffic.
|
||||
options:
|
||||
prefix:
|
||||
description:
|
||||
- route/prefix that module is checking for. Uses format acceptable
|
||||
to "ip route show" command. See manpage of "ip-route" for more
|
||||
details
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Describes if the prefix should be present.
|
||||
choices: ['present', 'absent']
|
||||
default: ['present']
|
||||
timeout:
|
||||
description:
|
||||
- timeout in seconds to wait for route condition to be met
|
||||
default: 5
|
||||
poll_interval:
|
||||
description:
|
||||
- poll interval in seconds to check route.
|
||||
default: 1
|
||||
nonexthop:
|
||||
description:
|
||||
- address of node is not desired in result to prefix
|
||||
default: ""
|
||||
nexthop:
|
||||
description:
|
||||
- address of node is desired in result to prefix
|
||||
default: ""
|
||||
|
||||
notes:
|
||||
- IP Route Documentation -
|
||||
http://manpages.ubuntu.com/manpages/precise/man8/route.8.html
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
Example playbook entries using the cl_prefix_check module to check if a prefix
|
||||
exists
|
||||
|
||||
tasks:
|
||||
- name: Test if prefix is present.
|
||||
cl_prefix_check: prefix=4.4.4.0/24
|
||||
|
||||
- name: Test if route is absent. poll for 200 seconds. Poll interval at
|
||||
default setting of 1 second
|
||||
cl_prefix_check: prefix=10.0.1.0/24 timeout=200 state=absent
|
||||
|
||||
- name: Test if route is present, with a timeout of 10 seconds and poll
|
||||
interval of 2 seconds
|
||||
cl_prefix_check: prefix=10.1.1.0/24 timeout=10 poll_interval=2
|
||||
|
||||
- name: Test if route is present, with a nexthop of 4.4.4.4 will fail if no
|
||||
nexthop of 5.5.5.5
|
||||
cl_prefix_check: prefix=4.4.4.4 nexthop=5.5.5.5
|
||||
|
||||
- name: Test if route is present, with no nexthop of 3.3.3.3 will fail if
|
||||
there is a nexthop of 6.6.6.6
|
||||
cl_prefix_check: prefix=3.3.3.3 nonexthop=6.6.6.6
|
||||
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
def run_cl_cmd(module, cmd, check_rc=True):
|
||||
try:
|
||||
(rc, out, err) = module.run_command(cmd, check_rc=check_rc)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=e.strerror)
|
||||
# trim last line as it is always empty
|
||||
ret = out.splitlines()
|
||||
f = open('workfile', 'w')
|
||||
for a in ret:
|
||||
f.write(a)
|
||||
return ret
|
||||
|
||||
def route_is_present(result):
|
||||
if len(result) > 0:
|
||||
return True
|
||||
|
||||
def route_is_absent(result):
|
||||
if len(result) == 0:
|
||||
return True
|
||||
|
||||
def check_hop(result,hop):
|
||||
for line in result:
|
||||
if hop in line.split():
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_next_hops(module, result):
|
||||
nexthop = module.params.get('nexthop')
|
||||
nonexthop = module.params.get('nonexthop')
|
||||
prefix = module.params.get('prefix')
|
||||
|
||||
if not nexthop and not nonexthop:
|
||||
return True
|
||||
elif not nexthop and nonexthop:
|
||||
if check_hop(result,nonexthop)==False:
|
||||
return True
|
||||
elif nexthop and not nonexthop:
|
||||
if check_hop(result,nexthop)==True:
|
||||
return True
|
||||
elif nexthop and nonexthop:
|
||||
if check_hop(result,nexthop)==True and check_hop(result,nonexthop)==False:
|
||||
return True
|
||||
else:
|
||||
return false
|
||||
|
||||
def loop_route_check(module):
|
||||
prefix = module.params.get('prefix')
|
||||
state = module.params.get('state')
|
||||
timeout = int(module.params.get('timeout'))
|
||||
poll_interval = int(module.params.get('poll_interval'))
|
||||
|
||||
# using ip route show instead of ip route get
|
||||
# because ip route show will be blank if the exact prefix
|
||||
# is missing from the table. ip route get tries longest prefix
|
||||
# match so may match default route.
|
||||
# command returns empty array if prefix is missing
|
||||
cl_prefix_cmd = '/sbin/ip route show %s' % (prefix)
|
||||
time_elapsed = 0
|
||||
while True:
|
||||
result = run_cl_cmd(module, cl_prefix_cmd)
|
||||
if state == 'present' and route_is_present(result):
|
||||
if check_next_hops(module, result)==True:
|
||||
return True
|
||||
if state == 'absent' and route_is_absent(result):
|
||||
if check_next_hops(module, result)==True:
|
||||
return True
|
||||
time.sleep(poll_interval)
|
||||
time_elapsed += poll_interval
|
||||
if time_elapsed == timeout:
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
prefix=dict(required=True, type='str'),
|
||||
state=dict(default='present', type='str',
|
||||
choices=['present', 'absent']),
|
||||
timeout=dict(default=2, type='int'),
|
||||
poll_interval=dict(default=1, type='int'),
|
||||
nexthop=dict(default='', type='str'),
|
||||
nonexthop=dict(default='', type='str'),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
_state = module.params.get('state')
|
||||
_timeout = module.params.get('timeout')
|
||||
_msg = "Testing whether route is %s. " % (_state)
|
||||
_nexthop = module.params.get('nexthop')
|
||||
_nonexthop = module.params.get('nonexthop')
|
||||
|
||||
#checking for bad parameters
|
||||
if _nexthop == _nonexthop and _nexthop != '':
|
||||
module.fail_json(msg='nexthop and nonexthop cannot be the same')
|
||||
|
||||
#the loop
|
||||
if loop_route_check(module):
|
||||
_msg += 'Condition Met'
|
||||
module.exit_json(msg=_msg, changed=False)
|
||||
else:
|
||||
_msg += 'Condition not met %s second timer expired' % (_timeout)
|
||||
module.fail_json(msg='paremeters not found')
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
import time
|
||||
# from ansible.module_utils.urls import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,469 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_quagga_ospf
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Configure basic OSPFv2 parameters and interfaces using Quagga
|
||||
description:
|
||||
- Configures basic OSPFv2 global parameters such as
|
||||
router id and bandwidth cost, or OSPFv2 interface configuration like
|
||||
point-to-point settings or enabling OSPFv2 on an interface. Configuration
|
||||
is applied to single OSPFv2 instance. Multiple OSPFv2 instance
|
||||
configuration is currently not supported. It requires Quagga version
|
||||
0.99.22 and higher with the non-modal Quagga CLI developed by Cumulus
|
||||
Linux. For more details go to the Routing User Guide at
|
||||
http://docs.cumulusnetworks.com/ and Quagga Docs at
|
||||
http://www.nongnu.org/quagga/
|
||||
options:
|
||||
router_id:
|
||||
description:
|
||||
- Set the OSPFv2 router id
|
||||
required: true
|
||||
reference_bandwidth:
|
||||
description:
|
||||
- Set the OSPFv2 auto cost reference bandwidth
|
||||
default: 40000
|
||||
saveconfig:
|
||||
description:
|
||||
- Boolean. Issue write memory to save the config
|
||||
choices: ['yes', 'no']
|
||||
default: ['no']
|
||||
interface:
|
||||
description:
|
||||
- define the name the interface to apply OSPFv2 services.
|
||||
point2point:
|
||||
description:
|
||||
- Boolean. enable OSPFv2 point2point on the interface
|
||||
choices: ['yes', 'no']
|
||||
require_together:
|
||||
- with interface option
|
||||
area:
|
||||
description:
|
||||
- defines the area the interface is in
|
||||
required_together:
|
||||
- with interface option
|
||||
cost:
|
||||
description:
|
||||
- define ospf cost.
|
||||
required_together:
|
||||
- with interface option
|
||||
passive:
|
||||
description:
|
||||
- make OSPFv2 interface passive
|
||||
choices: ['yes', 'no']
|
||||
required_together:
|
||||
- with interface option
|
||||
state:
|
||||
description:
|
||||
- Describes if OSPFv2 should be present on a particular interface.
|
||||
Module currently does not check that interface is not associated
|
||||
with a bond or bridge. User will have to manually clear the
|
||||
configuration of the interface from the bond or bridge. This will
|
||||
be implemented in a later release
|
||||
choices: [ 'present', 'absent']
|
||||
default: 'present'
|
||||
required_together:
|
||||
- with interface option
|
||||
requirements: ['Cumulus Linux Quagga non-modal CLI, Quagga version 0.99.22 and higher']
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
Example playbook entries using the cl_quagga_ospf module
|
||||
|
||||
tasks:
|
||||
- name: configure ospf router_id
|
||||
cl_quagga_ospf: router_id=10.1.1.1
|
||||
- name: enable OSPFv2 on swp1 and set it be a point2point OSPF
|
||||
interface with a cost of 65535
|
||||
cl_quagga_ospf: interface=swp1 point2point=yes cost=65535
|
||||
- name: enable ospf on swp1-5
|
||||
cl_quagga_ospf: interface={{ item }}
|
||||
with_sequence: start=1 end=5 format=swp%d
|
||||
- name: disable ospf on swp1
|
||||
cl_quagga_ospf: interface=swp1 state=absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
|
||||
def run_cl_cmd(module, cmd, check_rc=True, split_lines=True):
|
||||
try:
|
||||
(rc, out, err) = module.run_command(cmd, check_rc=check_rc)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=e.strerror)
|
||||
# trim last line as it is always empty
|
||||
if split_lines:
|
||||
ret = out.splitlines()
|
||||
else:
|
||||
ret = out
|
||||
return ret
|
||||
|
||||
|
||||
def check_dsl_dependencies(module, input_options,
|
||||
dependency, _depend_value):
|
||||
for _param in input_options:
|
||||
if module.params.get(_param):
|
||||
if not module.params.get(dependency):
|
||||
_param_output = module.params.get(_param)
|
||||
_msg = "incorrect syntax. " + _param + " must have an interface option." + \
|
||||
" Example 'cl_quagga_ospf: " + dependency + "=" + _depend_value + " " + \
|
||||
_param + "=" + _param_output + "'"
|
||||
module.fail_json(msg=_msg)
|
||||
|
||||
|
||||
def has_interface_config(module):
|
||||
if module.params.get('interface') is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_running_config(module):
|
||||
running_config = run_cl_cmd(module, '/usr/bin/vtysh -c "show run"')
|
||||
got_global_config = False
|
||||
got_interface_config = False
|
||||
module.interface_config = {}
|
||||
module.global_config = []
|
||||
for line in running_config:
|
||||
line = line.lower().strip()
|
||||
# ignore the '!' lines or blank lines
|
||||
if len(line.strip()) <= 1:
|
||||
if got_global_config:
|
||||
got_global_config = False
|
||||
if got_interface_config:
|
||||
got_interface_config = False
|
||||
continue
|
||||
# begin capturing global config
|
||||
m0 = re.match('router\s+ospf', line)
|
||||
if m0:
|
||||
got_global_config = True
|
||||
continue
|
||||
m1 = re.match('^interface\s+(\w+)', line)
|
||||
if m1:
|
||||
module.ifacename = m1.group(1)
|
||||
module.interface_config[module.ifacename] = []
|
||||
got_interface_config = True
|
||||
continue
|
||||
if got_interface_config:
|
||||
module.interface_config[module.ifacename].append(line)
|
||||
continue
|
||||
if got_global_config:
|
||||
m3 = re.match('\s*passive-interface\s+(\w+)', line)
|
||||
if m3:
|
||||
ifaceconfig = module.interface_config.get(m3.group(1))
|
||||
if ifaceconfig:
|
||||
ifaceconfig.append('passive-interface')
|
||||
else:
|
||||
module.global_config.append(line)
|
||||
continue
|
||||
|
||||
|
||||
def get_config_line(module, stmt, ifacename=None):
|
||||
if ifacename:
|
||||
pass
|
||||
else:
|
||||
for i in module.global_config:
|
||||
if re.match(stmt, i):
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def update_router_id(module):
|
||||
router_id_stmt = 'ospf router-id '
|
||||
actual_router_id_stmt = get_config_line(module, router_id_stmt)
|
||||
router_id_stmt = 'ospf router-id ' + module.params.get('router_id')
|
||||
if router_id_stmt != actual_router_id_stmt:
|
||||
cmd_line = "/usr/bin/cl-ospf router-id set %s" %\
|
||||
(module.params.get('router_id'))
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.exit_msg += 'router-id updated '
|
||||
module.has_changed = True
|
||||
|
||||
|
||||
def update_reference_bandwidth(module):
|
||||
bandwidth_stmt = 'auto-cost reference-bandwidth'
|
||||
actual_bandwidth_stmt = get_config_line(module, bandwidth_stmt)
|
||||
bandwidth_stmt = bandwidth_stmt + ' ' + \
|
||||
module.params.get('reference_bandwidth')
|
||||
if bandwidth_stmt != actual_bandwidth_stmt:
|
||||
cmd_line = "/usr/bin/cl-ospf auto-cost set reference-bandwidth %s" %\
|
||||
(module.params.get('reference_bandwidth'))
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.exit_msg += 'reference bandwidth updated '
|
||||
module.has_changed = True
|
||||
|
||||
|
||||
def add_global_ospf_config(module):
|
||||
module.has_changed = False
|
||||
get_running_config(module)
|
||||
if module.params.get('router_id'):
|
||||
update_router_id(module)
|
||||
if module.params.get('reference_bandwidth'):
|
||||
update_reference_bandwidth(module)
|
||||
if module.has_changed is False:
|
||||
module.exit_msg = 'No change in OSPFv2 global config'
|
||||
module.exit_json(msg=module.exit_msg, changed=module.has_changed)
|
||||
|
||||
|
||||
def check_ip_addr_show(module):
|
||||
cmd_line = "/sbin/ip addr show %s" % (module.params.get('interface'))
|
||||
result = run_cl_cmd(module, cmd_line)
|
||||
for _line in result:
|
||||
m0 = re.match('\s+inet\s+\w+', _line)
|
||||
if m0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_interface_addr_config(module):
|
||||
ifacename = module.params.get('interface')
|
||||
cmd_line = "/sbin/ifquery --format json %s" % (ifacename)
|
||||
int_config = run_cl_cmd(module, cmd_line, True, False)
|
||||
ifquery_obj = json.loads(int_config)[0]
|
||||
iface_has_address = False
|
||||
if 'address' in ifquery_obj.get('config'):
|
||||
for addr in ifquery_obj.get('config').get('address'):
|
||||
try:
|
||||
socket.inet_aton(addr.split('/')[0])
|
||||
iface_has_address = True
|
||||
break
|
||||
except socket.error:
|
||||
pass
|
||||
else:
|
||||
iface_has_address = check_ip_addr_show(module)
|
||||
if iface_has_address is False:
|
||||
_msg = "interface %s does not have an IP configured. " +\
|
||||
"Required for OSPFv2 to work"
|
||||
module.fail_json(msg=_msg)
|
||||
# for test purposes only
|
||||
return iface_has_address
|
||||
|
||||
|
||||
def enable_or_disable_ospf_on_int(module):
|
||||
ifacename = module.params.get('interface')
|
||||
_state = module.params.get('state')
|
||||
iface_config = module.interface_config.get(ifacename)
|
||||
if iface_config is None:
|
||||
_msg = "%s is not found in Quagga config. " % (ifacename) + \
|
||||
"Check that %s is active in kernel" % (ifacename)
|
||||
module.fail_json(msg=_msg)
|
||||
return False # for test purposes
|
||||
found_area = None
|
||||
for i in iface_config:
|
||||
m0 = re.search('ip\s+ospf\s+area\s+([0-9.]+)', i)
|
||||
if m0:
|
||||
found_area = m0.group(1)
|
||||
break
|
||||
if _state == 'absent':
|
||||
for i in iface_config:
|
||||
if found_area:
|
||||
cmd_line = '/usr/bin/cl-ospf clear %s area' % \
|
||||
(ifacename)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += "OSPFv2 now disabled on %s " % (ifacename)
|
||||
return False
|
||||
area_id = module.params.get('area')
|
||||
if found_area != area_id:
|
||||
cmd_line = '/usr/bin/cl-ospf interface set %s area %s' % \
|
||||
(ifacename, area_id)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += "OSPFv2 now enabled on %s area %s " % \
|
||||
(ifacename, area_id)
|
||||
return True
|
||||
|
||||
|
||||
def update_point2point(module):
|
||||
ifacename = module.params.get('interface')
|
||||
point2point = module.params.get('point2point')
|
||||
iface_config = module.interface_config.get(ifacename)
|
||||
found_point2point = None
|
||||
for i in iface_config:
|
||||
m0 = re.search('ip\s+ospf\s+network\s+point-to-point', i)
|
||||
if m0:
|
||||
found_point2point = True
|
||||
break
|
||||
if point2point:
|
||||
if not found_point2point:
|
||||
cmd_line = '/usr/bin/cl-ospf interface set %s network point-to-point' % \
|
||||
(ifacename)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += 'OSPFv2 point2point set on %s ' % (ifacename)
|
||||
else:
|
||||
if found_point2point:
|
||||
cmd_line = '/usr/bin/cl-ospf interface clear %s network' % \
|
||||
(ifacename)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += 'OSPFv2 point2point removed on %s ' % \
|
||||
(ifacename)
|
||||
|
||||
|
||||
def update_passive(module):
|
||||
ifacename = module.params.get('interface')
|
||||
passive = module.params.get('passive')
|
||||
iface_config = module.interface_config.get(ifacename)
|
||||
found_passive = None
|
||||
for i in iface_config:
|
||||
m0 = re.search('passive-interface', i)
|
||||
if m0:
|
||||
found_passive = True
|
||||
break
|
||||
if passive:
|
||||
if not found_passive:
|
||||
cmd_line = '/usr/bin/cl-ospf interface set %s passive' % \
|
||||
(ifacename)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += '%s is now OSPFv2 passive ' % (ifacename)
|
||||
else:
|
||||
if found_passive:
|
||||
cmd_line = '/usr/bin/cl-ospf interface clear %s passive' % \
|
||||
(ifacename)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += '%s is no longer OSPFv2 passive ' % \
|
||||
(ifacename)
|
||||
|
||||
|
||||
def update_cost(module):
|
||||
ifacename = module.params.get('interface')
|
||||
cost = module.params.get('cost')
|
||||
iface_config = module.interface_config.get(ifacename)
|
||||
found_cost = None
|
||||
for i in iface_config:
|
||||
m0 = re.search('ip\s+ospf\s+cost\s+(\d+)', i)
|
||||
if m0:
|
||||
found_cost = m0.group(1)
|
||||
break
|
||||
|
||||
if cost != found_cost and cost is not None:
|
||||
cmd_line = '/usr/bin/cl-ospf interface set %s cost %s' % \
|
||||
(ifacename, cost)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += 'OSPFv2 cost on %s changed to %s ' % \
|
||||
(ifacename, cost)
|
||||
elif cost is None and found_cost is not None:
|
||||
cmd_line = '/usr/bin/cl-ospf interface clear %s cost' % \
|
||||
(ifacename)
|
||||
run_cl_cmd(module, cmd_line)
|
||||
module.has_changed = True
|
||||
module.exit_msg += 'OSPFv2 cost on %s changed to default ' % \
|
||||
(ifacename)
|
||||
|
||||
|
||||
def config_ospf_interface_config(module):
|
||||
enable_int_defaults(module)
|
||||
module.has_changed = False
|
||||
# get all ospf related config from quagga both globally and iface based
|
||||
get_running_config(module)
|
||||
# if interface does not have ipv4 address module should fail
|
||||
get_interface_addr_config(module)
|
||||
# if ospf should be enabled, continue to check for the remaining attrs
|
||||
if enable_or_disable_ospf_on_int(module):
|
||||
# update ospf point-to-point setting if needed
|
||||
update_point2point(module)
|
||||
# update ospf interface cost if needed
|
||||
update_cost(module)
|
||||
# update ospf interface passive setting
|
||||
update_passive(module)
|
||||
|
||||
|
||||
def saveconfig(module):
|
||||
if module.params.get('saveconfig') is True and\
|
||||
module.has_changed:
|
||||
run_cl_cmd(module, '/usr/bin/vtysh -c "wr mem"')
|
||||
module.exit_msg += 'Saving Config '
|
||||
|
||||
|
||||
def enable_int_defaults(module):
|
||||
if not module.params.get('area'):
|
||||
module.params['area'] = '0.0.0.0'
|
||||
if not module.params.get('state'):
|
||||
module.params['state'] = 'present'
|
||||
|
||||
|
||||
def check_if_ospf_is_running(module):
|
||||
if not os.path.exists('/var/run/quagga/ospfd.pid'):
|
||||
_msg = 'OSPFv2 process is not running. Unable to execute command'
|
||||
module.fail_json(msg=_msg)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
reference_bandwidth=dict(type='str',
|
||||
default='40000'),
|
||||
router_id=dict(type='str'),
|
||||
interface=dict(type='str'),
|
||||
cost=dict(type='str'),
|
||||
area=dict(type='str'),
|
||||
state=dict(type='str',
|
||||
choices=['present', 'absent']),
|
||||
point2point=dict(type='bool', choices=BOOLEANS),
|
||||
saveconfig=dict(type='bool', choices=BOOLEANS, default=False),
|
||||
passive=dict(type='bool', choices=BOOLEANS)
|
||||
),
|
||||
mutually_exclusive=[['reference_bandwidth', 'interface'],
|
||||
['router_id', 'interface']]
|
||||
)
|
||||
check_if_ospf_is_running(module)
|
||||
|
||||
check_dsl_dependencies(module, ['cost', 'state', 'area',
|
||||
'point2point', 'passive'],
|
||||
'interface', 'swp1')
|
||||
module.has_changed = False
|
||||
module.exit_msg = ''
|
||||
if has_interface_config(module):
|
||||
config_ospf_interface_config(module)
|
||||
else:
|
||||
# Set area to none before applying global config
|
||||
module.params['area'] = None
|
||||
add_global_ospf_config(module)
|
||||
saveconfig(module)
|
||||
if module.has_changed:
|
||||
module.exit_json(msg=module.exit_msg, changed=module.has_changed)
|
||||
else:
|
||||
module.exit_json(msg='no change', changed=False)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
# incompatible with ansible 1.4.4 - ubuntu 12.04 version
|
||||
# from ansible.module_utils.urls import *
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,212 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Cumulus Networks <ce-ceng@cumulusnetworks.com>
|
||||
#
|
||||
# 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: cl_quagga_protocol
|
||||
version_added: "2.1"
|
||||
author: "Cumulus Networks (@CumulusNetworks)"
|
||||
short_description: Enable routing protocol services via Quagga
|
||||
description:
|
||||
- Enable Quagga services available on Cumulus Linux. This includes OSPF
|
||||
v2/v3 and BGP. Quagga services are defined in the /etc/quagga/daemons
|
||||
file. This module creates a file that will only enable OSPF or BGP routing
|
||||
protocols, because this is what Cumulus Linux currently supports. Zebra is
|
||||
automatically enabled when a supported routing protocol is listed. If all
|
||||
routing protocols are disabled, this module will disable zebra as well.
|
||||
Using Ansible Templates you can run any supported or unsupported quagga
|
||||
routing protocol. For more details go to the Quagga Documentation located
|
||||
at http://docs.cumulusnetworks.com/ and at
|
||||
http://www.nongnu.org/quagga/docs.html
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the protocol to update
|
||||
choices: ['ospfd', 'ospf6d', 'bgpd']
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- describe whether the protocol should be enabled or disabled
|
||||
choices: ['present', 'absent']
|
||||
required: true
|
||||
activate:
|
||||
description:
|
||||
- restart quagga process to activate the change. If the service
|
||||
is already configured but not activated, setting activate=yes will
|
||||
not activate the service. This will be fixed in an upcoming
|
||||
release
|
||||
choices: ['yes', 'no']
|
||||
default: ['no']
|
||||
requirements: ['Quagga version 0.99.23 and higher']
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
Example playbook entries using the cl_quagga module
|
||||
|
||||
## Enable OSPFv2. Do not activate the change
|
||||
cl_quagga_protocol name="ospfd" state=present
|
||||
|
||||
## Disable OSPFv2. Do not activate the change
|
||||
cl_quagga_protocol name="ospf6d" state=absent
|
||||
|
||||
## Enable BGPv2. Do not activate the change. Activating the change requires a
|
||||
## restart of the entire quagga process.
|
||||
cl_quagga_protocol name="bgpd" state=present
|
||||
|
||||
## Enable OSPFv2 and activate the change as this might not start quagga when you
|
||||
## want it to
|
||||
cl_quagga_protocol name="ospfd" state=present activate=yes
|
||||
|
||||
## To activate a configured service
|
||||
|
||||
- name: disable ospfv2 service. Its configured but not enabled
|
||||
cl_quagga_protocol name=ospfd state=absent
|
||||
|
||||
- name: enable ospfv2 service and activate it
|
||||
cl_quagga_protocol name=ospfd state=present activate=yes
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: whether the interface was changed
|
||||
returned: changed
|
||||
type: bool
|
||||
sample: True
|
||||
msg:
|
||||
description: human-readable report of success or failure
|
||||
returned: always
|
||||
type: string
|
||||
sample: "interface bond0 config updated"
|
||||
'''
|
||||
|
||||
|
||||
def run_cl_cmd(module, cmd, check_rc=True):
|
||||
try:
|
||||
(rc, out, err) = module.run_command(cmd, check_rc=check_rc)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=e.strerror)
|
||||
# trim last line as it is always empty
|
||||
ret = out.splitlines()
|
||||
return ret
|
||||
|
||||
|
||||
def convert_to_yes_or_no(_state):
|
||||
if _state == 'present':
|
||||
_str = 'yes'
|
||||
else:
|
||||
_str = 'no'
|
||||
return _str
|
||||
|
||||
|
||||
def read_daemon_file(module):
|
||||
f = open(module.quagga_daemon_file)
|
||||
if f:
|
||||
return f.readlines()
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def setting_is_configured(module):
|
||||
_protocol = module.params.get('name')
|
||||
_state = module.params.get('state')
|
||||
_state = convert_to_yes_or_no(_state)
|
||||
_daemon_output = read_daemon_file(module)
|
||||
_str = "(%s)=(%s)" % (_protocol, 'yes|no')
|
||||
_daemonstr = re.compile("\w+=yes")
|
||||
_zebrastr = re.compile("zebra=(yes|no)")
|
||||
_matchstr = re.compile(_str)
|
||||
daemoncount = 0
|
||||
module.disable_zebra = False
|
||||
for _line in _daemon_output:
|
||||
_match = re.match(_matchstr, _line)
|
||||
_active_daemon_match = re.match(_daemonstr, _line)
|
||||
_zebramatch = re.match(_zebrastr, _line)
|
||||
if _active_daemon_match:
|
||||
daemoncount += 1
|
||||
if _zebramatch:
|
||||
if _zebramatch.group(1) == 'no' and _state == 'yes':
|
||||
return False
|
||||
elif _match:
|
||||
if _state == _match.group(2):
|
||||
_msg = "%s is configured and is %s" % \
|
||||
(_protocol, module.params.get('state'))
|
||||
module.exit_json(msg=_msg, changed=False)
|
||||
# for nosetests purposes only
|
||||
if daemoncount < 3 and _state == 'no':
|
||||
module.disable_zebra = True
|
||||
return False
|
||||
|
||||
|
||||
def modify_config(module):
|
||||
_protocol = module.params.get('name')
|
||||
_state = module.params.get('state')
|
||||
_state = convert_to_yes_or_no(_state)
|
||||
_daemon_output = read_daemon_file(module)
|
||||
_str = "(%s)=(%s)" % (_protocol, 'yes|no')
|
||||
_zebrastr = re.compile("zebra=(yes|no)")
|
||||
_matchstr = re.compile(_str)
|
||||
write_to_file = open(module.quagga_daemon_file, 'w')
|
||||
for _line in _daemon_output:
|
||||
_match = re.match(_matchstr, _line)
|
||||
_zebramatch = re.match(_zebrastr, _line)
|
||||
if _zebramatch:
|
||||
if module.disable_zebra is True and _state == 'no':
|
||||
write_to_file.write('zebra=no\n')
|
||||
elif _state == 'yes':
|
||||
write_to_file.write('zebra=yes\n')
|
||||
else:
|
||||
write_to_file.write(_line)
|
||||
elif _match:
|
||||
if _state != _match.group(2):
|
||||
_str = "%s=%s\n" % (_protocol, _state)
|
||||
write_to_file.write(_str)
|
||||
else:
|
||||
write_to_file.write(_line)
|
||||
write_to_file.close()
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(type='str',
|
||||
choices=['ospfd', 'ospf6d', 'bgpd'],
|
||||
required=True),
|
||||
state=dict(type='str',
|
||||
choices=['present', 'absent'],
|
||||
required=True),
|
||||
activate=dict(type='bool', choices=BOOLEANS, default=False)
|
||||
))
|
||||
module.quagga_daemon_file = '/etc/quagga/daemons'
|
||||
setting_is_configured(module)
|
||||
modify_config(module)
|
||||
_protocol = module.params.get('name')
|
||||
_state = module.params.get('state')
|
||||
_state = convert_to_yes_or_no(_state)
|
||||
_msg = "%s protocol setting modified to %s" % \
|
||||
(_protocol, _state)
|
||||
if module.params.get('activate') is True:
|
||||
run_cl_cmd(module, '/usr/sbin/service quagga restart')
|
||||
_msg += '. Restarted Quagga Service'
|
||||
module.exit_json(msg=_msg, changed=True)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
# incompatible with ansible 1.4.4 - ubuntu 12.04 version
|
||||
# from ansible.module_utils.urls import *
|
||||
import re
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue