Back to the main page

Automation: Cobbler iPXE boot/install on iSCSI LUN

This is basically automation for Cobbler iPXE boot and install on iSCSI LUN

Intro

Reminder: (From OL7.6 Release Notes) Booting from an iSCSI device that is not configured by using iBFT now supported. The installer now includes a new boot option, inst.nonibftiscsiboot. This boot option can be used to install the boot loader onto an iSCSI LUN that has not been configured in the iSCSI Boot Firmware Table (iBFT).

Process

DHCP reservation

 

#!/usr/bin/env python import os import sys import getpass import socket import netaddr import argparse import ldap import re import platform import pwd # getting real user import subprocess from time import gmtime, strftime import datetime import logging, logging.handlers sys.path.append(os.path.abspath(os.path.join(sys.path[0], os.pardir))) # append parent dir import lib.dhcp_cfg as dhcp_cfg # import module for DHCP cfg files creation # --- LOCATION is uniq ---- LOC = "nsh" # some NSH site # ------------------------- i_am=getpass.getuser() if i_am != "root": sys.exit(i_am + ", please run this via sudo (otherwise you won't have permissions to complete reservation)") # even root must use sudo try: realuser = pwd.getpwuid(int(os.getenv('SUDO_UID'))).pw_name realuserid = os.getenv('SUDO_UID') except: #SUDO_UID is missing sys.exit("Even root is requested to use sudo, please re-run it with sudo.") # define logging directory structure PROGRAM = os.path.basename(sys.argv[0]) # name of this script LOG_PATH = ("/var/log/" + PROGRAM) # put together "/var/log/" if not os.path.exists(LOG_PATH): # create LOG_PATH if doesn't exists os.makedirs(LOG_PATH) os.chown(LOG_PATH,-1,580) # root(-1 means no change):gid 580 os.chmod(LOG_PATH,0775) # drwxrwxr-x def verify_mac(hwaddr): """ To be used as type for MAC argument """ if not netaddr.valid_mac(hwaddr): sys.exit("ERROR: %s is not valid MAC" % hwaddr) return hwaddr def verify_owner(username): """ Check if this is valid DSEE username https://www.python-ldap.org/en/python-ldap-3.2.0/reference/ldap.html Search filers can be: email address: searchFilter = "mail=mickey.mouse@dom.com" login: searchFilter = "uid=mmouse" UID: searchFilter = "uidNumber=123456789" """ DSEE_SRV="dsee-server.dom.com" # DSEE server baseDN = "dc=dom,dc=com" searchScope = ldap.SCOPE_SUBTREE # subtree search retrieveAttributes = None # create ldap object try: ldapobject = ldap.initialize('ldap://' + DSEE_SRV) ldapobject.protocol_version = ldap.VERSION3 except ldap.LDAPError as err: sys.exit("ldap.LDAPError: {0}".format(err)) try: searchFilter = "uid=" + username l_search = ldapobject.search(baseDN, searchScope, searchFilter, retrieveAttributes) result_status, result_data = ldapobject.result(l_search, 0) if not result_data: sys.exit("ERROR: there is no " + username + " in DSEE" ) else: return username except ldap.LDAPError as err: sys.exit("ldap.LDAPError: {0}".format(err)) # -- argument work parser = argparse.ArgumentParser( description="Add DHCP cfg file for a system (location: " + LOC + " )", epilog='Brought to you by ZD') os = parser.add_mutually_exclusive_group(required=True) os.add_argument("-a", "--static", help="Static reservation", action="store_true") # store_true for flag, no value os.add_argument("-l", "--linux", help="Linux install via Cobbler", action="store_true") os.add_argument("-i", "--solaris11", help="Solaris 11 install", action="store_true") os.add_argument("-j", "--solaris10", help="Solaris 10 x86 install (SPARC is not supported)", action="store_true") os.add_argument("-z", "--ak", help="AK install", action="store_true") os.add_argument("--ipxe_install", help="Install via iPXE (Linux install)", action="store_true") os.add_argument("--ipxe_boot", help="Boot (Linux) from iSCSI LUN via iPXE", action="store_true") parser.add_argument("-s", "--system", help="System's FQDN", required=True) parser.add_argument("-m", "--mac", help="System's MAC ( : as delimiter)", type=verify_mac, required=True) parser.add_argument("-b", "--bootfile", help="Custom boot file, only linux install (it must be in :/tftpboot)") parser.add_argument("-o", "--owner", help=argparse.SUPPRESS, type=verify_owner) # hidden option args = parser.parse_args() system=args.system mac=args.mac static=args.static linuxinstall=args.linux s11install=args.solaris11 s10install=args.solaris10 akinstall=args.ak ipxeinstall=args.ipxe_install ipxeboot=args.ipxe_boot # --- Define logging LOG_FILE = (LOG_PATH + "/" + system + "_" + datetime.datetime.now().strftime("%m-%d-%Y_%Hh%Mm%Ss")) # create logger (interface that script uses for logging) logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # create file handler (define log destination) handler = logging.handlers.TimedRotatingFileHandler(LOG_FILE, when='MIDNIGHT', backupCount=50, utc=False) # create formatter (define layout of logs) formatter = logging.Formatter('%(asctime)s:%(levelname)s: %(message)s') handler.setFormatter(formatter) # add handler to logger logger.addHandler(handler) # start logging logger.debug("") logger.debug("START AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime())) logger.debug("Location for a config file: " + LOC) SUBNETFILE = (sys.path[0] + "/" + LOC + "-subnets.txt") # location subnet file, in same dir as running script logger.debug("Subnet list is from the file " + SUBNETFILE) def not_whq_pls(): """ Check that script is not used on DHCP in WHQ and PLS2. These two DHCP servers are under LabOps control, and users should not modify anything there. """ if socket.gethostname() == "hq-infra1.dom.com" or socket.gethostname() == "pls-infra1.dom.com": logger.debug("ERROR: This can't be run on WHQ or PLS DHCP servers.") sys.exit("ERROR: This can't be run on WHQ or PLS DHCP servers.") # No running in WHQ/PLS not_whq_pls() # Log who runs this and on behalf of whom # compare with i_am, which is basically root # also check if hidden argument 'owner' is used if realuser != i_am: # run by mortal user via sudo if not args.owner: logger.debug("Run by " + realuser + " (uid: " + realuserid + ") as root, via sudo.") else: logger.debug(realuser + ", you cannot reserve on behalf of someone else.") sys.exit(realuser + ", you cannot reserve on behalf of someone else.") if realuser == i_am: # run by root if not args.owner: logger.debug("Root cannot do this without providing owner of reservation, use <-o DSEE_account>") sys.exit("Root cannot do this without providing owner of reservation, use <-o DSEE_account>") else: logger.debug("Run by root on behalf of " + args.owner) def bootfile_check(): """ Not used, but can be used to check existence of bootfile in Cobbler server """ if args.bootfile: if os.path.isfile("/tftpboot/" + args.bootfile): logger.debug("OK: /tftpboot/" + args.bootfile + " exists") else: logger.debug("ERROR: Cannot find /tftpboot/" + args.bootfile ) sys.exit("ERROR: Cannot find /tftpboot/" + args.bootfile ) def find_ip(): """ Determine IP for given system """ try: global ipaddress # define global to be used outside of this function global systemfqdn ipaddress = socket.gethostbyname(system) logger.debug("OK: " + system + "'s IP address is " + ipaddress) systemfqdn = socket.getfqdn(system) except: logger.debug("ERROR: " + system + " is not in DNS") sys.exit("ERROR: " + system + " is not in DNS") def subnet_check(ipaddress): """ Check if IP is in subnet, and find router and broadcast address. Arguments: 1. IP, determined by find_ip() 2. file (list of subnets), it's hard coded here! """ global default_router f = open(SUBNETFILE, 'r') ip_ok = False try: for subnet in f: if netaddr.IPAddress(ipaddress) in netaddr.IPNetwork(subnet): logger.debug("OK: " + ipaddress + " is on supported " + LOC + " subnet " + str(netaddr.IPNetwork(subnet))) default_router = str(netaddr.IPNetwork(subnet).network + 1) # assumption: default router is network IP + 1 broadcast_ip = str(netaddr.IPNetwork(subnet).broadcast) logger.debug("Default router (assumption: network IP + 1): " + default_router) logger.debug("Broadcast IP: " + broadcast_ip) ip_ok = True f.close() except: logger.debug("ERROR: Cannot analyze " + ipaddress + " and determine subnet and default router") sys.exit("ERROR: Cannot analyze " + ipaddress + " and determine subnet and default router") if not ip_ok: logger.debug("WARNING: " + ipaddress + " is on " + LOC + " subnet not supported on this DHCP!") sys.exit("WARNING: " + ipaddress + " is on " + LOC + " subnet not supported on this DHCP!") def create_cobbler_cfg_file(): """ Note: dash (-) & dot (.) can't be used in object name, hence we create first systemobject """ systemobject = system.replace("-", "_").replace(".", "_") logger.debug("System's object/ID: " + systemobject) try: if args.bootfile: systemobject = dhcp_cfg.COBBLER_SYSTEM(LOC, systemfqdn, mac, ipaddress, default_router, args.bootfile) logger.debug("Creating DHCP cfg for Cobbler system (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac + " " + systemobject.router + " " + str(systemobject.bootfile) ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout else: systemobject = dhcp_cfg.COBBLER_SYSTEM(LOC, systemfqdn, mac, ipaddress, default_router) logger.debug("Creating DHCP cfg for Cobbler system (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac + " " + systemobject.router ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout except: logger.debug("ERROR: Cannot create DHCP cfg for Cobbler system " + system) sys.exit("ERROR: Cannot create DHCP cfg for Cobbler system " + system) def create_static_cfg_file(): """ Note: dash (-) & dot (.) can't be used in object name, hence we create first systemobject """ systemobject = system.replace("-", "_").replace(".", "_") logger.debug("System's object/ID: " + systemobject) try: systemobject = dhcp_cfg.STATIC(LOC, systemfqdn, mac, ipaddress) logger.debug("Creating static DHCP cfg (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout except: logger.debug("ERROR: Cannot create static DHCP cfg for " + system) sys.exit("ERROR: Cannot create static DHCP cfg for " + system) def create_ipxe_install_cfg_file(): """ Note: dash (-) & dot (.) can't be used in object name, hence we create first systemobject """ systemobject = system.replace("-", "_").replace(".", "_") logger.debug("System's object/ID: " + systemobject) try: systemobject = dhcp_cfg.IPXE_INSTALL(LOC, systemfqdn, mac, ipaddress, default_router) logger.debug("Creating DHCP cfg for install via iPXE (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout except: logger.debug("ERROR: Cannot create DHCP cfg for install via iPXE for " + system) sys.exit("ERROR: Cannot create DHCP cfg for install via iPXE for " + system) def create_ipxe_iscsi_boot_cfg_file(): """ Note: dash (-) & dot (.) can't be used in object name, hence we create first systemobject """ systemobject = system.replace("-", "_").replace(".", "_") logger.debug("System's object/ID: " + systemobject) try: systemobject = dhcp_cfg.IPXE_ISCSI_BOOT(LOC, systemfqdn, mac, ipaddress, default_router) logger.debug("Creating DHCP cfg for iPXE boot from iSCSI LUN (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout except: logger.debug("ERROR: Cannot create DHCP cfg for iPXE boot from iSCSI LUN for " + system) sys.exit("ERROR: Cannot create DHCP cfg for iPXE boot from iSCSI LUN for " + system) def create_s11_cfg_file(): """ Note: dash (-) & dot (.) can't be used in object name, hence we create first systemobject """ systemobject = system.replace("-", "_").replace(".", "_") logger.debug("System's object/ID: " + systemobject) try: systemobject = dhcp_cfg.S11_SYSTEM(LOC, systemfqdn, mac, ipaddress) logger.debug("Creating DHCP cfg for Solaris11 system (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout except: logger.debug("ERROR: Cannot create DHCP cfg for Solaris 11 system " + system) sys.exit("ERROR: Cannot create DHCP cfg for Solaris 11 system " + system) def create_ak_cfg_file(): """ Note: dash (-) & dot (.) can't be used in object name, hence we create first systemobject """ systemobject = system.replace("-", "_").replace(".", "_") logger.debug("System's object/ID: " + systemobject) try: systemobject = dhcp_cfg.AK_SYSTEM(LOC, systemfqdn, mac, ipaddress) logger.debug("Creating DHCP cfg for AK system (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout except: logger.debug("ERROR: Cannot create DHCP cfg for AK system " + system) sys.exit("ERROR: Cannot create DHCP cfg for AK system " + system) def create_s10_cfg_file(): """ Note: dash (-) & dot (.) can't be used in object name, hence we create first systemobject """ systemobject = system.replace("-", "_").replace(".", "_") logger.debug("System's object/ID: " + systemobject) try: systemobject = dhcp_cfg.S10_SYSTEM_X86(LOC, systemfqdn, mac, ipaddress) logger.debug("Creating DHCP cfg for Solaris10 x86 system (location: " + systemobject.loc + ") with: " + systemobject.hostname + " " + systemobject.mac ) systemobject.create_cfg_file() orig = sys.stdout # initial stdout sys.stdout = open(LOG_FILE, "a") # want to read cfg file into log logger.debug("Checking the cfg file:") systemobject.read_cfg_file() sys.stdout.close() sys.stdout = orig # restore initial stdout except: logger.debug("ERROR: Cannot create DHCP cfg for Solaris 10 x86 system " + system) sys.exit("ERROR: Cannot create DHCP cfg for Solaris 10 x86 system " + system) def compile_client_list(): """ Compile list of clients for clients.include file. Need to import os module again or get error: AttributeError: '_MutuallyExclusiveGroup' object has no attribute 'path' Need to troubleshoot more!? """ import os CLIENTSDIR = "/etc/dhcp/clients/" if not os.path.exists(CLIENTSDIR): logger.debug("ERROR: " + CLIENTSDIR + " does not exist.") sys.exit("ERROR: " + CLIENTSDIR + " does not exist.") f = open("/etc/dhcp/clients.include", "w+") try: for root, dirs, files in os.walk(CLIENTSDIR): for clientname in files: f.write("include \"" + CLIENTSDIR + clientname + "\" ;\n") logger.debug("OK: Compiled new /etc/dhcp/clients.include") except: logger.debug("ERROR: cannot compile /etc/dhcp/clients.include") sys.exit("ERROR: cannot compile /etc/dhcp/clients.include") f.close() def dhcp_cfg_check(): """ Check DHCP cfg syntax, ol678 """ if re.match("6.*", platform.dist()[1]): logger.debug("Check DHCP config for OL6") return True if subprocess.call(['service', 'dhcpd', 'configtest']) == 0 else False else: logger.debug("Check DHCP config for OL[78]") return True if subprocess.call(['dhcpd', '-t']) == 0 else False def dhcp_restart(): """ Restart DHCP service, ol678 """ if re.match("6.*", platform.dist()[1]): logger.debug("OL6 DHCP service restart") return True if subprocess.call(['service', 'dhcpd', 'restart']) == 0 else False else: logger.debug("OL[78] DHCP service restart") return True if subprocess.call(['systemctl', 'restart', 'dhcpd']) == 0 else False # ---- MAIN ----- if __name__ == '__main__': if linuxinstall: print ("Install type: Linux") logger.debug("Install type: Linux") find_ip() subnet_check(ipaddress) create_cobbler_cfg_file() compile_client_list() if not dhcp_cfg_check(): logger.debug("ERROR: DHCP config check fails") sys.exit("ERROR: DHCP config check fails") else: logger.debug("OK: DHCP config looks good") if not dhcp_restart(): logger.debug("ERROR: DHCP service restart fails") sys.exit("ERROR: DHCP service restart fails") else: logger.debug("OK: DHCP service has been restarted") elif static: print ("Static reservation") logger.debug("Static reservation") find_ip() subnet_check(ipaddress) create_static_cfg_file() compile_client_list() if not dhcp_cfg_check(): logger.debug("ERROR: DHCP config check fails") sys.exit("ERROR: DHCP config check fails") else: logger.debug("OK: DHCP config looks good") print("OK: DHCP config looks good") if not dhcp_restart(): logger.debug("ERROR: DHCP service restart fails") sys.exit("ERROR: DHCP service restart fails") else: logger.debug("OK: DHCP service has been restarted") print("OK: DHCP service has been restarted") elif s11install: print ("Install type: Solaris 11") logger.debug("Install type: Solaris 11") find_ip() subnet_check(ipaddress) create_s11_cfg_file() compile_client_list() if not dhcp_cfg_check(): logger.debug("ERROR: DHCP config check fails") sys.exit("ERROR: DHCP config check fails") else: logger.debug("OK: DHCP config looks good") if not dhcp_restart(): logger.debug("ERROR: DHCP service restart fails") sys.exit("ERROR: DHCP service restart fails") else: logger.debug("OK: DHCP service has been restarted") elif akinstall: print ("Install type: AK") logger.debug("Install type: AK") find_ip() subnet_check(ipaddress) create_ak_cfg_file() compile_client_list() if not dhcp_cfg_check(): logger.debug("ERROR: DHCP config check fails") sys.exit("ERROR: DHCP config check fails") else: logger.debug("OK: DHCP config looks good") if not dhcp_restart(): logger.debug("ERROR: DHCP service restart fails") sys.exit("ERROR: DHCP service restart fails") else: logger.debug("OK: DHCP service has been restarted") elif s10install: print ("Install type: Solaris 10 x86") logger.debug("Install type: Solaris 10 x86") find_ip() subnet_check(ipaddress) create_s10_cfg_file() # arch is checked inside function compile_client_list() if not dhcp_cfg_check(): logger.debug("ERROR: DHCP config check fails") sys.exit("ERROR: DHCP config check fails") else: logger.debug("OK: DHCP config looks good") if not dhcp_restart(): logger.debug("ERROR: DHCP service restart fails") sys.exit("ERROR: DHCP service restart fails") else: logger.debug("OK: DHCP service has been restarted") elif ipxeinstall: print ("Install type: via iPXE (Linux)") logger.debug("Install type: via iPXE (Linux)") find_ip() subnet_check(ipaddress) create_ipxe_install_cfg_file() compile_client_list() if not dhcp_cfg_check(): logger.debug("ERROR: DHCP config check fails") sys.exit("ERROR: DHCP config check fails") else: logger.debug("OK: DHCP config looks good") if not dhcp_restart(): logger.debug("ERROR: DHCP service restart fails") sys.exit("ERROR: DHCP service restart fails") else: logger.debug("OK: DHCP service has been restarted") elif ipxeboot: print ("Boot from iSCSI LUN via iPXE") logger.debug("Boot from iSCSI LUN via iPXE") find_ip() subnet_check(ipaddress) create_ipxe_iscsi_boot_cfg_file() compile_client_list() if not dhcp_cfg_check(): logger.debug("ERROR: DHCP config check fails") sys.exit("ERROR: DHCP config check fails") else: logger.debug("OK: DHCP config looks good") if not dhcp_restart(): logger.debug("ERROR: DHCP service restart fails") sys.exit("ERROR: DHCP service restart fails") else: logger.debug("OK: DHCP service has been restarted") logger.debug("FINISH AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime()))

