For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

Python script that clusters two BIG-IPs

Problem this snippet solves:

This script automates the clustering of two BIG-IPs and performs some pre-clustering validation (NTP, CM device configuration, matching TMOS)

How to use this snippet:

Configure your BIG-IPs hostnames, CM device characteristics, NTP, etc and execute the script.

Code :

#!/usr/bin/env python3

# Import the proper python modules we'll need
import datetime
import time
import requests
import sys
import json

from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

#
# Declare functions
#

def abort_script(reason):
    print('*** Aborting script execution! ***')
    if len(str(reason)) > 0:
        print('ERROR: ' + str(reason))
    sys.exit(2)

def icontrol_get(host,username,password,path):
    apiCall = requests.session()
    apiCall.headers.update({'Content-type':'application/json'})
    apiCall.auth = (username,password)
    apiUri = 'https://' + host + '/mgmt/tm' + path
    try:
        apiResponse = apiCall.get(apiUri,verify=False)
    except requests.exceptions.RequestException as e:
        abort_script(str(e))
    return(apiResponse.text)

def icontrol_post(host,username,password,path,api_payload):
    apiCall = requests.session()
    apiCall.headers.update({'Content-type':'application/json'})
    apiCall.auth = (username,password)
    apiUri = 'https://' + host + '/mgmt/tm' + path
    try:
        apiResponse = apiCall.post(apiUri,verify=False,data=json.dumps(api_payload))
    except requests.exceptions.RequestException as e:
        abort_script(str(e))
    return(apiResponse.text)

def icontrol_put(host,username,password,path,api_payload):
    apiCall = requests.session()
    apiCall.headers.update({'Content-type':'application/json'})
    apiCall.auth = (username,password)
    apiUri = 'https://' + host + '/mgmt/tm' + path
    try:
        apiResponse = apiCall.put(apiUri,verify=False,data=json.dumps(api_payload))
    except requests.exceptions.RequestException as e:
        abort_script(str(e))
    return(apiResponse.text)

def icontrol_patch(host,username,password,path,api_payload):
    apiCall = requests.session()
    apiCall.headers.update({'Content-type':'application/json'})
    apiCall.auth = (username,password)
    apiUri = 'https://' + host + '/mgmt/tm' + path
    try:
        apiResponse = apiCall.patch(apiUri,verify=False,data=json.dumps(api_payload))
    except requests.exceptions.RequestException as e:
        abort_script(str(e))
    return(apiResponse.text)

def icontrol_delete(host,username,password,path,object):
    apiCall = requests.session()
    apiCall.headers.update({'Content-type':'application/json'})
    apiCall.auth = (username,password)
    apiUri = 'https://' + host + '/mgmt/tm' + path + object
    try:
        apiResponse = apiCall.delete(apiUri,verify=False)
    except requests.exceptions.RequestException as e:
        abort_script(str(e))
    return(apiResponse.text)

def icontrol_test_connection(host,username,password):
    apiCall = requests.session()
    apiCall.headers.update({'Content-type':'application/json'})
    apiCall.auth = (username,password)
    apiUri = 'https://' + host + '/mgmt/tm/sys/clock'
    try:
        apiResponse = apiCall.get(apiUri,verify=False)
    except requests.exceptions.RequestException as e:
        abort_script(str(e))
    if '"kind"' in apiResponse.text:
        testresult = True
    else:
        testresult = False
    return(testresult)

def icontrol_save_config(host,username,password):
    apiCall = requests.session()
    apiCall.headers.update({'Content-type':'application/json'})
    apiCall.auth = (username,password)
    apiUri = 'https://' + host + '/mgmt/tm/sys/config'
    try:
        apiResponse = apiCall.post(apiUri,verify=False,data=json.dumps({'command':'save'}))
    except requests.exceptions.RequestException as e:
        abort_script(str(e))
    return(apiResponse.text)

#
# We will save variables in the primary and standby variables, which we'll define now
#

primary = {}
standby = {}

###############################
# Pre-clustering Verification
###############################

#
# Ask for the cluster member mgmt IP addresses, usernames and passwords
#

primary['mgmt_ip'] = input('Enter the management IP address of the primary node: ')
primary['username'] = input('Enter the username for the primary node: ')
primary['password'] = input('Password: ')
standby['mgmt_ip'] = input('Enter the management IP address of the secondary node: ')
standby['username'] = input('Enter the username for the secondary node: ')
standby['password'] = input('Password: ')
print('Enter the desired device group name on the BIG-IPs')
cluster_group_name = input('[If you\'re unsure, enter failover-bigip]: ')

