Friday, November 19, 2010

Python Texas Hold’em Simulator

Update

I finally got around to looking at this again, and I decided to re-write the program. I also posted the new version to GitHub. You can find it at https://github.com/jfilliben/poker-sim. Feel free to disregard the below info, except perhaps to learn a lot of things you shouldn't do with Python ;)


Original

In addition to my practical Python script for Cisco switch configuration verification, I wrote a purely impractical Texas Hold’em Simulator.  I enjoy the game, and it occurred to me that a poker simulator would be a fun way to explore Monte Carlo simulations.  The following program takes as an input a count of simulations to perform and two player hands, plus five community cards.  For any community card specified with “Xx”, it will randomly select a card and play out the hand.  At it’s simplest form, you can run it with one iteration and a fully-specified set of cards, and it will tell you which hand wins:
C:>python.exe pokersim.py 1 Ac7d 8sKd 2s3s4s4c4d
Total Hands: 1
Hand1: 1 Hand2: 0 Ties: 0
Hand1: 100.0% Hand2: 0.0% Ties: 0.0%

Or you can leave a couple cards ‘blank’ and see what random turn and river cards will provide:
C:>python.exe pokersim.py 1000 Ac7d 8sKd 2s3sXxXxXx
Total Hands: 1000
Hand1: 612 Hand2: 385 Ties: 3
Hand1: 61.2% Hand2: 38.5% Ties: 0.3%
(note, this program uses a Monte Carlo simulation, so there is a measure of randomness to the results.  If you run the same test multiple times, you will almost always get slightly different results.  And of course, the more iterations you choose to do, the more accurate the results)
Here is the script.  Again, any comments would be appreciated.  If there is a more efficient way to accomplish portions of this program, I’d love to hear of them.  And if you spot any errors, please let me know.  As a disclaimer, there is no guarantee that the results of this script are accurate.  I don’t use this for profit in any way, it was purely a thought exercise for me.
Run it with no options (or “——help”) to get a brief description of the options.
----------
pokersim.py:
#
# pokersim.py - Runs a Monte Carlo simulation of two Texas Hold'em hands
#               with user-specified (or random) community cards
#
# Work to be done:
# Add exhaustive search?
# Compare speed of copy.deepcopy and hand_copy()
# Add input checking for user input
#
import sys
import random
def hand_copy(cards):
#
# Replace copy.deepcopy with specific copy function to speed things up
# (presumably, but not tested)
#
    results = []
    for i, v in enumerate(cards):
        results.append(cards[i])
    return results
def legal_hand(cards):
#
# Returns 1 if hand is legal
# Returns 0 if hand is illegal (two of same card)
#
    for i, v in enumerate(cards):
        if cards.count(v) > 1: return 0
        elif cards.count([-1, -1]): return 0
    return 1
def valid_card(card):
#
# Returns 1 if card is a valid card in text format (rank in (A-2),
#  suit in (c, d, h, s) or wildcard (Xx)
# Returns 0 if card is invalid
#
    if card[0] in ("X", "x", "A", "a", "K", "k", "Q", "q", "J", "j"\
    , "T", "t", "9", "8", "7", "6", "5", "4", "3", "2"):
        if card[1] in ("x", "X", "c", "C", "d", "D", "h", "H", "s", "S"):
            return 1
    else: return 0
def readable_hand(cards):
#
# Returns a readable version of a set of cards
#
    string = ""
    for i, v in enumerate(cards):
        if v[0] == 0: string += "2"
        elif v[0] == 1: string += "3"
        elif v[0] == 2: string += "4"
        elif v[0] == 3: string += "5"
        elif v[0] == 4: string += "6"
        elif v[0] == 5: string += "7"
        elif v[0] == 6: string += "8"
        elif v[0] == 7: string += "9"
        elif v[0] == 8: string += "T"
        elif v[0] == 9: string += "J"
        elif v[0] == 10: string += "Q"
        elif v[0] == 11: string += "K"
        elif v[0] == 12: string += "A"
        elif v[0] == -1: string += "X"
        if v[1] == 0: string += "c"
        elif v[1] == 1: string += "d"
        elif v[1] == 2: string += "h"
        elif v[1] == 3: string += "s"
        elif v[1] == -1: string += "x"
    return string