dhcp_cfg.py library

 

" Module to create DHCP config file for Cobbler and ca-install clients" # The module name is .py # When import, use only : ex. # import # Use sys.path.append("/path/to/module") if needed # ------------------------------------------------------ import os import socket import sys import re global DHCPCFGDIR DHCPCFGDIR = "/etc/dhcp/clients/" # -------------------------- # Cobbler system # -------------------------- class COBBLER_SYSTEM(object): """ Blueprint for DHCP Cobbler system's config file Attributes: 1. loc (string, location is hardcoded in script that imports this module) 2. hostname (string, user input) 3. mac (string, user input) 4. ip (string, calculated) 5. router (string, calculated) 6. bootfile (string, user input): default is None object """ def __init__(self, loc, hostname, mac, ip, router, bootfile=None): """ Initialize object whos attributes are collection of data used to create DHCP config file for Cobbler client """ self.loc = loc self.hostname = hostname self.mac = mac self.ip = ip self.router = router self.bootfile = bootfile global CFG_COBBLER_SYSTEM CFG_COBBLER_SYSTEM = DHCPCFGDIR + self.hostname # Get values from config file, each loc has its own dir/cfg file # parent directory is os.path.join(sys.path[0], os.pardir) LOCATION_CFGFILE = (os.path.join(sys.path[0], os.pardir) + "/" + self.loc + "/" + self.loc + ".cfg") try: execfile(LOCATION_CFGFILE, globals()) except IOError as err: sys.exit("IOError: {0}".format(err)) global cobbler_server_ip cobbler_server_ip=socket.gethostbyname(COBBLER_SERVER) # IP of Cobbler server # --------------------------------------------------------------- # this is of you want to manipulate filename attribute of an object # --------------------------------------------------------------- @property def bootfile(self): """ Get 'bootfile' property/value """ return self._bootfile @bootfile.setter def bootfile(self, value): """ Set value to 'bootfile' """ self._bootfile = value @bootfile.deleter def bootfile(self): """ Delete 'bootfile' """ del self._bootfile # --------------------------------------------------------------- def create_cfg_file(self): """ Creates config file """ print ("DHCP reservation has been created: " + CFG_COBBLER_SYSTEM) try: if self.bootfile != None: f = open(CFG_COBBLER_SYSTEM, "w+") f.write("host " + self.hostname + " {\n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\toption routers " + self.router + " ;\n") f.write("\tnext-server " + cobbler_server_ip + " ;\n") f.write("\tfilename " + "\"" + str(self.bootfile) + "\"" + " ;\n") f.write("\t }\n") f.close() else: f = open(CFG_COBBLER_SYSTEM, "w+") f.write("host " + self.hostname + " {\n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\toption routers " + self.router + " ;\n") f.write("\tnext-server " + cobbler_server_ip + " ;\n") f.write("\t }\n") f.close() except IOError as err: print("IOError: {0}".format(err)) def remove_cfg_file(self): """ Removes config file """ try: os.remove(CFG_COBBLER_SYSTEM) print ("File " + CFG_COBBLER_SYSTEM + " is removed!") except IOError as err: print("IOError: {0}".format(err)) def read_cfg_file(self): """ Prints config file """ print ("Reading file " + CFG_COBBLER_SYSTEM) try: f = open(CFG_COBBLER_SYSTEM, "r") print f.read() f.close() except IOError as err: print("IOError: {0}".format(err)) # ---------------------- # Static reservation # --------------------- class STATIC(object): """ Blueprint for static DHCP reservation Attributes: 1. loc (string, location is hardcoded in script that imports this module) 2. hostname (string, user input) 3. mac (string, user input) 4. ip (string, calculated) """ def __init__(self, loc, hostname, mac, ip): """ Initialize object whos attributes are collection of data used to create client's static reservation """ self.loc = loc self.hostname = hostname self.mac = mac self.ip = ip global CFG_STATIC CFG_STATIC = DHCPCFGDIR + self.hostname def create_cfg_file(self): """ Creates config file """ print ("DHCP reservation has been created: " + CFG_STATIC) try: f = open(CFG_STATIC, "w+") f.write("host " + self.hostname + " { \n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\t }\n") f.close() except IOError as err: print("IOError: {0}".format(err)) def remove_cfg_file(self): """ Removes config file """ try: os.remove(CFG_STATIC) print ("File " + CFG_STATIC + " is removed!") except IOError as err: print("IOError: {0}".format(err)) def read_cfg_file(self): """ Prints config file """ print ("Reading file " + CFG_STATIC) try: f = open(CFG_STATIC, "r") print f.read() f.close() except IOError as err: print("IOError: {0}".format(err)) # -------------------------- # Solaris 11 system # -------------------------- class S11_SYSTEM(object): """ Blueprint for Solaris 11 client's DHCP config file Attributes: 1. loc (string, location is hardcoded in script that imports this module) 2. hostname (string, user input) 3. mac (string, user input) 4. ip (string, calculated) """ def __init__(self, loc, hostname, mac, ip): """ Initialize object whos attributes are collection of data used to create DHCP config file for ca-install client """ self.loc = loc self.hostname = hostname self.mac = mac self.ip = ip global CFG_S11_SYSTEM CFG_S11_SYSTEM = DHCPCFGDIR + self.hostname # Get values from config file, each loc has its own dir/cfg file # parent directory is os.path.join(sys.path[0], os.pardir) LOCATION_CFGFILE = (os.path.join(sys.path[0], os.pardir) + "/" + self.loc + "/" + self.loc + ".cfg") try: execfile(LOCATION_CFGFILE, globals()) except IOError as err: sys.exit("IOError: {0}".format(err)) global cainstall_ip cainstall_ip=socket.gethostbyname(CAINSTALL_SERVER) # IP of ca-install server def create_cfg_file(self): """ Creates config file """ print ("DHCP reservation has been created: " + CFG_S11_SYSTEM) client_id = "01" + re.sub(":","",self.mac).upper() try: f = open(CFG_S11_SYSTEM, "w+") f.write("host " + self.hostname + " { \n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\tnext-server " + cainstall_ip + " ;\n") f.write("\tif option arch = 00:00 { \n") f.write("\t\tfilename \"" + client_id + ".bios" + "\";\n") f.write("\t} else if option arch = 00:07 { \n") f.write("\t\tfilename \"" + client_id + ".uefi" + "\";\n") f.write("\t} else { \n") f.write("\t\tfilename \"http://" + cainstall_ip + ":5555/cgi-bin/wanboot-cgi\" ;\n") f.write("\t\t }\n") f.write("\t }\n") f.close() except IOError as err: print("IOError: {0}".format(err)) def remove_cfg_file(self): """ Removes config file """ try: os.remove(CFG_S11_SYSTEM) print ("File " + CFG_S11_SYSTEM + " is removed!") except IOError as err: print("IOError: {0}".format(err)) def read_cfg_file(self): """ Prints config file """ print ("Reading file " + CFG_S11_SYSTEM) try: f = open(CFG_S11_SYSTEM, "r") print f.read() f.close() except IOError as err: print("IOError: {0}".format(err)) # ----------------------------- # Solaris 10 system x84 # ----------------------------- class S10_SYSTEM_X86(object): """ Blueprint for Solaris 10 x86 client's DHCP config file Attributes: 1. loc (string, location is hardcoded in script that imports this module) 2. hostname (string, user input) 3. mac (string, user input) 4. ip (string, calculated) """ def __init__(self, loc, hostname, mac, ip): """ Initialize object whos attributes are collection of data used to create DHCP config file for ca-install client """ self.loc = loc self.hostname = hostname self.mac = mac self.ip = ip global CFG_S10_SYSTEM_X86 CFG_S10_SYSTEM_X86 = DHCPCFGDIR + self.hostname # Get values from config file, each loc has its own dir/cfg file # parent directory is os.path.join(sys.path[0], os.pardir) LOCATION_CFGFILE = (os.path.join(sys.path[0], os.pardir) + "/" + self.loc + "/" + self.loc + ".cfg") try: execfile(LOCATION_CFGFILE, globals()) except IOError as err: sys.exit("IOError: {0}".format(err)) global cainstall_ip cainstall_ip=socket.gethostbyname(CAINSTALL_SERVER) # IP of ca-install server def create_cfg_file(self): """ Creates config file """ print ("DHCP reservation has been created: " + CFG_S10_SYSTEM_X86) client_id = "01" + re.sub(":","",self.mac).upper() try: f = open(CFG_S10_SYSTEM_X86, "w+") f.write("host " + self.hostname + " { \n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\tfilename \"" + client_id + "\" ;\n") f.write("\tnext-server " + cainstall_ip + " ;\n") f.write("\t }\n") f.close() except IOError as err: print("IOError: {0}".format(err)) def remove_cfg_file(self): """ Removes config file """ try: os.remove(CFG_S10_SYSTEM_X86) print ("File " + CFG_S10_SYSTEM_X86 + " is removed!") except IOError as err: print("IOError: {0}".format(err)) def read_cfg_file(self): """ Prints config file """ print ("Reading file " + CFG_S10_SYSTEM_X86) try: f = open(CFG_S10_SYSTEM_X86, "r") print f.read() f.close() except IOError as err: print("IOError: {0}".format(err)) # --------------- # AK system # --------------- class AK_SYSTEM(object): """ Blueprint for AK client's DHCP config file Attributes: 1. loc (string, location is hardcoded in script that imports this module) 2. hostname (string, user input) 3. mac (string, user input) 4. ip (string, calculated) """ def __init__(self, loc, hostname, mac, ip): """ Initialize object whos attributes are collection of data used to create DHCP config file for ca-install client """ self.loc = loc self.hostname = hostname self.mac = mac self.ip = ip global CFG_AK_SYSTEM CFG_AK_SYSTEM = DHCPCFGDIR + self.hostname # Get values from config file, each loc has its own dir/cfg file # parent directory is os.path.join(sys.path[0], os.pardir) LOCATION_CFGFILE = (os.path.join(sys.path[0], os.pardir) + "/" + self.loc + "/" + self.loc + ".cfg") try: execfile(LOCATION_CFGFILE, globals()) except IOError as err: sys.exit("IOError: {0}".format(err)) global cainstall_ip cainstall_ip=socket.gethostbyname(CAINSTALL_SERVER) # IP of ca-install server def create_cfg_file(self): """ Creates config file """ print ("DHCP reservation has been created: " + CFG_AK_SYSTEM) client_id = "01" + re.sub(":","",self.mac).upper() try: f = open(CFG_AK_SYSTEM, "w+") f.write("host " + self.hostname + " { \n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\tnext-server " + cainstall_ip + " ;\n") f.write("\tif option arch = 00:00 { \n") f.write("\t\tfilename \"" + client_id + ".bios" + "\";\n") f.write("\t} else if option arch = 00:07 { \n") f.write("\t\tfilename \"" + client_id + ".uefi" + "\";\n") f.write("\t} \n") f.write("\t }\n") f.close() except IOError as err: print("IOError: {0}".format(err)) def remove_cfg_file(self): """ Removes config file """ try: os.remove(CFG_AK_SYSTEM) print ("File " + CFG_AK_SYSTEM + " is removed!") except IOError as err: print("IOError: {0}".format(err)) def read_cfg_file(self): """ Prints config file """ print ("Reading file " + CFG_AK_SYSTEM) try: f = open(CFG_AK_SYSTEM, "r") print f.read() f.close() except IOError as err: print("IOError: {0}".format(err)) # --------------- # iPXE install # --------------- class IPXE_INSTALL(object): """ Blueprint for DHCP config file for iPXE install Attributes: 1. loc (string, location is hardcoded in script that imports this module) 2. hostname (string, user input) 3. mac (string, user input) 4. ip (string, calculated) 5. router (string, calculated) """ def __init__(self, loc, hostname, mac, ip, router): """ Initialize object whos attributes are collection of data used to create DHCP config file for Cobbler client for iPXE install """ self.loc = loc self.hostname = hostname self.mac = mac self.ip = ip self.router = router global CFG_IPXE_INSTALL CFG_IPXE_INSTALL = DHCPCFGDIR + self.hostname # Get values from config file, each loc has its own dir/cfg file # parent directory is os.path.join(sys.path[0], os.pardir) LOCATION_CFGFILE = (os.path.join(sys.path[0], os.pardir) + "/" + self.loc + "/" + self.loc + ".cfg") try: execfile(LOCATION_CFGFILE, globals()) except IOError as err: sys.exit("IOError: {0}".format(err)) global cobbler_server_ip cobbler_server_ip=socket.gethostbyname(COBBLER_SERVER) # IP of Cobbler server def create_cfg_file(self): """ Creates config file """ print ("DHCP reservation has been created: " + CFG_IPXE_INSTALL) try: f = open(CFG_IPXE_INSTALL, "w+") f.write("host " + self.hostname + " {\n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\toption routers " + self.router + " ;\n") f.write("\tnext-server " + cobbler_server_ip + " ;\n") f.write("\t# this is configuration to boot and install an OS via iPXE \n") f.write("\t# install can be on local disk or iSCSI LUN \n") f.write("\tif exists user-class and option user-class = \"iPXE\" { \n") f.write("\t\tfilename \"http://" + cobbler_server_ip + "/cblr/svc/op/gpxe/system/" + self.hostname + "\" ;\n") f.write("\t} \n") f.write("\telsif option arch != 00:00 { filename \"gpxe/snponly.efi\" ; } \n") f.write("\telse { filename \"gpxe/undionly.kpxe\" ; } \n") f.write("\t }\n") f.close() except IOError as err: print("IOError: {0}".format(err)) def remove_cfg_file(self): """ Removes config file """ try: os.remove(CFG_IPXE_INSTALL) print ("File " + CFG_IPXE_INSTALL + " is removed!") except IOError as err: print("IOError: {0}".format(err)) def read_cfg_file(self): """ Prints config file """ print ("Reading file " + CFG_IPXE_INSTALL) try: f = open(CFG_IPXE_INSTALL, "r") print f.read() f.close() except IOError as err: print("IOError: {0}".format(err)) # --------------------------- # iPXE boot from iSCSI LUN # ------------------------- class IPXE_ISCSI_BOOT(object): """ Blueprint for DHCP config file for iPXE boot from iSCSI LUN Attributes: 1. loc (string, location is hardcoded in script that imports this module) 2. hostname (string, user input) 3. mac (string, user input) 4. ip (string, calculated) 5. router (string, calculated) """ def __init__(self, loc, hostname, mac, ip, router): """ Initialize object whos attributes are collection of data used to create DHCP config file for Cobbler client to iPXE boot from iSCSI LUN """ self.loc = loc self.hostname = hostname self.mac = mac self.ip = ip self.router = router global CFG_IPXE_ISCSI_BOOT CFG_IPXE_ISCSI_BOOT = DHCPCFGDIR + self.hostname # Get values from config file, each loc has its own dir/cfg file # parent directory is os.path.join(sys.path[0], os.pardir) LOCATION_CFGFILE = (os.path.join(sys.path[0], os.pardir) + "/" + self.loc + "/" + self.loc + ".cfg") try: execfile(LOCATION_CFGFILE, globals()) except IOError as err: sys.exit("IOError: {0}".format(err)) global cobbler_server_ip cobbler_server_ip=socket.gethostbyname(COBBLER_SERVER) # IP of Cobbler server def create_cfg_file(self): """ Creates config file """ print ("DHCP reservation has been created: " + CFG_IPXE_ISCSI_BOOT) try: f = open(CFG_IPXE_ISCSI_BOOT, "w+") f.write("host " + self.hostname + " {\n") f.write("\thardware ethernet " + self.mac + " ;\n") f.write("\tfixed-address " + self.ip + " ;\n") f.write("\toption routers " + self.router + " ;\n") f.write("\tnext-server " + cobbler_server_ip + " ;\n") f.write("\t# this is configuration to iPXE boot from iSCSI LUN \n") f.write("\tif exists user-class and option user-class = \"iPXE\" { \n") f.write("\t\tfilename \"http://" + cobbler_server_ip + "/tftpboot/gpxe/clients/" + self.hostname + "\" ;\n") f.write("\t} \n") f.write("\telsif option arch != 00:00 { filename \"gpxe/snponly.efi\" ; } \n") f.write("\telse { filename \"gpxe/undionly.kpxe\" ; } \n") f.write("\t }\n") f.close() except IOError as err: print("IOError: {0}".format(err)) def remove_cfg_file(self): """ Removes config file """ try: os.remove(CFG_IPXE_ISCSI_BOOT) print ("File " + CFG_IPXE_ISCSI_BOOT + " is removed!") except IOError as err: print("IOError: {0}".format(err)) def read_cfg_file(self): """ Prints config file """ print ("Reading file " + CFG_IPXE_ISCSI_BOOT) try: f = open(CFG_IPXE_ISCSI_BOOT, "r") print f.read() f.close() except IOError as err: print("IOError: {0}".format(err)) if __name__ == '__main__': # Cobbler client test, only when module is run directly ! # Staic print STATIC.__doc__ ca_zdudic1 = STATIC("sca", "ca-zdudic1.dom.com", "00:ab:cd:ef:22:33", "10.147.24.49") ca_zdudic1.create_cfg_file() ca_zdudic1.read_cfg_file() ca_zdudic1.remove_cfg_file() # iPXE install test print IPXE_INSTALL.__doc__ ca_zdudic2 = IPXE_INSTALL("sca", "ca-zdudic2.dom.com", "00:ab:cd:ef:22:33", "10.147.24.49", "10.147.24.1") ca_zdudic2.create_cfg_file() ca_zdudic2.read_cfg_file() ca_zdudic2.remove_cfg_file() # iPXE boot from iSCSI print IPXE_ISCSI_BOOT.__doc__ ca_zdudic3 = IPXE_ISCSI_BOOT("sca", "ca-zdudic2.dom.com", "00:ab:cd:ef:22:33", "10.147.24.49", "10.147.24.1") ca_zdudic3.create_cfg_file() ca_zdudic3.read_cfg_file() ca_zdudic3.remove_cfg_file()