#
# Test iControl REST connectivity
#

print('Testing API connectivity to the primary node')
primary_test = icontrol_test_connection(primary['mgmt_ip'],primary['username'],primary['password'])
if primary_test == True:
    print('  Successfully connected to the primary node')
else:
    abort_script('Could not connect to the primary node during API check')

print('Testing API connectivity to the secondary node')
secondary_test = icontrol_test_connection(standby['mgmt_ip'],standby['username'],standby['password'])
if secondary_test == True:
    print('  Successfully connected to the secondary node')
else:
    abort_script('Could not connect to the secondary node during API check')

#
# Gather details about each node
#

apiResponse = icontrol_get(primary['mgmt_ip'],primary['username'],primary['password'],'/sys/global-settings/')
primary['hostname'] = json.loads(apiResponse)['hostname']

apiResponse = icontrol_get(standby['mgmt_ip'],standby['username'],standby['password'],'/sys/global-settings/')
standby['hostname'] = json.loads(apiResponse)['hostname']

apiResponse = icontrol_get(primary['mgmt_ip'],primary['username'],primary['password'],'/cm/device/')
primary['cm_devices'] = json.loads(apiResponse)['items']
for current_cm_device in primary['cm_devices']:
    if current_cm_device['selfDevice'] == 'true':
        primary['cm_properties'] = current_cm_device
        primary['cm_name'] = current_cm_device['name']
        primary['sw_version'] = current_cm_device['version']
        primary['sw_build'] = current_cm_device['build']

apiResponse = icontrol_get(standby['mgmt_ip'],standby['username'],standby['password'],'/cm/device/')
standby['cm_devices'] = json.loads(apiResponse)['items']
for current_cm_device in standby['cm_devices']:
    if current_cm_device['selfDevice'] == 'true':
        standby['cm_properties'] = current_cm_device
        standby['cm_name'] = current_cm_device['name']
        standby['sw_version'] = current_cm_device['version']
        standby['sw_build'] = current_cm_device['build']

#
# Verify that the TMOS versions match between nodes
#

if not (primary['sw_version'] == standby['sw_version']) and (primary['sw_build'] == standby['sw_build']):
    abort_script('Software mismatch on cluster members!')

#
# Verify that the CM device has been configured from default
#

if primary['cm_name'] == 'bigip1' or standby['cm_name'] == 'bigip1':
    abort_script('A cluster member was found to have the default CM device name. Please run the configuration script.')

#
# Verify NTP is configured
#

print('Checking for NTP server configuration on primary')
apiResponse = icontrol_get(primary['mgmt_ip'],primary['username'],primary['password'],'/sys/ntp')
if 'servers' in apiResponse:
    primary['ntp_servers'] = json.loads(apiResponse)['servers']
    primary['timezone'] = json.loads(apiResponse)['timezone']
    print('  Success!')
else:
    abort_script('NTP not configured on cluster member!')

print('Checking for NTP server configuration on secondary')
apiResponse = icontrol_get(standby['mgmt_ip'],standby['username'],standby['password'],'/sys/ntp')
if 'servers' in apiResponse:
    standby['ntp_servers'] = json.loads(apiResponse)['servers']
    standby['timezone'] = json.loads(apiResponse)['timezone']
    print('  Success!')
else:
    abort_script('NTP not configured on cluster member!')

#
# Verify host clocks are in sync
#
print('Verifying device clock sync')
apiResponse = icontrol_get(primary['mgmt_ip'],primary['username'],primary['password'],'/sys/clock')
primary['clock_timestamp']=json.loads(apiResponse)['entries']['https://localhost/mgmt/tm/sys/clock/0']['nestedStats']['entries']['fullDate']['description'][:16]

apiResponse = icontrol_get(standby['mgmt_ip'],standby['username'],standby['password'],'/sys/clock')
standby['clock_timestamp'] = json.loads(apiResponse)['entries']['https://localhost/mgmt/tm/sys/clock/0']['nestedStats']['entries']['fullDate']['description'][:16]

if primary['clock_timestamp'] != standby['clock_timestamp']:
    abort_script('Device clocks are out of sync!')
else:
    print('  Success!')

