316 lines
11 KiB
Python
316 lines
11 KiB
Python
#!/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.
|
|
default: None
|
|
required: false
|
|
switch_slot:
|
|
description:
|
|
- Switch slots after installing the image.
|
|
To run the installed code, reboot the switch
|
|
choices: ['yes', 'no']
|
|
default: 'no'
|
|
required: false
|
|
|
|
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 = get_exception()
|
|
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()
|