def hand_to_numeric(cards):
#
# Converts alphanumeric hand to numeric values for easier comparisons
# Also sorts cards based on rank
#
    result = []
    for i, v in enumerate(cards):
        currentcard = [0, 0]
        if cards[i][0] == "2": currentcard[0] = 0
        elif cards[i][0] == "3": currentcard[0] = 1
        elif cards[i][0] == "4": currentcard[0] = 2
        elif cards[i][0] == "5": currentcard[0] = 3
        elif cards[i][0] == "6": currentcard[0] = 4
        elif cards[i][0] == "7": currentcard[0] = 5
        elif cards[i][0] == "8": currentcard[0] = 6
        elif cards[i][0] == "9": currentcard[0] = 7
        elif cards[i][0] in ("t","T"): currentcard[0] = 8
        elif cards[i][0] in ("j","J"): currentcard[0] = 9
        elif cards[i][0] in ("q","Q"): currentcard[0] = 10
        elif cards[i][0] in ("k","K"): currentcard[0] = 11
        elif cards[i][0] in ("a","A"): currentcard[0] = 12
        elif cards[i][0] in ("x","X"): currentcard[0] = -1
        if cards[i][1] in ("c","C"): currentcard[1] = 0
        elif cards[i][1] in ("d","D"): currentcard[1] = 1
        elif cards[i][1] in ("h","H"): currentcard[1] = 2
        elif cards[i][1] in ("s","S"): currentcard[1] = 3
        elif cards[i][1] in ("x","X"): currentcard[1] = -1
        result.append(currentcard)
    result.sort()
    result.reverse()
    return result
def check_flush(hand):
# Return 0 if not true
# Return 1 if true
#
# Initialization
#
    hand_suit = []
    hand_suit.append(hand[0][1])
    hand_suit.append(hand[1][1])
    hand_suit.append(hand[2][1])
    hand_suit.append(hand[3][1])
    hand_suit.append(hand[4][1])
    for i in range(0, 4):
        if hand_suit.count(i) == 5: return 1
    return 0
def check_straight(hand):
# Return 0 if not true
# Return 1 if true
    if hand[0][0] == (hand[1][0] + 1) == (hand[2][0] + 2) == (hand[3][0] + 3)\
    == (hand[4][0] + 4): return 1
    elif (hand[0][0] == 12) and (hand[1][0] == 3) and (hand[2][0] == 2)\
    and (hand[3][0] == 1) and (hand[4][0] == 0): return 1
    return 0
def check_straightflush(hand):
# Return 0 if not true
# Return 1 if true
    if check_flush(hand) and check_straight(hand): return 1
    return 0
def check_fourofakind(hand):
# Return 0 if not true
# Return 1 if true
# Also returns rank of four of a kind card and rank of fifth card
# (garbage value if no four of a kind)
    hand_rank = []
    hand_rank.append(hand[0][0])
    hand_rank.append(hand[1][0])
    hand_rank.append(hand[2][0])
    hand_rank.append(hand[3][0])
    hand_rank.append(hand[4][0])
    for value in range (0, 13):
        if hand_rank.count(value) == 4:
            for n in range (0, 13):
                if hand_rank.count(n) == 1: return 1, value, n
    return 0, 13, 13
def check_fullhouse(hand):
# Return 0 if not true
# Return 1 if true
# Also returns rank of three of a kind card and two of a kind card
# (garbage values if no full house)
    hand_rank = []
    hand_rank.append(hand[0][0])
    hand_rank.append(hand[1][0])
    hand_rank.append(hand[2][0])
    hand_rank.append(hand[3][0])
    hand_rank.append(hand[4][0])
    for value in range(0, 13):
        if hand_rank.count(value) == 3:
            for n in range(0, 13):
                if hand_rank.count(n) == 2: return 1, value, n
    return 0, 13, 13
