Some days, you get better

Today while working on a function to perform server discovery from a customer ID, I looked back at some code I wrote over a year when I first starting out. It’s pretty amazing to see how far my writing has come and how much better efficient it has gotten.

Server discovery 101

One of the nice things about my environment is customer servers have a fairly consistent naming convention and it’s only been modified a couple of time. In general, servers follow a customerID + serverID + domain, naming convention.

I had written my discovery function(s) to look up servers, get their IP addresses and then fill out a table in a MOP so I didn’t have to do it manually. In my original scripts, I supplied the customerID, then pinged a variety of server name permutations and looked for the proper response, then pinged the rest of the servers based on the successful response. Although this had the benefit of verifying connectivity to the servers, it could take a couple of minutes to fail when connectivity wasn’t present for some reason.

Today, I re-wrote it to use nslookup results to build a dictionary of server role and name values. The benefit here, it runs quickly and returns the dictionary much quicker. The downside is I don’t get server reachability.

To resolve that, I decided to generate the dictionary then ping each server in the dictionary

def find_cucm_pub(customerID: str) -> str:
    fqdn_format = {
        'format1': customerID + '-cucmpub.example.com',
        'format2': customerID + '-cucmpub.example2.com',
    }
    name_format = None
    for name in fqdn_format:
        nslookup = subprocess.run(['nslookup', fqdn_format[name]], capture_output=True)
        nslookup_resp = nslookup.stdout.decode('UTF-8')
        if 'Name:' in nslookup_resp:
            name_format = name
    
    return name_format

