Skip to content

Instantly share code, notes, and snippets.

@chrisking
Last active December 31, 2015 01:59
Show Gist options
  • Select an option

  • Save chrisking/7918255 to your computer and use it in GitHub Desktop.

Select an option

Save chrisking/7918255 to your computer and use it in GitHub Desktop.
client.py hackery done
"""
client.py contains all of the functionality required to interact with a client in your lab.
"""
#imports
import logging
import sh
import simplejson
import json
import os
from config import LUN_CONFIG
from config import CLIENTS
from config import READ_ONLY_SETTINGS
from config import PATH_TO_CONFIG
from config import LOG_FILE_DIRECTORY
#ASU acts as a global interface to the ASU command using the SH module.
ASU = sh.Command("./asu/asu64")
class ISCSIConfig(object):
"""
A ISCSIConfig is a pretty simple class, it leverages the config options for your lab
to determine the correct values for the core parameters that must be set for your
LUN booting attempt(BIOS term for boot option).
During the initialization of a client object, this will be invoked and an object will
then be bound to that client object.
"""
def __init__(self, client):
"""
The object is bootstrapped using the LUN Profile's configuration options first.
After this the specifics will be finalized, set, and the object is complete.
"""
self.client = client
if client.is_odd:
self.dict = LUN_CONFIG['ODD']
else:
self.dict = LUN_CONFIG['EVEN']
self.set_initiator_ip()
def set_initiator_ip(self):
"""
set_initiator_ip takes the client's hostname and uses it to generate
the correct IP for the client's 'Initiator IP'.
"""
#TODO check to make sure this can't be more than 254 after the additon of the hundred
ip_array = self.dict['Initiator IP'].split('.')
new_ip = ""
#set the new end value
ip_array[-1] = str(100+self.client.client_number)
#rebuild the string
for item in ip_array[:-1]:
new_ip += item + "."
new_ip += ip_array[-1]
self.dict['Initiator IP'] = new_ip
class Client(object):
"""
A Client object holds all of the necessary helper methods to perform the following
tasks:
1. Connect to the ASU and determine the state of this client.
2. Connect to the ASU and set proper values for the client to enable LUN booting.
3. Connect to the ASU and verify that all settings were correct.
4. Log any errors during this process.
5. Determine the proper LUN booting configuration options based on lab specified
cofiguration options in other files.
"""
def __init__(self, hostname=None):
"""
Takes in a hostname, then references the client configuration
dictionary. The attributes stored there are then bound to
this instance of the class.
A logging instance with the schema of hostname.log is also created.
It will store all of the logs for this client.
If no hostname is applied, none of these are set and a logfile of
'no_hostname.log' is set instead.
"""
#TODO make logging exposable via path
if hostname:
logging.basicConfig(filename=(os.path.join(LOG_FILE_DIRECTORY, (hostname + ".log"))),level=logging.DEBUG)
self.hostname = hostname
self.full_name = CLIENTS[hostname]['full_name']
self.username = CLIENTS[hostname]['username']
self.password = CLIENTS[hostname]['password']
self.IMM_name = CLIENTS[hostname]['IMM_name']
self.client_number = int(self.hostname.split('x')[1])
self.ISCSIConfig = ISCSIConfig(client=self)
else:
logging.basicConfig(filename=(os.path.join(LOG_FILE_DIRECTORY,"no_hostname.log")),level=logging.DEBUG)
def build_dictionary_from_asu_results(self, results):
"""
build_dictionary_from_asu_results takes in input as a string called results.
The string is then parsed into an array, cropped, and then parsed into a dictionary.
This dictionary stores all of the configuration options of a client in its current state.
"""
#Array by line
input_array = results.split('\n')
#Remove the first 4 lines as they are jibbersh output and not settings
input_array = input_array[4:]
config = {}
for line in input_array:
try:
item = line.split('=', 1)
config[item[0]] = item[1]
except Exception, e:
logging.error(e)
return config
def generated_desired_bios_state(self):
"""
generated_desired_bios_state performs the following tasks:
1. Obtains the skeleton config from the sample_client.json file included
in the project.
2. Loads in any custom values for the specific client.
3. Creates an instance dictionary that stores the desired config.
"""
try:
logging.info("Attempting to generate the desired bios state.")
with open(PATH_TO_CONFIG) as file_object:
self.desired_config = json.loads(file_object.read())
#Map ISCSI config data to desired_config info
self.desired_config['iSCSI.AttemptName.1'] = self.ISCSIConfig.dict['iSCSI Attempt Name']
self.desired_config['iSCSI.IscsiMode.1'] = self.ISCSIConfig.dict['iSCSI Mode']
self.desired_config['iSCSI.IpMode.1'] = self.ISCSIConfig.dict['IP']
self.desired_config['iSCSI.ConnectRetryCount.1'] = self.ISCSIConfig.dict['Connection Retry Count']
self.desired_config['iSCSI.ConnectTimeout.1'] = self.ISCSIConfig.dict['Connection Est']
#Hack due to crazy name of DHCP value:
if self.ISCSIConfig.dict['Enable DHCP'] == 'No':
self.desired_config['iSCSI.InitiatorInfoFromDhcp.1'] = 'Disable'
else:
self.desired_config['iSCSI.InitiatorInfoFromDhcp.1'] = 'Enable'
self.desired_config['iSCSI.LocalIp.1'] = self.ISCSIConfig.dict['Initiator IP']
self.desired_config['iSCSI.SubnetMask.1'] = self.ISCSIConfig.dict['Initiator Subnet']
self.desired_config['iSCSI.Gateway.1'] = self.ISCSIConfig.dict['Gateway']
self.desired_config['iSCSI.TargetIp.1'] = self.ISCSIConfig.dict['Target IP']
self.desired_config['iSCSI.TargetName.1'] = self.ISCSIConfig.dict['Target IQN']
logging.info("Desired configuration generation successful.")
except Exception, e:
logging.error("There was an error generating the desired bios configuration.")
logging.error(e)
def get_current_bios_state(self):
"""
get_current_bios_state executes a show all with the ASU utility, then pipes
the output to build_dictionary_from_asu_results. The output is then set as
and instance variable called current_config.
"""
asu_show = ASU.bake("show", "all", host=self.IMM_name, user=self.username, password=self.password)
logging.info("Starting show all command.")
self.current_config = self.build_dictionary_from_asu_results(results=asu_show())
logging.info("Show all successful, all data obtained.")
def generate_status_report_of_work(self, total_settings, unchanged_settings, successful_settings, failed_settings):
"""
generate_status_report_of_work takes in a few counters and prints out a lovely summary of the work
completed by send_bios_state.
"""
print "Client Report For: " + self.hostname
print "===================================="
print "Unchanged Settings: " + str(unchanged_settings)
print "Successfully Changed Settings: " + str(successful_settings)
print "Failed Settings: " + str(failed_settings)
print "Total Settings Evaluated: " + str(total_settings)
def send_bios_state(self):
"""
send_bios_state examines the current state of the bios on the client.
If any changes need to be made, they are performed and the status of that change
(success or failure) is logged.
"""
#These counters are used to report the total volume of work done by the application.
total_settings = len(self.desired_config.keys())
unchanged_settings = 0
successful_settings = 0
failed_settings = 0
#This is a hack to see if creating the attempt works better if done first.
try:
key_command = "iSCSI.AttemptName.1"
baked_command = ASU.bake("set", (key_command), self.desired_config[key_command] , host=self.IMM_name, user=self.username, password=self.password)
logging.info(baked_command)
baked_command()
successful_settings += 1
except Exception, e:
logging.error("Command Failed: " + str(baked_command))
logging.error(e)
failed_settings +=1
for key in self.desired_config.keys():
key_command = str(key)
baked_command = ASU.bake("set", (key_command), self.desired_config[key_command] , host=self.IMM_name, user=self.username, password=self.password)
try:
if ((self.desired_config[key] == "") and (self.current_config[key] == "")):
logging.info(key_command + " was blank so it was ignored.")
unchanged_settings += 1
else:
if self.current_config[key] == self.desired_config[key]:
logging.info("Skipped: " + key_command + " Was already set correctly.")
unchanged_settings += 1
else:
if key_command in READ_ONLY_SETTINGS:
logging.info("Skipped: " + key_command + " is readonly.")
unchanged_settings += 1
else:
logging.info("Changing: " + str(key) + " from: " + str(self.current_config[key]) + " to: " + str(self.desired_config[key]))
baked_command()
successful_settings += 1
except Exception, e:
logging.error("Command Failed: " + str(baked_command))
logging.error(e)
failed_settings +=1
self.generate_status_report_of_work(total_settings, unchanged_settings, successful_settings, failed_settings)
def is_odd(self):
"""
is_odd returns True if the node name is odd.
The designation of odd and even is important due to the layout of the
LUN booting implementation.
#TODO: Link to documentation on lab layout here.
Example:
pbgibm3650m3x028 = 28, False
pbgibm3650m3x027 = 27, True
"""
if self.client_number % 2 == 0:
return False
return True
def verify_bios_changes(self):
"""
verify_bios_changes retrieves another snapshot of the settings currently on the client.
This second pass is done after all of the settings were applied so no changes should be missed.
Once retrieved, each setting is examined against the desired configuration. All results are logged.
"""
#First delete the existing config
del(self.current_config)
#Update the current config object
self.get_current_bios_state()
#Compare
for key in self.desired_config.keys():
if self.desired_config[key] == self.current_config[key]:
logging.info(str(key) + " verified to be correct.")
else:
logging.error(str(key) + " was not verified to be correct.")
logging.error("Should have been: " + str(self.desired_config[key]) + ". Is: " + str(self.current_config[key]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment