|
|
|
#!/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
|
|
|
|
U(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 = get_exception()
|
|
|
|
_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 = get_exception()
|
|
|
|
_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 = get_exception()
|
|
|
|
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()
|