def check_threeofakind(hand):
# Return 0 if not true
# Return 1 if true
# Also returns rank of three of a kind card and remaining two cards
# (garbage values if no three of a kind)
    hand_rank = []
    hand_rank.append(hand[0][0])
    hand_rank.append(hand[1][0])
    hand_rank.append(hand[2][0])
    hand_rank.append(hand[3][0])
    hand_rank.append(hand[4][0])
    for value in range(0, 13):
        if hand_rank.count(value) == 3:
            for n in range(0, 13):
                if hand_rank.count(n) == 1:
                    for m in range(n+1, 13):
                        if hand_rank.count(m) == 1: return 1, value, [m, n]
    return 0, 13, [13, 13]
def check_twopair(hand):
# Return 0 if not true
# Return 1 if true
# Also returns ranks of paired cards and remaining card
# (garbage values if no two pair)
    value = 0
    hand_rank = []
    hand_rank.append(hand[0][0])
    hand_rank.append(hand[1][0])
    hand_rank.append(hand[2][0])
    hand_rank.append(hand[3][0])
    hand_rank.append(hand[4][0])
    for value in range(0, 13):
        if hand_rank.count(value) == 2:
            for n in range(value+1, 13):
                if hand_rank.count(n) == 2:
                    for m in range(0, 13):
                        if hand_rank.count(m) == 1: return 1, [n, value], m
    return 0, [13, 13], 13
def check_onepair(hand):
# Return 0 if not true
# Return 1 if true
# Also returns ranks of paired cards and remaining three cards
# (garbage values if no pair)
    hand_rank = []
    hand_rank.append(hand[0][0])
    hand_rank.append(hand[1][0])
    hand_rank.append(hand[2][0])
    hand_rank.append(hand[3][0])
    hand_rank.append(hand[4][0])
    for value in range(0, 13):
        if hand_rank.count(value) == 2:
            for n in range (0, 13):
                if hand_rank.count(n) == 1:
                    for m in range(n+1, 13):
                        if hand_rank.count(m) == 1:
                            for o in range (m+1, 13):
                                if hand_rank.count(o) == 1: return 1, value, [o, m, n]
    return 0, 13, [13, 13, 13]
def highest_card(hand1, hand2):
# Return 0 if hand1 is higher
# Return 1 if hand2 is higher
# Return 2 if equal
#
# Initialization
#
    hand1_rank = []
    hand1_rank.append(hand1[0][0])
    hand1_rank.append(hand1[1][0])
    hand1_rank.append(hand1[2][0])
    hand1_rank.append(hand1[3][0])
    hand1_rank.append(hand1[4][0])
    hand2_rank = []
    hand2_rank.append(hand2[0][0])
    hand2_rank.append(hand2[1][0])
    hand2_rank.append(hand2[2][0])
    hand2_rank.append(hand2[3][0])
    hand2_rank.append(hand2[4][0])
#
# Compare
#
    if hand1_rank > hand2_rank: return 0
    elif hand1_rank < hand2_rank: return 1
    return 2
def highest_card_straight(hand1, hand2):
# Return 0 if hand1 is higher
# Return 1 if hand2 is higher
# Return 2 if equal
#
# Compare second card first (to account for Ace low straights)
# if equal, we could have Ace low straight, so compare first card. 
# If first card is Ace, that is the lower straight
#
    if hand1[1][0] > hand2[1][0]: return 0
    elif hand1[1][0] < hand2[1][0]: return 1
    elif hand1[0][0] > hand2[0][0]: return 1
    elif hand1[0][0] < hand2[0][0]: return 0
    return 2
def compare_hands(hand1, hand2):
#
# Compare two hands
# Return 0 if hand1 is better
# Return 1 if hand2 is better
# Return 2 if equal
#
#
# Initialization
#
    result1 = []
    result2 = []
#
# Check for straight flush
#
    if check_straightflush(hand1):
        if check_straightflush(hand2):
            return(highest_card_straight(hand1, hand2))
        else: return 0
    elif check_straightflush(hand2): return 1