Add Cobbler system

 

#!/opt/satools/bin/python3.5 # ------------ NSH --------------------------------------- # Add a system (including DHCP reservation) to NSH Cobbler # see variables below! # ----------------------------------------------------- import os import argparse import logging, logging.handlers from time import gmtime, strftime import datetime from pathlib import Path import socket import netaddr import sys import subprocess import paramiko import getpass import pwd import ipaddress import re from lxml import html import requests # ------------- # variables # ------------ nsh_dns = "206.22.27.1,206.22.27.2" nsh_dhcp = "ca-dhcp-nsh.dom.com" # NSH DHCP server url_subnets = "http://" + nsh_dhcp + "/subnets/" # logging destination PROGRAM = os.path.basename(sys.argv[0]) # name of this script LOG_PATH = ("/var/log/" + PROGRAM) # put together "/var/log/" if not os.path.exists(LOG_PATH): # create LOG_PATH if doesn't exists os.makedirs(LOG_PATH) os.chown(LOG_PATH,-1,10) # root(-1 means no change):staff DSEE group os.chmod(LOG_PATH,0o775) # drwxrwxr-x def verify_mac(hwaddr): """ To be used as type for MAC argument """ if not netaddr.valid_mac(hwaddr): sys.exit("%s is not valid MAC" % hwaddr) return hwaddr def list_cobbler_profiles(): """ List Cobbler profiles, to be used as choices for -p """ b_profiles = subprocess.check_output(['sudo', 'cobbler', 'profile', 'list']) # returns byte object profiles = b_profiles.decode('ASCII').replace("\n", "").strip() # convert byte object into string l_profiles = profiles.split() # create list from string list_length = len(l_profiles) # number of profiles print("-----------------\n Cobbler profiles: \n-----------------") global dict_profiles dict_profiles = {} # create empty dictionary for prof in l_profiles: print(str(l_profiles.index(prof)) + ". " + prof) dict_profiles[l_profiles.index(prof)] = prof # fill directory with "number. profile" print("\n") return list_length # this will be used in argparse choices parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description="Add a system (including DHCP reservation) to NSH Cobbler", epilog='Brought to you by ZD') parser.add_argument("-s", "--system", help="A system to be installed via Cobbler", required=True) parser.add_argument("-m", "--mac", help="System's MAC (: as delimiter)", type=verify_mac, required=True) parser.add_argument("-p", "--profile", help="List of current Cobbler profiles, select one", type=int, choices=range(list_cobbler_profiles()), required=True) parser.add_argument("-i", "--iscsi_lun", help="iSCSI SAN URI, ex. :[protocol]:[port]:[LUN]: \ \n- ipv4 (required): iSCSI server IPv4 \ \n- protocol (optional): ignored and can be left empty \ \n- port (optional): TCP port of iSCSI target, leave empty for default port 3260 \ \n- LUN (optional): LUN number, leave empty for default value 0 \ \n- targetname (required): iSCSI target IQN") parser.add_argument("-k", "--kickstart", help="Full path to your custom file, ex. /var/lib/cobbler/kickstarts/users-tmp/.ks") args = parser.parse_args() system=args.system mac=args.mac profile=args.profile iscsilun=args.iscsi_lun # --- Define logging LOG_FILE = (LOG_PATH + "/" + system + "_" + datetime.datetime.now().strftime("%m-%d-%Y_%Hh%Mm%Ss")) # create logger (interface that script uses for logging) logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # create file handler (define log destination) handler = logging.handlers.TimedRotatingFileHandler(LOG_FILE, when='MIDNIGHT', backupCount=50, utc=False) # create formatter (define layout of logs) formatter = logging.Formatter('%(asctime)s:%(levelname)s: %(message)s') handler.setFormatter(formatter) # add handler to logger logger.addHandler(handler) logger.debug("") logger.debug("START AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime())) i_am=getpass.getuser() if i_am == "root": logger.debug("Root cannot run this, please run it as yourself") sys.exit("Root cannot run this, please run it as yourself") else: logger.debug("Running by " + i_am) def dns_check(): """ Check if system is registered in DNS """ global ip # need ip for other functions try: ip = socket.gethostbyname(system) except: logger.debug("ERROR: " + system + " is not in DNS") sys.exit("ERROR: " + system + " is not in DNS") if system.lower() != socket.getfqdn(system): logger.debug(system + ".<something> should be in DNS, please enter FQDN, thanks.") sys.exit(system + ".<something> should be in DNS, please enter FQDN, thanks.") def iscsi_server_ip_check(): """ Check validity if IPv4 for iSCSI server """ try: if ipaddress.ip_address(iscsilun.split(":")[0]): pass except: logger.debug(iscsilun.split(":")[0] + " is not valid iSCSI server IPv4") sys.exit(iscsilun.split(":")[0] + " is not valid iSCSI server IPv4") def dhcp_reservation_linux(): """ Create DHCP reservation for Linux """ print("Please enter DSEE LDAP password.") dsee_passwd = getpass.getpass() client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=nsh_dhcp, username=i_am, password=dsee_passwd) try: print("Run DHCP reservation for Linux for " + system + " " + mac) logger.debug("Run DHCP reservation for Linux for " + system + " " + mac) stdin, stdout, stderr = client.exec_command('sudo -S ca-dhcp-reserve.py -l -s %s -m %s' % (system, mac)) stdin.write(dsee_passwd +'\n') stdin.flush() if stdout.channel.recv_exit_status() != 0: logger.debug("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") sys.exit("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") stdout=stdout.readlines() # this is list #for line in stdout: # if you want to print what's in list # print(line) print(' '.join(stdout)) # print list, shorter logger.debug(' '.join(stdout)) except paramiko.ssh_exception.SSHException as ssherr: logger.debug("SSHException: {0}".format(ssherr)) sys.exit("SSHException: {0}".format(ssherr)) except paramiko.ssh_exception.ChannelException as cherr: logger.debug("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) sys.exit("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) except paramiko.ssh_exception.NoValidConnectionsError as err: logger.debug("NoValidConnectionsError: {0}".format(err)) sys.exit("NoValidConnectionsError: {0}".format(err)) except paramiko.ssh_exception.PartialAuthentication as parerr: logger.debug("PartialAuthentication: {0}".format(parerr)) sys.exit("PartialAuthentication: {0}".format(parerr)) close() def dhcp_reservation_ipxe_install(): """ Create DHCP reservation for iSCSI install """ print("Please enter DSEE LDAP password.") dsee_passwd = getpass.getpass() client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=nsh_dhcp, username=i_am, password=dsee_passwd) try: print("Run DHCP reservation for iSCSI install for " + system + " " + mac) logger.debug("Run DHCP reservation for iSCSI install for " + system + " " + mac) stdin, stdout, stderr = client.exec_command('sudo -S ca-dhcp-reserve.py --ipxe_install -s %s -m %s' % (system, mac)) stdin.write(dsee_passwd +'\n') stdin.flush() if stdout.channel.recv_exit_status() != 0: logger.debug("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") sys.exit("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") stdout=stdout.readlines() # this is list #for line in stdout: # if you want to print what's in list # print(line) print(' '.join(stdout)) # print list, shorter logger.debug(' '.join(stdout)) except paramiko.ssh_exception.SSHException as ssherr: logger.debug("SSHException: {0}".format(ssherr)) sys.exit("SSHException: {0}".format(ssherr)) except paramiko.ssh_exception.ChannelException as cherr: logger.debug("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) sys.exit("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) except paramiko.ssh_exception.NoValidConnectionsError as err: logger.debug("NoValidConnectionsError: {0}".format(err)) sys.exit("NoValidConnectionsError: {0}".format(err)) except paramiko.ssh_exception.PartialAuthentication as parerr: logger.debug("PartialAuthentication: {0}".format(parerr)) sys.exit("PartialAuthentication: {0}".format(parerr)) close() def create_tftpboot_gpxe_clients_file(): """ In Cobbler server, create file /tftpboot/gpxe/clients/fqdn The file is used for iPXE boot from ISCSI LUN """ logger.debug("Create in Cobbler /tftpboot/gpxe/clients/" + system + " using " + iscsilun) if os.path.exists("/tftpboot/gpxe/clients/" + system): logger.debug("Removing older /tftpboot/gpxe/clients/" + system ) try: subprocess.check_output(["rm -f /tftpboot/gpxe/clients/%s" % system], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) try: f = open("/tftpboot/gpxe/clients/" + system, "w+") f.write("#!gpxe \n") f.write("dhcp || echo \"Cannot configure interface via DHCP\" \n") f.write("echo Starting " + system + " \n") f.write("echo Boot filename is ${filename} \n") f.write("sanboot iscsi:" + iscsilun + " || echo \"Command sanboot fails\" \n") f.write("boot || echo \"Cannot download and boot an executable image\" \n") f.close() except IOError as err: logger.debug("IOError: {0}".format(err)) sys.exit("IOError: {0}".format(err)) def get_profile_ks(): """ Get profile's kickstart file, The 'network' for a system will be added to this profile's ks file """ try: b_profile_ks = subprocess.check_output(["sudo cobbler profile report --name=%s | \ grep -w Kickstart | awk '{print $3}'" % dict_profiles[profile]], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) global s_profile_ks s_profile_ks = b_profile_ks.decode('ASCII').replace("\n", "").replace(":", "").strip() # convert byte into string logger.debug("Profile ks: " + s_profile_ks) def system_in_cobbler(): """ Check if system is in Cobbler? """ try: cobblersystem = subprocess.check_output(["sudo cobbler system find --name=%s" % system], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) # see below howto convert bytes (cobblersystem) into string if str(cobblersystem.strip(), 'utf-8') == system: logger.debug(system + " exists in Cobbler, remove it first with *cobbler system remove*, then run this again.") sys.exit(system + " exists in Cobbler, remove it first with *cobbler system remove*, then run this again.") logger.debug("OK: " + system + " is not in Cobbler, continue with adding it.") def add_system_to_cobbler(): """ This uses "cobbler system add" to add system to Cobbler """ print("Adding " + system + " to Cobbler with profile " + dict_profiles[profile]) logger.debug("Adding " + system + " to Cobbler with profile " + dict_profiles[profile]) this_cobbler = socket.gethostname() try: subprocess.call(['sudo cobbler system add --name=%s --mac=%s --profile=%s \ --interface=eth0 --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, mac, dict_profiles[profile], this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) def add_vnc_into_profile_ks(): """ Add vnc to profile ks, this is for iSCSI install, and associate this new ks to a system """ this_cobbler = socket.gethostname() logger.debug("Adding vnc to profile " + dict_profiles[profile] + " ks file, for iPXE install") try: global profile_vnc_ks profile_vnc_ks = ("/var/lib/cobbler/kickstarts/users-tmp/" + dict_profiles[profile] + "_vnc.ks") if os.path.exists(profile_vnc_ks): logger.debug("Removing old " + profile_vnc_ks) try: subprocess.check_output(["rm -f %s" % profile_vnc_ks], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) open(profile_vnc_ks, 'w').write("vnc \n" + open(s_profile_ks, 'r').read()) except IOError as err: logger.debug("IOError: {0}".format(err)) sys.exit("IOError: {0}".format(err)) logger.debug("Associating " + profile_vnc_ks + " with " + system ) try: subprocess.call(['sudo cobbler system edit --name=%s \ --kickstart=\"%s\" \ --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, profile_vnc_ks, this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) def create_system_ks_with_network_info(profileks): """ Add network informaiton for new system's ks file, by adding 'network' line to profile's ks file. System's netmask and gateway is calculated from DHCP server. Argument: profile's ks file """ # first access subnet's URL and get data try: logger.debug("Getting subnet data from " + url_subnets) subnet_page = requests.get(url_subnets) #print("Return status code is " + str(subnet_page.status_code) + " \n") #print("Content is " + subnet_page.text + " \n") #print("Content is " + str(subnet_page.content) + " \n") except ConnectionError as con_err: logger.debug("Connection Error: {0}".format(con_err)) # no exit in case of error except Exception as err: logger.debug("Other Error: {0}".format(err)) tree = html.fromstring(subnet_page.content) # create tree, later we use xpath to locate tree's elements #example: <a href="10_132_21_0.24">10_132_21_0.24</a></td><td align="right"> # help: https://www.w3schools.com/xml/xpath_syntax.asp #subnets = tree.xpath('//td') #list of table rows #subnets = tree.xpath('//td') #list of table cells #subnets = tree.xpath('//a[@href="10_134_3_0.24"]/text()') #list of element with attribut href="10_134_3_0.24" #subnets = tree.xpath('//a') #list of a element full_list = tree.xpath('//a[@href]/text()') #list of a element with attribut href logger.debug("Collected subnet data: " + str(full_list)) # example of full_list is : # ['Name', 'Last modified', 'Size', 'Description', 'Parent Directory', '1', '10_132_21_0.24'] # need to filter only subnets, by looking that 1st character is digit global netmask global router for member in full_list: if member[0].isdigit(): if netaddr.IPAddress(ip) in netaddr.IPNetwork(member.replace(".", "/").replace("_", ".")): logger.debug(ip + " is in subnet " + member.replace(".", "/").replace("_", ".")) # we found in what subnet is system logger.debug("Getting " + member + " data from " + url_subnets + member) correctsubnet = requests.get(url_subnets + member) # now get netmask and router from subnet's config file netmaskindex = correctsubnet.text.split().index('netmask') netmask = re.sub(r"[;{},]", "", correctsubnet.text.split()[netmaskindex + 1]) # remove ;{}, logger.debug("System's netmask: " + netmask) routerindex = correctsubnet.text.split().index('routers') router = re.sub(r"[;{},]", "", correctsubnet.text.split()[routerindex + 1]) logger.debug("System's router: " + router) global netline netline = "network --device %s --bootproto static --ip %s --netmask %s --gateway %s --nameserver %s --hostname %s \n" \ % (mac, ip, netmask, router, nsh_dns, system) logger.debug("Adding to ks file: " + netline) try: global system_ks system_ks = ("/var/lib/cobbler/kickstarts/users-tmp/" + system + ".ks") if os.path.exists(system_ks): logger.debug("Removing older " + system_ks) try: subprocess.check_output(["rm -f %s" % system_ks], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) # add netline to profile's ks to create new system's ks file open(system_ks, 'w').write(netline + "\n" + open(profileks, 'r').read()) except IOError as err: logger.debug("IOError: {0}".format(err)) sys.exit("IOError: {0}".format(err)) def add_ks_to_system(): """ Add ks with network info to a system, hence profile's ks is overwritten """ this_cobbler = socket.gethostname() logger.debug("Add ks with network info as system's ks file") try: subprocess.call(['sudo cobbler system edit --name=%s \ --kickstart=\"/var/lib/cobbler/kickstarts/users-tmp/%s.ks\" \ --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, system, this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) def add_user_provided_ks_to_system(): """ Add user provided ks to a system """ this_cobbler = socket.gethostname() logger.debug("Add user's provided ks as system's ks file") if not Path(args.kickstart).is_file(): print("WARNING: file " + args.kickstart + " doesn't exist. Never mind, a profile's ks file will be used instead.") logger.debug("WARNING: file " + args.kickstart + " doesn't exist. Never mind, a profile's ks file will be used instead.") else: try: subprocess.call(['sudo cobbler system edit --name=%s \ --kickstart=\"%s\" \ --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, args.kickstart, this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) if __name__ == '__main__': dns_check() get_profile_ks() if iscsilun: iscsi_server_ip_check() system_in_cobbler() add_system_to_cobbler() dhcp_reservation_ipxe_install() create_tftpboot_gpxe_clients_file() if args.kickstart: add_user_provided_ks_to_system() else: add_vnc_into_profile_ks() create_system_ks_with_network_info(profile_vnc_ks) add_ks_to_system() else: system_in_cobbler() add_system_to_cobbler() dhcp_reservation_linux() if args.kickstart: add_user_provided_ks_to_system() else: create_system_ks_with_network_info(s_profile_ks) add_ks_to_system() logger.debug("FINISH AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime())) sys.exit(0)