def uc_cluster(customerID: str) -> dict:
    customerID = customerID.lower()
    fqdn_format = find_cucm_pub(customerID)
    cluster = None
    if not fqdn_format:
        pass
    elif fqdn_format == 'format1:
        cluster = {
            'cucm_pub': customerID + '-cucmpub.example.com',
            'cucm_sub1': customerID + '-cucmsub1.example.com',
...
    return cluster

cluster = uc_cluster(customerID)

for s in cluster:
    subprocess.run(['ping', '-c', '4', cluster[s]])

String generator

Another thing I worked on when I trying to understand lists in the very beginning was generating lists of strings of varying length. I found I could only build lists of a certain length before my laptop sounded like a jet engine taking off. I simply trying to generate a list of all the potential characters from a length 1 through a specified length.

My original “finished” product is here. It was interesting to me because I found the limitations of my laptop, but it was also interesting because I learned how much random stuff I could create.

Today I built this why sitting a conference call bored:

import string
import itertools


letters = string.ascii_lowercase
numbers = string.digits


def build_a(x):
    abc = []
    for a in itertools.product(letters, repeat=x):
        abc.append(''.join(a))
    
    return abc


def build_n(y):
    nnnn = []
    for n in itertools.product(numbers, repeat=y):
        nnnn.append(''.join(n))
    
    return nnnn


def build_an(x, y):
    abc = build_a(x)
    nnnn = build_n(y)
    ccmid = []
    for a, n in itertools.product(abc, nnnn):
        with open('customerID.txt', 'a') as f:
            f.write(a + n + '\n')
        f.close()

Feels like a slightly more elegant way to generate random customerIDs than my original attempts at creating data strings.

Random string generator number 2

import random
import string


characters = string.ascii_letters + string.digits + string.punctuation


def build_string(x):
    word = []
    while len(word) < x:
        word.append(random.choice(characters))
    
    return ''.join(word)

Cisco + Python trick of the day

Download config file(s) from Cisco Expressway and print them in a human friendly way.

"""Stupid Expressway tricks

From a CLI with scp available:
scp -r root@expressway.name:/tandberg/web/ local_dir
cd local_dir
python stupid_expressway_trick.py

You can also run something like scp -r root@expressway.name:/ local_dir
to copy the entire root structure
"""

import json


conf = open('web_cdb_cache.json', 'r')
conf = conf.readlines()
conf_json = json.loads(conf[0])
print(json.dumps(conf_json, indent=4))

Cisco UC server db_grabber.py

from __future__ import print_function
import argparse
import re
import traceback
import paramiko
from paramiko_expect import SSHClientInteraction


def db_grabber(server: str) -> dict:
    """
    Create SSH connection to server, get list of tables in DB, then copy each DB table to db_dump.txt
    """
    try:
        client = paramiko.SSHClient()
        client.load_system_host_keys()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(hostname=server, username=CLI_ADMIN, password=CLI_PASSWORD, allow_agent=False, look_for_keys=False)

        with SSHClientInteraction(client, timeout=60, display=False) as interact:
            interact.expect(PROMPT)
            print('connected')
            interact.send('run sql select tabname from systables')
            interact.expect(PROMPT)
            tabname = interact.current_output_clean
            tabname = tabname.splitlines()
            for line in tabname:
                if 'tabname' in line or '=' in line or 'scratch' in line:
                    pass
                elif re.match('^cdr.*', line):
                    pass
                elif re.match('^sys.*', line):
                    pass
                elif not line:
                    pass
                else:
                    print(f'run sql select * from {line.strip()}')
                    interact.send(f'run sql select * from {line.strip()}')
                    interact.expect(PROMPT)
                    db_output = interact.current_output_clean
                    with open('db_dump.txt', 'a') as f:
                        f.write(f'run sql select * from {line.strip()}\n')
                        f.writelines(db_output)
                        f.write('\n\n')
                    f.close()

            interact.send('exit')
            return 'finished'


    except Exception:
        traceback.print_exc()

        return 'SSH Failure'

    finally:
        try:
            client.close()
        except Exception:
            pass

parser = argparse.ArgumentParser(description='cisco db grabber')
parser.add_argument('server', help='Server to pull DB from')
args = parser.parse_args()
hostname = args.server
if __name__ == '__main__':
    db_grabber(hostname)

How to extract Cisco COP files

I’ve worked with Cisco servers for years now. Watching upgrades run, I always wondered what the scripts and files that run actually look like. Well, guess what, you can extract them and examine them yourself with a few free tools.

Step 1: Download the COP file you want to look at

Plain and simple. Go find the COP file you want to examine and download it from Cisco’s site.

Directory of COP files I wanted to look at

Step 2: Edit the COP file in a hex editor

I use Fedora Linux for my desktop and I found wxHexEditor works really well for this.

Open your COP file in your hex editor

Hex code… fun

Most hex editors have a column that contain all the code. In my screenshot you can see the big middle section shows the hex with a lot of strange looking characters just to the right.

Highlight the “real” file name.

In this area to the right, you want to search for the “real” file name. In this case, it’s ciscocm.preUpgradeCheck.tar. As I highlight in the right column, it highlights the code on the left. By doing this, it allows us to locate the string that indicates the start of the tar file – 1F 8B 08.

Once you’ve located the string you’re looking for, you need to delete everything before it. Highlight the all the code before the 1F 8B 08 string and then delete or cut it (this will depend on your editor).

Highlight everything before 1F 8B 08 and then delete it
My file now starts with 1F 8B 08

Now that you’ve deleted all the unwanted, signed header junk, you should be left with just the code for the tar file. Save the edited code as .gz or tar or zip file.

Saving as ciscocm.preUpgradeCheck-00024.gz

Once you have your newly saved file, you can close your hex editor and then extract the files with your favorite archive tool. 7-zip is handy for this.

Directory of extracted files

Step 3: Go look at your freshly extracted files

Depending on what you downloaded, go look at your freshly extracted files and check them.

I have to admit, most aren’t that exciting, but you can tell Cisco uses Python scripts to do a lot of the work and it is interesting to see how professional Cisco software engineers write their code.

100 days of code

I’ve decided to take on the 100 days of code “challenge.”

It’s a challenge because there is no way I am going to write code for 60 minutes each day for 100 days successfully. Won’t happen. Period.

I have a life away from my computer: wife, family, ultra running, going outside.

Most weekends I don’t even open my computer.

I just need a structure, but not really. I need something to work on and to do it consistently. I have multiple versions of scripts in my projects folder that all do similar things for work stuff. My real goal through this is to consolidate / unify them all into a usable package or a couple of packages and improve my skills in a demonstrable way.

My github repo – https://github.com/jgmitchell257/cisco-uc-scripts

a fresh start?

Once upon a time, I registered this URL because I wanted to write and work through all the things I think about while running long miles alone in the hills. I never really got around to doing anything with it or actually doing what I thought I might do with it.

Today, I decided I should change that for a number of reasons. There isn’t much here to begin with, so there’s not a lot a of baggage to deal with. I did pay for wordpress.com so there wouldn’t be ads and I can have more storage space for all the photos I don’t intend to upload. Really, I just support this product and don’t want ads.

so what is this?

alone in the hills is just my journal to replace facebook and instagram and other social media that I’ve grown tired of using. It is really for myself, it will probably fill up with rambling thoughts, pictures from daily life, python code snippets, and links to things I want to recall later. Beyond that, I can’t say. I’m not a fortune teller.

kids should play more

This morning while waiting with my son before school, there were a couple of kids from his class bouncing a basketball. Nothing disruptive. There wasn’t anyone really around them, they were just standing in line dribbling the ball and maybe passing it back and forth, a few feet between them.

The outside monitor lady told them to stop.

Some people wonder why kids behave the way they do. Maybe it’s because adults are constantly telling kids to not do kid stuff.