#
# Check for four of a kind
#
    result1 = check_fourofakind(hand1)
    result2 = check_fourofakind(hand2)
    if result1[0] == 1:
        if result2[0] == 1:
            if result1[1] > result2[1]: return 0
            elif result1[1] < result2[1]: return 1
            elif result1[2] > result2[2]: return 0
            elif result1[2] < result2[2]: return 1
            else: return 2
        else: return 0
    elif result2[0] == 1: return 1
#
# Check for full house
#
    result1 = check_fullhouse(hand1)
    result2 = check_fullhouse(hand2)
    if result1[0] == 1:
        if result2[0] == 1:
            if result1[1] > result2[1]: return 0
            elif result1[1] < result2[1]: return 1
            elif result1[2] > result2[2]: return 0
            elif result1[2] < result2[2]: return 1
            else: return 2
        else: return 0
    elif result2[0] == 1: return 1
#
# Check for flush
#
    if check_flush(hand1):
        if check_flush(hand2):
            return(highest_card(hand1, hand2))
        else: return 0
    elif check_flush(hand2): return 1
#
# Check for straight
#
    if check_straight(hand1):
        if check_straight(hand2):
            temp = highest_card_straight(hand1, hand2)
            return temp
        else: return 0
    elif check_straight(hand2): return 1
#
# Check for three of a kind
#
    result1 = check_threeofakind(hand1)
    result2 = check_threeofakind(hand2)
    if result1[0] == 1:
        if result2[0] == 1:
            if result1[1] > result2[1]: return 0
            elif result1[1] < result2[1]: return 1
            elif result1[2] > result2[2]: return 0
            elif result1[2] < result2[2]: return 1
            else: return 2
        else: return 0
    elif result2[0] == 1: return 1
#
# Check for two pair
#
    result1 = check_twopair(hand1)
    result2 = check_twopair(hand2)
    if result1[0] == 1:
        if result2[0] == 1:
            if result1[1] > result2[1]: return 0
            elif result1[1] < result2[1]: return 1
            elif result1[2] > result2[2]: return 0
            elif result1[2] < result2[2]: return 1
            else: return 2
        else: return 0
    elif result2[0] == 1: return 1
#
# Check for one pair
#
    result1 = check_onepair(hand1)
    result2 = check_onepair(hand2)
    if result1[0] == 1:
        if result2[0] == 1:
            if result1[1] > result2[1]: return 0
            elif result1[1] < result2[1]: return 1
            elif result1[2] > result2[2]: return 0
            elif result1[2] < result2[2]: return 1
            else: return 2
        else: return 0
    elif result2[0] == 1: return 1
    return (highest_card(hand1, hand2))
def bestfive(hand, community):
#
# Takes hand and community cards in numeric form and returns best five cards
#
    currentbest = hand_copy(community)
    currentbest.sort()
    currentbest.reverse()
    m = 0
#
# Compare current best to five cards including only one player card
#
    for m in range (0, 2):
        for n in range (0, 5):
            comparehand = hand_copy(community)
            comparehand[n] = hand[m]
            comparehand.sort()
            comparehand.reverse()
            if compare_hands(currentbest, comparehand) == 1:
                currentbest = hand_copy(comparehand)
#
# Compare current best to five cards including both player cards
#
    for m in range (0, 5):
        for n in range (m+1, 5):
            comparehand = hand_copy(community)
            comparehand[m] = hand[0]
            comparehand[n] = hand[1]
            comparehand.sort()
            comparehand.reverse()
            if compare_hands(currentbest, comparehand) == 1:
                currentbest = hand_copy(comparehand)
    return currentbest