Switch between *iPXE install* and *iPXE boot from iSCSI LUN*

 

#!/opt/satools/bin/python3.5 # ---------------- NSH ------------------------------------ # Switch Cobbler system between: # iPXE install to iPXE boot from iSCSI LUN # and vice versa # It's done on DHCP server that supports this Cobbler server. # # This is site specific script: # 1. variable dhcp_server is specific for each site # ----------------------------------------------------- import os import argparse import logging, logging.handlers from time import gmtime, strftime import datetime from pathlib import Path import socket import netaddr import sys import subprocess import paramiko import getpass import pwd # ------------- # variables # ------------ dhcp_server = "ca-dhcp-nsh.dom.com" # # logging destination PROGRAM = os.path.basename(sys.argv[0]) # name of this script LOG_PATH = ("/var/log/" + PROGRAM) # put together "/var/log/" if not os.path.exists(LOG_PATH): # create LOG_PATH if doesn't exists os.makedirs(LOG_PATH) os.chown(LOG_PATH,-1,10) # root(-1 means no change):staff DSEE group os.chmod(LOG_PATH,0o775) # drwxrwxr-x def verify_mac(hwaddr): """ To be used as type for MAC argument """ if not netaddr.valid_mac(hwaddr): sys.exit("%s is not valid MAC" % hwaddr) return hwaddr parser = argparse.ArgumentParser( description="Switch Cobbler system, iPXE install <-> iPXE boot from iSCSI LUN", epilog='Brought to you by ZD') parser.add_argument("-s", "--system", help="A system to be installed via Cobbler", required=True) parser.add_argument("-m", "--mac", help="System's MAC (: as delimiter)", type=verify_mac, required=True) ipxe = parser.add_mutually_exclusive_group(required=True) ipxe.add_argument("--install2boot", help="Switch from *iPXE install* to *iPXE boot from iSCSI LUN*", action="store_true") ipxe.add_argument("--boot2install", help="Switch from *iPXE boot from iSCSI LUN* to *iPXE install*", action="store_true") args = parser.parse_args() system=args.system mac=args.mac install2boot=args.install2boot boot2install=args.boot2install # --- Define logging LOG_FILE = (LOG_PATH + "/" + system + "_" + datetime.datetime.now().strftime("%m-%d-%Y_%Hh%Mm%Ss")) # create logger (interface that script uses for logging) logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # create file handler (define log destination) handler = logging.handlers.TimedRotatingFileHandler(LOG_FILE, when='MIDNIGHT', backupCount=50, utc=False) # create formatter (define layout of logs) formatter = logging.Formatter('%(asctime)s:%(levelname)s: %(message)s') handler.setFormatter(formatter) # add handler to logger logger.addHandler(handler) logger.debug("") logger.debug("START AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime())) i_am=getpass.getuser() if i_am == "root": logger.debug("Root cannot run this, please run it as yourself") sys.exit("Root cannot run this, please run it as yourself") else: logger.debug("Running by " + i_am) def dns_check(): """ Check if system is registered in DNS """ global ip # need ip for other functions try: ip = socket.gethostbyname(system) except: logger.debug("ERROR: " + system + " is not in DNS") sys.exit("ERROR: " + system + " is not in DNS") if system != socket.getfqdn(system): logger.debug(system + ".<something> should be in DNS, please enter FQDN, thanks.") sys.exit(system + ".<something> should be in DNS, please enter FQDN, thanks.") logger.debug("OK: " + system + " is in DNS.") def system_in_cobbler(): """ Check if system is in Cobbler? """ try: cobblersystem = subprocess.check_output(["sudo cobbler system find --name=%s" % system], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) # see below howto convert bytes (cobblersystem) into string if str(cobblersystem.strip(), 'utf-8') != system: logger.debug(system + " does not exist in Cobbler, it has to be added first. ") sys.exit(system + " does not exist in Cobbler, it has to be added first. ") logger.debug("OK: " + system + " is in Cobbler.") def disable_cobbler_netboot(): """ Disable net boot in Cobbler """ try: cobblersystem = subprocess.check_output(["sudo cobbler system edit --name=%s --netboot-enabled=false" % system], shell=True) except subprocess.SubprocessError as err: logger.debug("Cannot disable netboot for " + system + "\n SubprocessError: {0}".format(err)) sys.exit("Cannot disable netboot for " + system + "\n SubprocessError: {0}".format(err)) logger.debug("OK: " + system + " has disabled net boot in Cobbler") def enable_cobbler_netboot(): """ Enable net boot in Cobbler """ try: cobblersystem = subprocess.check_output(["sudo cobbler system edit --name=%s --netboot-enabled=true" % system], shell=True) except subprocess.SubprocessError as err: logger.debug("Cannot enable netboot for " + system + "\n SubprocessError: {0}".format(err)) sys.exit("Cannot enable netboot for " + system + "\n SubprocessError: {0}".format(err)) logger.debug("OK: " + system + " has enabled net boot in Cobbler") def dhcp_reservation_ipxeboot(): """ Do reservation on remote DHCP server, this is for iPXE from iSCSI LUN """ print("Please enter DSEE LDAP password.") dsee_passwd = getpass.getpass() client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=dhcp_server, username=i_am, password=dsee_passwd) try: print("Configuring DHCP reservation for iPXE boot from iSCSI LUN for " + system + " " + mac) logger.debug("Configuring DHCP reservation for iPXE boot from iSCSI LUN for " + system + " " + mac) stdin, stdout, stderr = client.exec_command('sudo -S ca-dhcp-reserve.py --ipxe_boot -s %s -m %s' % (system, mac)) stdin.write(dsee_passwd +'\n') stdin.flush() if stdout.channel.recv_exit_status() != 0: logger.debug("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") sys.exit("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") stdout=stdout.readlines() # this is list #for line in stdout: # if you want to print what's in list # print(line) print(' '.join(stdout)) # print list, shorter logger.debug(' '.join(stdout)) except paramiko.ssh_exception.SSHException as ssherr: logger.debug("SSHException: {0}".format(ssherr)) sys.exit("SSHException: {0}".format(ssherr)) except paramiko.ssh_exception.ChannelException as cherr: logger.debug("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) sys.exit("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) except paramiko.ssh_exception.NoValidConnectionsError as err: logger.debug("NoValidConnectionsError: {0}".format(err)) sys.exit("NoValidConnectionsError: {0}".format(err)) except paramiko.ssh_exception.PartialAuthentication as parerr: logger.debug("PartialAuthentication: {0}".format(parerr)) sys.exit("PartialAuthentication: {0}".format(parerr)) close() def dhcp_reservation_ipxeinstall(): """ Do reservation on remote DHCP server, this is for iPXE installation """ print("Please enter DSEE LDAP password.") dsee_passwd = getpass.getpass() client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=dhcp_server, username=i_am, password=dsee_passwd) try: print("Configuring DHCP reservation for iPXE installation for " + system + " " + mac) logger.debug("Configuring DHCP reservation for iPXE installation for " + system + " " + mac) stdin, stdout, stderr = client.exec_command('sudo -S ca-dhcp-reserve.py --ipxe_install -s %s -m %s' % (system, mac)) stdin.write(dsee_passwd +'\n') stdin.flush() if stdout.channel.recv_exit_status() != 0: logger.debug("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") sys.exit("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") stdout=stdout.readlines() # this is list #for line in stdout: # if you want to print what's in list # print(line) print(' '.join(stdout)) # print list, shorter logger.debug(' '.join(stdout)) except paramiko.ssh_exception.SSHException as ssherr: logger.debug("SSHException: {0}".format(ssherr)) sys.exit("SSHException: {0}".format(ssherr)) except paramiko.ssh_exception.ChannelException as cherr: logger.debug("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) sys.exit("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) except paramiko.ssh_exception.NoValidConnectionsError as err: logger.debug("NoValidConnectionsError: {0}".format(err)) sys.exit("NoValidConnectionsError: {0}".format(err)) except paramiko.ssh_exception.PartialAuthentication as parerr: logger.debug("PartialAuthentication: {0}".format(parerr)) sys.exit("PartialAuthentication: {0}".format(parerr)) close() if __name__ == '__main__': dns_check() system_in_cobbler() if install2boot: dhcp_reservation_ipxeboot() disable_cobbler_netboot() elif boot2install: dhcp_reservation_ipxeinstall() enable_cobbler_netboot() logger.debug("FINISH AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime())) sys.exit(0)

Back to the main page