#
# Show details about each node and ask user for final verification to continue
#
print('\nPrimary details')
print(' - Mgmt IP: ' + primary['mgmt_ip'])
print(' - Hostname: ' + primary['hostname'])
print(' - Cluster Device Name: ' + primary['cm_name'])
print(' - Version ' + primary['sw_version'] + ', build ' + primary['sw_build'])
print(' - Time zone: ' + primary['timezone'])
print(' - NTP servers: ' + str(primary['ntp_servers']))
print('\nSecondary details')
print(' - Mgmt IP: ' + standby['mgmt_ip'])
print(' - Hostname: ' + standby['hostname'])
print(' - Cluster Device Name: ' + standby['cm_name'])
print(' - Version ' + standby['sw_version'] + ', build ' + standby['sw_build'])
print(' - Time zone: ' + standby['timezone'])
print(' - NTP servers: ' + str(standby['ntp_servers']))
print('\nThis is the last chance to abort before issuing the clustering commands!\n')
confirmation = input('Enter YES to continue ')
if not confirmation == 'YES':
    abort_script('User cancelled at final confirmation!')

#######################
# Clustering Commands
#######################

#
# Save the config on each device
#

print('Saving the device configuration on the primary')
save_result = icontrol_save_config(primary['mgmt_ip'],primary['username'],primary['password'])
if '"kind"' in save_result:
    print("  Configuration saved successfully.")
else:
    abort_script("Configuration could not be successfully saved! " + save_result)

print('Saving the device configuration on the secondary')
save_result = icontrol_save_config(standby['mgmt_ip'],standby['username'],standby['password'])
if '"kind"' in save_result:
    print("  Configuration saved successfully.")
else:
    abort_script("Configuration could not be successfully saved! " + save_result)


#
# Save a pre-clustering UCS on each box for rollback
#

ucs_filename = 'pre-cluster-script-{:%Y-%m-%d-%H%M%S}.ucs'.format(datetime.datetime.utcnow())
print('Saving UCS to /var/local/ucs/' + ucs_filename + ' on primary device')
ucs_create_result = icontrol_post(primary['mgmt_ip'],primary['username'],primary['password'],'/sys/ucs',{'command':'save','name':ucs_filename})

print('Saving UCS to /var/local/ucs/' + ucs_filename + ' on secondary device')
ucs_create_result = icontrol_post(standby['mgmt_ip'],standby['username'],standby['password'],'/sys/ucs',{'command':'save','name':ucs_filename})

#
# Add the secondary to the primary as a peer
#

print('Adding the nodes to the trust domain')
apiResponse = icontrol_post(primary['mgmt_ip'],primary['username'],primary['password'],'/cm/add-to-trust',{
    'command':'run',
    'name':'Root',
    'caDevice':True,
    'device':standby['mgmt_ip'],
    'deviceName':standby['cm_name'],
    'username':standby['username'],
    'password':standby['password']
})
if '"kind":"tm:cm:add-to-trust:runstate"' in apiResponse:
    print('  Command issued successfully but clustering must be verified!')
else:
    abort_script('Error! ' + apiResponse)

#
# Verify status of members
#

print('Verifying trust sync status on the primary')
apiResponse = icontrol_get(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/sync-status')
if '"mode":{"description":"trust-only"}' in apiResponse:
    print('  Devices are in sync!')
else:
    print('Sleeping 60 seconds to allow devices to discover and sync')
    time.sleep(60)
    apiResponse = icontrol_get(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/sync-status')
    if '"mode":{"description":"trust-only"}' in apiResponse:
        print('  Devices are in sync!')
    else:
        abort_script('Error! Devices not syncing! ' + apiResponse)

print('Verifying trust sync status on the secondary')
apiResponse = icontrol_get(standby['mgmt_ip'], standby['username'], standby['password'], '/cm/sync-status')
if '"mode":{"description":"trust-only"}' in apiResponse:
    print('  Devices are in sync!')
else:
    print('Sleeping 60 seconds to allow devices to discover and sync')
    time.sleep(60)
    apiResponse = icontrol_get(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/sync-status')
    if '"mode":{"description":"trust-only"}' in apiResponse:
        print('  Devices are in sync!')
    else:
        abort_script('Error! Devices not syncing!' + apiResponse)

#
# Create the device group
#

print('Creating the ' + cluster_group_name + ' device group')
apiResponse = icontrol_post(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/device-group',{
    'name':cluster_group_name,
    'type':'sync-failover'
})
if '"kind":"tm:cm:device-group:device-groupstate"' in apiResponse:
    print('  Success!')
elif '"01020066:3:' in apiResponse:
    print('  WARNING! Device group already created! Ignoring and continuing script execution!')
else:
    abort_script('Error! ' + apiResponse)

#
# Add both devices to the device group
#

print('Adding the primary node to the device group')
apiResponse = icontrol_post(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/device-group/' + cluster_group_name + '/devices',{
    'name':primary['cm_name']
})
if '"kind":"tm:cm:device-group:devices:devicesstate"' in apiResponse:
    print('  Success!')
elif '"code":409,"message":"01020037:3:' in apiResponse:
    print('  WARNING! Device is already a member of the device group! Ignoring and continuing script execution!')
else:
    abort_script('Error! ' + apiResponse)

print('Adding the secondary node to the device group')
apiResponse = icontrol_post(standby['mgmt_ip'], standby['username'], standby['password'], '/cm/device-group/' + cluster_group_name + '/devices',{
    'name':standby['cm_name']
})
if '"kind":"tm:cm:device-group:devices:devicesstate"' in apiResponse:
    print('  Success!')
elif '"code":409,"message":"01020037:3:' in apiResponse:
    print('  WARNING! Device is already a member of the device group! Ignoring and continuing script execution!')
else:
    abort_script('Error! ' + apiResponse)

#
# Enable auto-sync on both members
#

print('Enabling auto-sync on the primary member')
apiResponse = icontrol_patch(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/device-group/' + cluster_group_name,{
    'autoSync':'enabled'
})
if '"kind":"tm:cm:device-group:device-groupstate"' and '"autoSync":"enabled"' in apiResponse:
    print('  Success!')
