cancel
Showing results for 
Search instead for 
Did you mean: 
Chad_Jenison
Nimbostratus
Nimbostratus

Problem this snippet solves:

This python script uses ssh/tmsh to access a BIG-IP and iterates through virtual servers looking for unused virtuals so that the virtual and associated configuration objects can be removed/cleaned from a BIG-IP system.

How to use this snippet:

Install onto a system that has python+paramiko

then execute and provide arguments.

./ssh_bigip_cleaner.py usage: ssh_bigip_cleaner.py [-h] (--scan | --remove | --scanandremove) --bigip BIGIP --user USER [--file FILE] [--vipNoDns] [--vipNoDnsVsEnabled] [--vipNoDnsVsDisabled] [--vipNoDnsVsAvailable] [--vipNoDnsVsOffline] [--vs0TotalConns] [--vs0CurConns] [--vsDisabled] [--vsOffline]

Code :

#!/usr/bin/python

## SSH/TMSH BIG-IP Cleaner
## Author: Chad Jenison (c.jenison@f5.com)
## Script that connects to BIG-IP over network (via SSH) and then uses TMSH commands to enumerate virtual servers and then identifies candidate virtual servers for deletion
## Version: 1.1 - Fixed operation on 10.2.x BIG-IPs (due to lack of support for "list ltm virtual one-line" and changes in "show ltm virtual field-fmt" between 10.x and 11.x+

import argparse
import sys
import socket
import getpass
import paramiko
import time


# Taken from http://code.activestate.com/recipes/577058/
def query_yes_no(question, default="no"):
    valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
    if default == None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)
    while 1:
        sys.stdout.write(question + prompt)
        choice = raw_input().lower() 
        if default is not None and choice == '':
            return valid[default]
        elif choice in valid.keys():
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n")

def removeVirtual(virtualName, vip, service, currentConns, totalConns, availabilityState, enabledState, reverseDns):
    #Gather Up Pool and iRules used by the virtual server being removed
    stdin, stdout, stderr = sshSession.exec_command('tmsh list ltm virtual %s' % (virtualName))
    inRules = False
    rules = []
    for line in stdout.read().splitlines():
        if line.lstrip().startswith('pool '):
            pool = line.lstrip().split(' ')[1].rstrip()
        elif inRules:
            if line.lstrip().rstrip() == '}':
                inRules = False
            else:
                if not line.lstrip().rstrip().startswith('_sys'):
                    rules.append(line.lstrip().rstrip())
        elif line.lstrip().startswith('rules {'):
            inRules = True 
    if args.remove:
       print ('--Status Information from CSV File - NOT CURRENT--')
    else:
       print ('--Status Information - CURRENT--')
    print ('Virtual Name: %s' % (virtualName))
    print ('VIP: %s' % (vip))
    print ('Service: %s' % (service))
    print ('Current Conns: %s' % (currentConns))
    print ('Total Conns: %s' % (totalConns))
    print ('Availability State: %s' % (availabilityState))
    print ('Enabled State: %s' % (enabledState))
    print ('Reverse DNS: %s' % (reverseDns))
    print ('Pool: %s' % (pool))
    if rules:
        for rule in rules:
             print ('iRule: %s' % (rule)) 
    queryString = ('Remove Virtual Server: %s?' % (virtualName))
    if query_yes_no(queryString, default="no"):
        stdin, stdout, stderr = sshSession.exec_command('tmsh delete ltm virtual %s' % (virtualName))
        #Check for Errors in deleting configuration objects
        for line in stderr.read().splitlines():
            if ": " in line:
                print ('Virtual Server Delete encountered error: %s' % (line))
        queryString = ('Remove Pool: %s?' % (pool))
        if query_yes_no(queryString, default="no"):
            stdin, stdout, stderr = sshSession.exec_command('tmsh delete ltm pool %s' % (pool))
            for line in stderr.read().splitlines():
                if ": " in line:
                    print ('Pool Delete encountered error: %s' % (line))
        for rule in rules:
            queryString = ('Remove Rule: %s?' % (rule))
            if query_yes_no(queryString, default="no"):
                stdin, stdout, stderr = sshSession.exec_command('tmsh delete ltm rule %s' % (rule))
                for line in stderr.read().splitlines():
                    if ": " in line:
                        print ('Rule %s Delete encountered error: %s' % (rule, line))

