Last active
December 31, 2015 01:59
-
-
Save chrisking/7918255 to your computer and use it in GitHub Desktop.
client.py hackery done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| 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