else:
    abort_script('Error! ' + apiResponse)

print('Enabling auto-sync on the secondary member')
apiResponse = icontrol_patch(standby['mgmt_ip'], standby['username'], standby['password'], '/cm/device-group/' + cluster_group_name,{
    'autoSync':'enabled'
})
if '"kind":"tm:cm:device-group:device-groupstate"' and '"autoSync":"enabled"' in apiResponse:
    print('  Success!')
else:
    abort_script('Error! ' + apiResponse)

#
# Save the config on each device
#

print('Saving the device configuration on the primary')
save_result = icontrol_save_config(primary['mgmt_ip'],primary['username'],primary['password'])
if '"kind"' in save_result:
    print("  Configuration saved successfully.")
else:
    abort_script("Configuration could not be successfully saved! " + save_result)

print('Saving the device configuration on the secondary')
save_result = icontrol_save_config(standby['mgmt_ip'],standby['username'],standby['password'])
if '"kind"' in save_result:
    print("  Configuration saved successfully.")
else:
    abort_script("Configuration could not be successfully saved! " + save_result)


###############################
# Post-clustering steps
###############################

#
# Force the initial sync
#

print('Forcing configuration sync between peers')
apiResponse = icontrol_post(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/config-sync',{
    'command':'run',
    'utilCmdArgs':'to-group ' + cluster_group_name
})
if '"kind":"tm:cm:config-sync:runstate"' in apiResponse:
    print('  Command issued successfully but sync status must be verified!')
else:
    abort_script('Error! ' + apiResponse)

#
# Verify sync status of members
#

print('Verifying trust sync status on the primary')
apiResponse = icontrol_get(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/sync-status')
if '"description":"failover-bigip (In Sync): All devices in the device group are in sync"':
    print('  Success!')
else:
    print('Sleeping 30 seconds to allow devices to fully sync')
    time.sleep(30)
    if '"description":"failover-bigip (In Sync): All devices in the device group are in sync"':
        print('  Devices are in sync!')
    else:
        abort_script('Error! Devices not syncing!')

print('Verifying trust sync status on the primary')
apiResponse = icontrol_get(primary['mgmt_ip'], primary['username'], primary['password'], '/cm/sync-status')
if '"description":"failover-bigip (In Sync): All devices in the device group are in sync"':
    print('  Success!')
else:
    print('Sleeping 30 seconds to allow devices to fully sync')
    time.sleep(30)
    if '"description":"failover-bigip (In Sync): All devices in the device group are in sync"':
        print('  Devices are in sync!')
    else:
        abort_script('Error! Devices not syncing!')

#
# Force the primary to active state, if necessary
#

print('Foricng the primary node to active status')
apiResponse = icontrol_post(standby['mgmt_ip'], standby['username'], standby['password'], '/sys/failover',{
    'command':'run',
    'standby':True,
    'traffic-group':'traffic-group-1'
})
if '"kind":"tm:sys:failover:runstate"' in apiResponse:
    print('  Success!')
else:
    abort_script('Error! ' + apiResponse)

################
# End of Script
################

print('\nEnd of execution. Script has completed.')
SystemExit()

Tested this on version:

12.1
Published Aug 13, 2019
Version 1.0
No CommentsBe the first to comment