#
# Main Program Body
#
#
# Initialization
#
hand1 = []
handnum1 = []
best_hand1 = []
hand2 = []
handnum2 = []
best_hand2 = []
community = []
communitytemp = []
totals = [0,0,0]
iterations = 0
#
# Process command-line arguments
#
if (len(sys.argv) == 1) or (sys.argv[1] in ("-h", "--help")):
        sys.exit("\n\
First input is number of iterations to run the Monte Carlo simulation\n\
Input cards in format [RANK][SUIT], as in Ace Clubs + Four Diamonds = Ac4d)\n\
Input should be two cards for player 1, two cards for player 2 and five community cards\n\
Wildcards should be written as Xx (capital X for rank, lower-case x for suit)\n\
Wildcards should be placed at the end of the community hand\n\n\
--help: This message\n")
else:
    iterations = int(sys.argv[1])
    if iterations < 1: iterations = 1
    if valid_card(sys.argv[2][0:2]): hand1.append(sys.argv[2][0:2])
    else: sys.exit("Player 1 Card 1 Invalid")
    if valid_card(sys.argv[2][2:4]): hand1.append(sys.argv[2][2:4])
    else: sys.exit("Player 1 Card 2 Invalid")
    if valid_card(sys.argv[3][0:2]): hand2.append(sys.argv[3][0:2])
    else: sys.exit("Player 2 Card 1 Invalid")
    if valid_card(sys.argv[3][2:4]): hand2.append(sys.argv[3][2:4])
    else: sys.exit("Player 2 Card 2 Invalid")
    if valid_card(sys.argv[4][0:2]): community.append(sys.argv[4][0:2])
    else: sys.exit("Community Card 1 Invalid")
    if valid_card(sys.argv[4][2:4]): community.append(sys.argv[4][2:4])
    else: sys.exit("Community Card 2 Invalid")
    if valid_card(sys.argv[4][4:6]): community.append(sys.argv[4][4:6])
    else: sys.exit("Community Card 3 Invalid")
    if valid_card(sys.argv[4][6:8]): community.append(sys.argv[4][6:8])
    else: sys.exit("Community Card 4 Invalid")
    if valid_card(sys.argv[4][8:10]): community.append(sys.argv[4][8:10])
    else: sys.exit("Community Card 5 Invalid")
handnum1 = hand_to_numeric(hand1)
handnum2 = hand_to_numeric(hand2)
#
#
# Monte Carlo Simulation
#
#
for n in range (0, iterations):
    communitytemp = hand_to_numeric(community)
    while not legal_hand(handnum1 + handnum2 + communitytemp):
        for i, v in enumerate(community):
            if community[i][0] in ("X", "x"):
                communitytemp[i] = [random.randrange(0,13), random.randrange(0,4)]
    best_hand1 = bestfive(handnum1, communitytemp)
    best_hand2 = bestfive(handnum2, communitytemp)
    totals[compare_hands(best_hand1, best_hand2)] += 1
print "\nTotal Hands: " + str(totals[0]+totals[1]+totals[2])
print "Hand1: " + str(totals[0]) + " Hand2: " + str(totals[1]) + " Ties: " + str(totals[2])
print "Hand1: " + str(round((100*(totals[0])/((totals[0]+totals[1]+totals[2])+0.0)), 2))\
+ "% Hand2: " + str(round(((100*totals[1])/((totals[0]+totals[1]+totals[2])+0.0)), 2))\
+ "% Ties: " + str(round(((100*totals[2])/((totals[0]+totals[1]+totals[2])+0.0)), 2)) + "%"

Switchport Verification Script

A commenter on my previous post directed me to the module CiscoConfParse which would have likely made this a much easier exercise.  Even so, this was a great exercise for me to go through, as it allowed me to learn a new programming language.
I’ll still post my solution to this problem, along with the supporting files.  The first file below is the Python script.  Running it with a –h option will show you the proper format for the parameters.  The supporting file “verification.txt” is also listed below.  It describes the format to use for the verification seed file.
If you happen to see anything wrong with this script, or room for improvement, please share your thoughts via comment or email.  I am always looking to learn, and I know there is room for improvement in my Python programming!
Jeremy

--------------------------
configcheck.py:
#
# configcheck.py - A script that verifies the existence or
#   absence of specific configuration lines in a Cisco switch config
#
import os
import re
import sys
import getopt
#
# Checks router interface configuration against a base config
#
def get_config_snmp():
    pass
def get_config_file(filename):
    """getconfig loads a router config and returns an array with individual lines"""
    if os.path.isfile(filename) == 0:
        sys.exit("ERROR: File does not exist")
    contents = []
    with open(filename, 'r') as file:
        for a in file: contents.append(a)
    file.closed
#
# Clear '/r', '/n', ' ' characters from file (to allow interchangeability between Unix/Windows)
#
    for x in range(0, len(contents)):
        if len(contents[x]) > 1:
            while contents[x][-1:] in ('\r', '\n', ' '): contents[x] = contents[x][:-1]
    return contents
def gethostname(config):
    """gethostname retrieves the device hostname from configuration file"""
    n = 0
    for n in range (0, len(config)):
        if len(config[n-1]) > 9:
            if config[n-1][:8] == "hostname":
                return config[n-1][9:]
    return "No Hostname Found"
def check_interfaces(config, int_type, verify_config):
    """check_interfaces retrieves interface configuration\
from the config and sends it to verify"""
    n = -1
    while n <= (len(config) - 2):
        n += 1
        if len(config[n]) > 9:
            if config[n][:9] == "interface":
                interface = []
                interface.append(config[n])
                n += 1
                while config[n][0] == ' ':
                    interface.append(config[n])
                    n += 1
                if re.search(int_type, interface[1]):
                    verify(interface, verify_config)
def verify(interface, verify_config):
    """verify takes an interface config and a list of required statements\
and verifies that each is present"""
    s = "\n\n! The Following Interface is Missing the Command(s) Below: \n\n"\
    + interface[0] + '\n' + interface[1]
    error = 0
    for a in verify_config:
        if a[1] == "!":
           b = " " + a[2:] + " "
           if interface.count(b):
               error += 1
               if b[1:2] == "no":
                   s += " " + b[3:] + '\n'
               else:
                   s += " no" + b + '\n'
        elif not interface.count(a):
            error += 1
            s += a + '\n'
#Print Error Interfaces
    if error: print s
#
# Main Program Body
#
#
# Process command-line arguments
#
config = []
verify_file = "verification.txt"
try:
    opts, args = getopt.getopt(sys.argv[1:], "hs:f:v:", ["help", "snmp=", "file=", "verify="])
except getopt.GetoptError, err:
    # print help information and exit:
    print str(err) # will print something like "option -a not recognized"
    sys.exit(2)
for o, a in opts:
    if o in ("-h", "--help"):
        sys.exit("\nOptions:\n\
-s, --snmp: Use SNMP to retrieve configuration (not implemented in this version)\n\
-f, --file: Load configuration from local file\n\
-v, --verify: Load verification file from local file (default is verification.txt)\n\
-h, --help: This message")
    elif o in ("-v", "--verify"):
        verify_file = a
    elif o in ("-s", "--snmp"):
        config = get_config_snmp(a)
        break
    elif o in ("-f", "--file"):
        config = get_config_file(a)
        break
    else:
        assert False, "unhandled option"
print "\n\n\nHostname (from configuration file): " + str(gethostname(config))
#
# Cycle through verification.txt to retrieve individual verification configuration
#
contents = []
with open(verify_file, 'r') as file:
    for a in file:
        if len(a) >= 4:
            if a[0] == '[':
                int_type = a[1:5]
            elif a[0] == ' ':
#
# Clear '/r', '/n', ' ' characters from file (to allow interchangeability between Unix/Windows)
#
                while a[-1:] in ('\r', '\n', ' '): a = a[:-1]
                contents.append(a)
            elif a[0] == "#":
                pass
        elif a[0] == "#":
            pass
        else:
            check_interfaces(config, int_type, contents)
            contents = []
file.closed
------------------------------------------------------
verification.txt:
#
# Comments begin with "#"
# Commands that must not be in the switchport configuration should begin with " !"
# Each interface type is headed with [XXXX]
# There is to be a blank line between each type
#
[ENDU]
switchport access vlan 10
switchport mode access
!mls qos trust dscp
service-policy input END-USER-IN
ip dhcp snooping limit rate 100
spanning-tree portfast
spanning-tree bpduguard enable
no logging event link-status
no snmp trap link-status
load-interval 30
switchport voice vlan 12
[SRVR]
!mls qos trust dscp
service-policy input SERVER-IN
ip dhcp snooping limit rate 100
spanning-tree portfast
spanning-tree bpduguard enable
logging event link-status
no snmp trap link-status
load-interval 30
[MPBX]
switchport mode access
mls qos trust dscp
flowcontrol receive desired
flowcontrol send off
ip dhcp snooping limit rate 100
logging event link-status
snmp trap link-status
load-interval 30
-------------
And to be complete, here is a sample switch configuration to use
sampleswitch.log:
!
hostname SampleSwitch
!
interface GigabitEthernet1/2
description ENDU; User A - Properly Configured
switchport
switchport access vlan 10
switchport mode access
switchport voice vlan 12
no logging event link-status
load-interval 30
wrr-queue bandwidth 5 25 70
wrr-queue queue-limit 5 25 40
wrr-queue random-detect min-threshold 1 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 2 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 3 50 60 70 80 90 100 100 100
wrr-queue random-detect max-threshold 1 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 2 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 3 60 70 80 90 100 100 100 100
wrr-queue cos-map 1 1 1
wrr-queue cos-map 2 1 0
wrr-queue cos-map 3 1 4
wrr-queue cos-map 3 2 2
wrr-queue cos-map 3 3 3
wrr-queue cos-map 3 4 6
wrr-queue cos-map 3 5 7
mls qos trust dscp
flowcontrol receive desired
flowcontrol send off
spanning-tree portfast
spanning-tree bpduguard enable
service-policy input END-USER-IN
ip dhcp snooping limit rate 100
no snmp trap link-status
!
interface GigabitEthernet1/3
description SRVR; Server A - Properly Configured
switchport
switchport access vlan 15
switchport mode access
logging event link-status
load-interval 30
wrr-queue bandwidth 5 25 70
wrr-queue queue-limit 5 25 40
wrr-queue random-detect min-threshold 1 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 2 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 3 50 60 70 80 90 100 100 100
wrr-queue random-detect max-threshold 1 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 2 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 3 60 70 80 90 100 100 100 100
wrr-queue cos-map 1 1 1
wrr-queue cos-map 2 1 0
wrr-queue cos-map 3 1 4
wrr-queue cos-map 3 2 2
wrr-queue cos-map 3 3 3
wrr-queue cos-map 3 4 6
wrr-queue cos-map 3 5 7
mls qos trust dscp
flowcontrol receive desired
flowcontrol send off
spanning-tree portfast
spanning-tree bpduguard enable
service-policy input SERVER-IN
ip dhcp snooping limit rate 100
no snmp trap link-status
!
interface GigabitEthernet1/4
description SRVR; Purposely Misconfigured
switchport
switchport trunk encapsulation dot1q
switchport mode trunk
logging event link-status
load-interval 30
wrr-queue bandwidth 5 25 70
wrr-queue queue-limit 5 25 40
wrr-queue random-detect min-threshold 1 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 2 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 3 50 60 70 80 90 100 100 100
wrr-queue random-detect max-threshold 1 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 2 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 3 60 70 80 90 100 100 100 100
wrr-queue cos-map 1 1 1
wrr-queue cos-map 2 1 0
wrr-queue cos-map 3 1 4
wrr-queue cos-map 3 2 2
wrr-queue cos-map 3 3 3
wrr-queue cos-map 3 4 6
wrr-queue cos-map 3 5 7
mls qos trust dscp
flowcontrol receive desired
flowcontrol send off
spanning-tree portfast
spanning-tree bpduguard enable
ip dhcp snooping trust
!
interface GigabitEthernet1/5
description ENDU; Purposely Misconfigured
switchport
switchport access vlan 30
switchport mode access
switchport voice vlan 125
no logging event link-status
load-interval 30
wrr-queue bandwidth 5 25 70
wrr-queue queue-limit 5 25 40
wrr-queue random-detect min-threshold 1 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 2 80 100 100 100 100 100 100 100
wrr-queue random-detect min-threshold 3 50 60 70 80 90 100 100 100
wrr-queue random-detect max-threshold 1 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 2 100 100 100 100 100 100 100 100
wrr-queue random-detect max-threshold 3 60 70 80 90 100 100 100 100
wrr-queue cos-map 1 1 1
wrr-queue cos-map 2 1 0
wrr-queue cos-map 3 1 4
wrr-queue cos-map 3 2 2
wrr-queue cos-map 3 3 3
wrr-queue cos-map 3 4 6
wrr-queue cos-map 3 5 7
snmp trap mac-notification change added
mls qos trust dscp
flowcontrol receive desired
flowcontrol send off
spanning-tree portfast
spanning-tree bpduguard enable
service-policy input END-USER-IN
ip dhcp snooping limit rate 100