parser = argparse.ArgumentParser(description='A tool to identify and remove stale virtual servers from F5 BIG-IP Systems (should work with any BIG-IP that runs 10.0+ and has TMSH)', epilog='Use this tool with caution')
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument('--scan', action='store_true', help='scan for unused virtual servers and output to file')
mode.add_argument('--remove', action='store_true', help='remove unused virtual servers based on input file')
mode.add_argument('--scanandremove', action='store_true', help='scan for unused virtual servers and immediately remove')
parser.add_argument('--bigip', help='IP or hostname of BIG-IP Management or Self IP', required=True)
parser.add_argument('--user', help='username to use for authentication', required=True)
parser.add_argument('--file', help='filename in cwd CSV formatted; file is output filename if scanning; file is input filename if removing', default='ssh_bigip_cleaner.csv')
virtualselector = parser.add_argument_group(title='Criteria for Selecting Stale Virtual Servers')
virtualselector.add_argument('--vipNoDns', action='store_true', help='select virtual server for removal solely based on VIP Reverse DNS Unknown')
virtualselector.add_argument('--vipNoDnsVsEnabled', action='store_true', help='select virtual server for removal based on VIP Reverse DNS Unknown AND Virtual Enabled')
virtualselector.add_argument('--vipNoDnsVsDisabled', action='store_true', help='select virtual server for removal based on VIP Reverse DNS Unknown AND Virtual Disabled')
virtualselector.add_argument('--vipNoDnsVsAvailable', action='store_true', help='select virtual server for removal based on VIP Reverse DNS Unknown AND Virtual has Available Status')
virtualselector.add_argument('--vipNoDnsVsOffline', action='store_true', help='select virtual server for removal based on VIP Reverse DNS Unknown AND Virtual has Offline Status')
virtualselector.add_argument('--vs0TotalConns', action='store_true', help='select virtual server for removal based on Virtual 0 Total Conns')
virtualselector.add_argument('--vs0CurConns', action='store_true', help='select virtual server for removal based on Virtual 0 Current Conns')
virtualselector.add_argument('--vsDisabled', action='store_true', help='select virtual server for removal based on Virtual Server Disabled')
virtualselector.add_argument('--vsOffline', action='store_true', help='select virtual server for removal based on Virtual Server Offline')

args = parser.parse_args()

passwd = getpass.getpass("Password for " + args.user + ":")

sshSession=paramiko.SSHClient()
sshSession.set_missing_host_key_policy(paramiko.AutoAddPolicy())
sshSession.connect(args.bigip, username=args.user, password=passwd, look_for_keys=False, allow_agent=False)

if args.scan or args.scanandremove:
    stdin, stdout, stderr = sshSession.exec_command("tmsh list ltm virtual")
    allvirtuals = []
    for line in stdout.read().splitlines():
        if line.startswith('ltm virtual'):
            virtual = []
            virtual.append(line.split(' ')[2])
            allvirtuals.append(virtual)

    for virtual in allvirtuals:
        stdin, stdout, stderr = sshSession.exec_command("tmsh list ltm virtual %s" % (virtual[0]))
        for line in stdout.read().splitlines():
            if line.lstrip().startswith('destination'):
                virtual.append(line.lstrip().split(' ')[1]) 
                virtual.append(line.lstrip().split(' ')[1].split(':')[0]) 
                virtual.append(line.lstrip().split(' ')[1].split(':')[1].rstrip()) 
        stdin, stdout, stderr = sshSession.exec_command("tmsh show ltm virtual %s field-fmt" % (virtual[0]))
        for line in stdout.read().splitlines():
            if line.lstrip().startswith('clientside.cur-conns'):
        virtual.append(line.lstrip().split(' ')[1].rstrip())
            elif line.lstrip().startswith('clientside.tot-conns'):
        virtual.append(line.lstrip().split(' ')[1].rstrip())
            #11.x+ Format
            elif line.lstrip().startswith('status.availability-state'):
        virtual.append(line.lstrip().split(' ')[1].rstrip())
            #10.x format
            elif line.lstrip().startswith('virtual-server.status.availability-state'):
        virtual.append(line.lstrip().split(' ')[1].rstrip())
            #11.x+ Format
            elif line.lstrip().startswith('status.enabled-state'):
        virtual.append(line.lstrip().split(' ')[1].rstrip())
            #10.x Format
            elif line.lstrip().startswith('virtual-server.status.enabled-state'):
        virtual.append(line.lstrip().split(' ')[1].rstrip())
        try:
            name, alias, addresslist = socket.gethostbyaddr(virtual[2])
            virtual.append(name)
        except socket.error:
            virtual.append('unknown')

    if args.scan:
        fileOut = open('%s' % (args.file), 'w')
    for virtual in allvirtuals:
        if (args.vipNoDns and virtual[8] == 'unknown') or (args.vipNoDnsVsEnabled and virtual[8] == 'unknown' and virtual[7] == 'enabled') or (args.vipNoDnsVsDisabled and virtual[8] == 'unknown' and virtual[7] == 'disabled') or (args.vipNoDnsVsAvailable and virtual[8] == 'unknown' and virtual[6] == 'available') or (args.vipNoDnsVsOffline and virtual[8] == 'unknown' and virtual[6] == 'offline') or (args.vs0TotalConns and virtual[5] == '0') or (args.vs0CurConns and virtual[4] == '0') or (args.vsDisabled and virtual[7] == 'disabled') or (args.vsOffline and virtual[6] == 'offline'):
            if args.scan:
                fileOut.write('VirtualName,%s,VIP,%s,Service,%s,CurrentConns,%s,TotalConns,%s,AvailabilityState,%s,EnabledState,%s,ReverseDNS,%s\n' % (virtual[0], virtual[2], virtual[3], virtual[4], virtual[5], virtual[6], virtual[7], virtual[8]))
            else:
removeVirtual(virtual[0], virtual[2], virtual[3], virtual[4], virtual[5], virtual[6], virtual[7], virtual[8])
    if args.scan:
        fileOut.close()

if args.remove:
   fileIn = open('%s' % (args.file), 'r')
   for line in fileIn:
       removeVirtual(line.split(',')[1], line.split(',')[3], line.split(',')[5], line.split(',')[7], line.split(',')[9], line.split(',')[11], line.split(',')[13], line.split(',')[15].rstrip())

##SUBLIST FIELD NUMBERS BELOW
#            print ('Virtual Name: %s' % (virtual[0]))
#            print ('VIP: %s' % (virtual[2]))
#            print ('Service: %s' % (virtual[3]))
#            print ('Current Conns: %s' % (virtual[4]))
#            print ('Total Conns: %s' % (virtual[5]))
#            print ('Availability State: %s' % (virtual[6]))
#            print ('Enabled State: %s' % (virtual[7]))
#     print ('Reverse DNS: %s' % (virtual[8]))

Tested this on version:

11.5
Comments
Andy_McGrath
Cumulonimbus
Cumulonimbus

Like this, looks very good (sure will link to for an answer to two in the future), just wondering if it could be expand to uses iControl or iControl REST API instead of SSH and TMSH?

 

Python F5 iControl REST library

 

Chad_Jenison
Nimbostratus
Nimbostratus

The version above was actually a second revision (rewrite) of an original that used iControl REST. For the customer in question, they had software that pre-dated REST, so I rewrote it to use SSH/TMSH via paramiko at their request.

 

Just uploaded a version (slightly different capabilities) that uses iControl REST at: https://tstdmzdevcentral.olympus.f5net.com/codeshare/big-ip-config-cleaner-icontrol-rest-1122

 

Version history
Last update:
‎31-Jan-2018 10:27
Updated by:
Contributors