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.1Published Aug 13, 2019
Version 1.0G-Rob
Employee
F5 Solutions Engineer focused on cloud, automation, modern apps and excellent customer-focused service.G-Rob
Employee
F5 Solutions Engineer focused on cloud, automation, modern apps and excellent customer-focused service.No CommentsBe the first to comment
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)