Tuesday, November 16, 2010

Python Programming and the Network Engineer

I’ve recently rediscovered my interest in computer programming.  As I mentioned before, I once was an avid programmer.  In my youth I hacked around in C++ and x86 assembler, making interesting little programs that didn’t accomplish much, but which gave me a great sense of accomplishment.  In high school, I was a member of the third place team in a national computer programming competition.  One of my teammates is now a software engineer at Google, so maybe I wasn’t exactly the star of the show, but my contributions seemed important at the time.  :)
My college years went a long way toward suppressing any interest I had in programming computers.  The rote, repetitive routine of programming compilers, operating systems, database engines, etc, drove me directly into a career of infrastructure work.  I have no professional regrets, but I do sort of miss the thrill of writing working code.  There is a great sense of satisfaction derived from formulating an idea and seeing it through from a blank Notepad++ document to a working, debugged applicati0n.
A couple of months ago I was faced with a project that resurrected my interest in programming.  My team and I have been working steadily toward standardizing our network deployments.  Standardization is a key requirement to effectively managing a large network with a small number of engineers.  Prior to this effort, such things as VLAN numbering and port-level configurations varied between our locations, even when two offices were roughly similar in size.  One of the causes of this was the melding of several different IT organizations when my employer was created.  We also acquired several smaller companies since our genesis in 2004.
To accomplish our switchport standardization, we use the description field to identify the type of device that is connected to a specific switchport.  If a PBX is connected to a port, the description field would be something like:
interface Gigabit1/1
description PBX; Name_of_PBX

and if the port is connected to an end user, we would use:
interface Gigabit1/2
description USR; John Doe – patch-panel 1A

The text after the semi-colon is free-form, and can be defined on a site-by-site basis to be whatever is relevant to the local support team.
PBX ports require a different set of configuration statements than USR ports.  For one example, we need a “DSCP trust” statement on the PBX port, while we remark USR traffic with a specific QoS policy using the “service-policy input USR-POLICY” command.
Our standardization effort is now largely complete.  Our latest challenge is making sure our configurations stay standardized.  VLAN identifiers are easy, since it is quite painful to change them.  Switchport configurations are a very different story.  It is quite easy for a switchport to be configured incorrectly but still work in a suboptimal fashion.  For example, if the wrong QoS marking policy is installed, basic testing will work (ping, etc), but under load there will likely be performance issues.  Even if the proper template is used to activate a port, it can be difficult to prevent that port from being reused for another purpose.
My solution to this issue was to implement an audit process that would take a baseline configuration and compare it to our production switch configurations.  I first reached out to our primary network tools provider, Solarwinds.  Their current feature provides some of the features I need, such as verifying that specific lines exist in the configuration.  But they cannot yet do context-sensitive configuration checking.  Other vendors (such as Netcordia) could perform this task, but I had recently championed a network tools consolidation project, so it would be a bit hypocritical of me to request funding for a new tool.  I decided to learn a scripting language to try and tackle this need.  A friend suggested that Python or Perl would fit the need nicely, so I chose Python (for no particularly good reason) and began learning.
I was quite surprised as how easily I learned the language and was able to solve my problem.  I spent a total of eight hours from the time I settled on Python until I had a working prototype.  The small investment of time is primarily due to the ease of use of the Python language, and not my programming acumen.
The entire script is a few hundred lines, including comments, and it meets all of my needs.  I wouldn’t say it is terribly user friendly, and I have a laundry list of additional features and ‘opportunities for improvement’, but I am happy with the result.  If I can find the time (and there is interest), I’ll clean it up and publish it in separate blog post.  It is generic enough that other organizations can probably use it without a lot of rewriting.