Created
September 8, 2021 01:21
-
-
Save f5-rahm/4842a41176a308ef833a66714f57bdf5 to your computer and use it in GitHub Desktop.
INCOMPLETE - Install tmos on BIG-IP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################################################################################### | |
# tmos_install.py | |
# v 0.1 - INCOMPLETE | |
# | |
# author: jason rahm | |
# | |
# usage: tmos_install.py [-h] [-a] inventory user password | |
# | |
# positional arguments: | |
# inventory inventory file (host IP/FQDN,image path) | |
# user BIG-IP username | |
# password BIG-IP password | |
# | |
# optional arguments: | |
# -h, --help show this help message and exit | |
# -a, --all update all devices in inventory, not just standby devices | |
# | |
# Example Inventory File Contents | |
# ltm3.test.local,/Users/citizenelah/Downloads/BIGIP-15.1.2.1-0.0.10.iso | |
# ltm13.test.local,/Users/citizenelah/Downloads/BIGIP-13.1.3.6-0.0.4.iso | |
################################################################################### | |
from bigrest.bigip import BIGIP | |
from concurrent.futures import ThreadPoolExecutor | |
from datetime import datetime | |
from logging import getLogger, INFO, StreamHandler | |
from pathlib import Path | |
from time import sleep | |
import argparse | |
import sys | |
def build_parser(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"-a", | |
"--all", | |
action="store_true", | |
help="install tmos on all devices in inventory, not just standby devices", | |
) | |
parser.add_argument( | |
"-u", | |
"--update", | |
action="store_true", | |
help="copy config to new tmos installation" | |
) | |
parser.add_argument( | |
"-r", | |
"--reboot", | |
action="store_true", | |
help="reboot" | |
) | |
parser.add_argument("inventory", help="inventory file (host IP/FQDN,image path)") | |
parser.add_argument("user", help="BIG-IP username") | |
parser.add_argument("password", help="BIG-IP password") | |
return parser.parse_args() | |
def instantiate_bigip(host, user, password): | |
try: | |
obj = BIGIP(host, user, password) | |
except Exception as e: | |
print(f"Failed to connect to {host} due to {type(e).__name__}:\n") | |
print(f"{e}") | |
sys.exit() | |
return obj | |
def get_logger(): | |
logger = getLogger(__name__) | |
logger.addHandler(StreamHandler()) | |
logger.setLevel(INFO) | |
return logger | |
def is_standby(obj): | |
status = obj.load("/mgmt/tm/sys/failover") | |
if "standby" in status.properties.get('apiRawValues').get('apiAnonymous'): | |
return "standby" | |
else: | |
return "active" | |
def get_time(format_type=None): | |
if format_type == "file_name": | |
return datetime.now().strftime("%Y%m%dT%H%M%S") | |
else: | |
return datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
def get_active_volume(obj): | |
# Get all volumes | |
volumes = obj.load("/mgmt/tm/sys/software/volume") | |
for volume in volumes: | |
if volume.properties.get("active"): | |
vol = volume.properties.get("name") | |
break | |
return vol | |
def get_available_volume(obj, host): | |
# Get all volumes | |
volumes = obj.load("/mgmt/tm/sys/software/volume") | |
available_volumes = [] | |
for volume in volumes: | |
if volume.properties.get("active") is None: | |
available_volumes.append(volume.properties.get("name")) | |
else: | |
active_volume = volume.properties.get("name") | |
if len(available_volumes) == 0: | |
vol = list(active_volume) | |
new_volume = f"{active_volume[:4]}{int(vol[4])+1}" | |
volume_status = "new" | |
else: | |
# TODO make this a flag to re-use or create new? | |
new_volume = available_volumes[-1] | |
volume_status = "existing" | |
LOGGER.info( | |
f"{get_time()} - active volume on {host}: {active_volume}, new volume: {new_volume}" | |
) | |
return new_volume, volume_status | |
def install_image_status(obj, image, volume): | |
if obj.exist(f"/mgmt/tm/sys/software/volume/{volume}"): | |
volume_details = obj.load(f"/mgmt/tm/sys/software/volume/{volume}") | |
if ( | |
volume_details.properties.get("version") == image.split("-")[1] | |
and volume_details.properties.get("status") == "complete" | |
): | |
LOGGER.info( | |
f"{get_time()} - {image} installation complete and system is ready for config copy and reboot" | |
) | |
return True | |
else: | |
LOGGER.info( | |
f'{get_time()} - {image} installation status: {volume_details.properties.get("status")}' | |
) | |
return False | |
else: | |
return False | |
def install_image(obj, image, host): | |
vol, status = get_available_volume(obj, host) | |
if status == "new": | |
options = {"create-volume": True} | |
data = { | |
"command": "install", | |
"name": f"{image}.iso", | |
"volume": vol, | |
"options": [options], | |
} | |
LOGGER.info(f"{get_time()} - creating {vol} on {host}") | |
else: | |
data = {"command": "install", "name": f"{image}.iso", "volume": vol} | |
LOGGER.info(f"{get_time()} - beginning {image} install on {vol}") | |
obj.command("/mgmt/tm/sys/software/image", data) | |
# No iControl async task for image installation, need to emulate | |
while True: | |
if install_image_status(obj, image, vol) is True: | |
break | |
else: | |
sleep(60) | |
return vol | |
def download_ucs(obj, host): | |
LOGGER.info(f"{get_time()} - Starting UCS creation for {host}") | |
ucs_name = f'{host}-{get_time(format_type="file_name")}' | |
data = {"command": "save", "name": ucs_name} | |
ucs_create = obj.task_start("/mgmt/tm/task/sys/ucs", data) | |
obj.task_wait(ucs_create) | |
if obj.task_completed(ucs_create) and obj.task_result(ucs_create) == "": | |
LOGGER.info(f"{get_time()} - UCS created for {host}") | |
obj.download("/mgmt/shared/file-transfer/ucs-downloads/", f"{ucs_name}.ucs") | |
if Path(f"{ucs_name}.ucs").exists(): | |
LOGGER.info(f"{get_time()} - UCS downloaded for {host}") | |
return True | |
else: | |
LOGGER.info(f"{get_time()} - UCS download failed for {host}") | |
return False | |
else: | |
LOGGER.info(f"{get_time()} - UCS creation failed for {host}") | |
return False | |
def upload_tmos(obj, host, image_name, image_iso): | |
# TODO check to see if image is already there before uploading!! | |
# Check to see if the image is alread there | |
if obj.exist(f"/mgmt/tm/sys/software/image/{image_name}.iso"): | |
LOGGER.info( | |
f"{get_time()} - {image_name} already validated as present on {host}" | |
) | |
return True | |
else: | |
# Upload image to the BIG-IP | |
LOGGER.info(f"{get_time()} - Starting {image_name} upload to {host}") | |
obj.upload("/mgmt/cm/autodeploy/software-image-uploads", image_iso) | |
LOGGER.info(f"{get_time()} - {image_name} upload complete to {host}") | |
sleep(10) | |
if obj.exist(f"/mgmt/tm/sys/software/image/{image_name}.iso"): | |
LOGGER.info(f"{get_time()} - {image_name} validated as present on {host}") | |
return True | |
else: | |
LOGGER.info( | |
f"{get_time()} - {image_name} not validated as present on {host}" | |
) | |
return False | |
def verify_config(obj, host): | |
LOGGER.info(f"{get_time()} - Verifying config for {host}") | |
options = {"verify": True} | |
data = {"command": "load", "options": [options]} | |
config_verify = obj.task_start("/mgmt/tm/task/sys/config", data) | |
obj.task_wait(config_verify) | |
if obj.task_completed(config_verify) and obj.task_result(config_verify) == "": | |
LOGGER.info(f"{get_time()} - Config verified for {host}") | |
return True | |
else: | |
LOGGER.info(f"{get_time()} - Config verification failed for {host}") | |
return False | |
def copy_config_and_reboot(obj, active_vol, new_vol, reboot, host): | |
LOGGER.info(f"{get_time()} - Copying config from {active_vol} to {new_vol} on {host} and rebooting") | |
if reboot: | |
data = {"command": "run", "utilCmdArgs": f"-c \"cpcfg --source={active_vol} --reboot {new_vol}\""} | |
else: | |
data = {"command": "run", "utilCmdArgs": f"-c \"cpcfg --source={active_vol} {new_vol}\""} | |
obj.command('/mgmt/tm/util/bash', data) | |
def update_device(device): | |
host, image_iso = device.split(",") | |
image_name = Path(image_iso).stem | |
LOGGER.info(f"{get_time()} - Begin process for {host}") | |
# Instatiate BIG-IP | |
b = instantiate_bigip(host, DEVICE_USER, DEVICE_PASSWORD) | |
# Check if device is standby if -a not specified | |
if DEVICE_ALL is True: | |
ha_status = "ignore" | |
else: | |
ha_status = is_standby(b) | |
if ha_status == "ignore" or ha_status == "standby": | |
# | |
# TODO Check license | |
# | |
LOGGER.info(f"{get_time()} - {host} HA status is {ha_status}, proceeding") | |
# Verify Configuration Loads, Move On if So | |
if verify_config(b, host): | |
# Create and Download UCS | |
if download_ucs(b, host): | |
# Upload TMOS Image | |
if upload_tmos(b, host, image_name, image_iso): | |
LOGGER.info( | |
f"{get_time()} - {image_name} is ready for install on {host}" | |
) | |
# Install image in new slot | |
new_vol = install_image(b, image_name, host) | |
# Update devices but on Standby only | |
if DEVICE_UPDATE: | |
active_vol = get_active_volume(b) | |
if DEVICE_REBOOT is False: | |
boot_status = False | |
LOGGER.info(f"{get_time()} - copying config to {new_vol} on {host} but not rebooting") | |
copy_config_and_reboot(b, active_vol, new_vol, boot_status, host) | |
elif DEVICE_REBOOT is True and ha_status == 'standby': | |
boot_status = True | |
LOGGER.info(f"{get_time()} - copying config to {new_vol} on {host} and rebooting") | |
copy_config_and_reboot(b, active_vol, new_vol, boot_status, host) | |
else: | |
LOGGER.info(f"{get_time()} - device is active, skipping config install/reboot on {host}") | |
else: | |
LOGGER.info(f"{get_time()} - copying config skipped on {host}") | |
else: | |
LOGGER.info( | |
f"{get_time()} - {image_name} is not ready for install on {host}" | |
) | |
else: | |
LOGGER.info(f"{get_time()} - UCS creation/download failed for {host}") | |
else: | |
LOGGER.info(f"{get_time()} - Config verification failed for {host}") | |
else: | |
LOGGER.info( | |
f"{get_time()} - {host} is active. Halting...use the -a flag to install anyway" | |
) | |
def device_mgr(devices): | |
with ThreadPoolExecutor(max_workers=len(devices)) as executor: | |
return executor.map(update_device, devices) | |
if __name__ == "__main__": | |
args = build_parser() | |
DEVICE_ALL = args.all | |
DEVICE_UPDATE = args.update | |
DEVICE_REBOOT = args.reboot | |
DEVICE_USER = args.user | |
DEVICE_PASSWORD = args.password | |
LOGGER = get_logger() | |
with open(args.inventory) as f: | |
bigip_inventory = f.read().splitlines() | |
# Check to see if the BIG-IP is the Standby | |
# Upload TMOS Images | |
LOGGER.info("\n\n\t---Script Start---\n\n") | |
device_mgr(bigip_inventory) | |
LOGGER.info("\n\n\t---Script Complete---\n\n") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment