Created
July 6, 2017 21:06
-
-
Save Wildcarde/8ef30cea397a02256074e607674bcd65 to your computer and use it in GitHub Desktop.
Small script to add entire idrac to cobbler in one step
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
#!/usr/bin/env python | |
""" | |
Functionally a rewrite of the CSES tool 'add_chassis_nodes(.sh)' however the approach | |
is slightly different. This script will add nodes to cobbler using the absolute minimum | |
information available. It automates the cobbler command line instead of using the API as | |
the developers have deemed the api to be unsafe in it's present state. It could be adapted to | |
do XMLRPC as well but is currently assumed to be run on the cobbler server itself so there is | |
no need. Some frameworking is done to make it possible to use this file as a component in a bigger | |
library, but as of now this approach to using the code is untested. | |
Reference idrac output: | |
Security Alert: Certificate is invalid - self signed certificate | |
Continuing execution. Use -S option for racadm to stop execution on certificate-related errors. | |
<Name> <Presence> <BMC MAC Address> <NIC1 MAC Address> <NIC2 MAC Address> | |
CMC Present N/A B0:83:FE:E2:5F:96 N/A | |
Server-1 Present 4C:76:25:34:22:C0 4C:76:25:34:22:C1 4C:76:25:34:22:C3 | |
Server-2 Present 4C:76:25:34:22:CD 4C:76:25:34:22:CE 4C:76:25:34:22:D0 | |
Server-3 Present 4C:76:25:34:22:DA 4C:76:25:34:22:DB 4C:76:25:34:22:DD | |
Server-4 Present 4C:76:25:34:22:E7 4C:76:25:34:22:E8 4C:76:25:34:22:EA | |
Server-5 Present 4C:76:25:34:22:F4 4C:76:25:34:22:F5 4C:76:25:34:22:F7 | |
Server-6 Present 4C:76:25:34:23:01 4C:76:25:34:23:02 4C:76:25:34:23:04 | |
Server-7 Present 4C:76:25:34:23:0E 4C:76:25:34:23:0F 4C:76:25:34:23:11 | |
Server-8 Present 4C:76:25:34:23:1B 4C:76:25:34:23:1C 4C:76:25:34:23:1E | |
Server-9 Present 4C:76:25:34:23:28 4C:76:25:34:23:29 4C:76:25:34:23:2B | |
Server-10 Present 4C:76:25:34:23:35 4C:76:25:34:23:36 4C:76:25:34:23:38 | |
Server-11 Present 4C:76:25:34:23:42 4C:76:25:34:23:43 4C:76:25:34:23:45 | |
Server-12 Present 4C:76:25:34:23:4F 4C:76:25:34:23:50 4C:76:25:34:23:52 | |
Server-13 Present 4C:76:25:34:23:5C 4C:76:25:34:23:5D 4C:76:25:34:23:5F | |
Server-14 Present 4C:76:25:34:23:69 4C:76:25:34:23:6A 4C:76:25:34:23:6C | |
Server-15 Present 4C:76:25:34:23:76 4C:76:25:34:23:77 4C:76:25:34:23:79 | |
Server-16 Present 4C:76:25:34:23:83 4C:76:25:34:23:84 4C:76:25:34:23:86 | |
Switch-1 Present Not Installed F8:B1:56:65:B7:90 Not Installed | |
Switch-2 Not Present Not Installed Not Installed Not Installed | |
Switch-3 Present Not Installed 00:00:00:00:00:00 Not Installed | |
Switch-4 Not Present Not Installed Not Installed Not Installed | |
Switch-5 Not Present Not Installed Not Installed Not Installed | |
Switch-6 Not Present Not Installed Not Installed Not Installed | |
""" | |
import logging | |
import argparse | |
import os | |
import subprocess as sp | |
import collections | |
import sys | |
logging.getLogger('idracCobblerImport').addHandler(logging.NullHandler()) | |
#I don't think this is necessary | |
#ServerEntry = namedtuple("ServerEntry","idracmac idracip eth0mac eth0ip") | |
""" | |
Usage menu and input handler. | |
""" | |
def inputparser(): | |
naminginfo="""Node naming: \n | |
If chassis and rack numbers are not provided cluster nodes will be named as follows: \n | |
<Cluster_Name>-n<nodenumber> Eg. rock-n3 \n | |
Supplying -c will add: \n | |
<Cluster_Name>-c<chassis_num>-n<nodenumber> Eg. rock-c0-n3 \n | |
Supplying -r will add: \n | |
<Cluster_Name>-r<racknum>-n<nodenumber> Eg. rock-r22-n3 \n | |
Supplying -r and -c will add: \n | |
<Cluster_Name>-r<racknum>-c<chassisnum>-n<nodenumber> Eg. rock-r22-c0-n3 \n | |
""" | |
parser=argparse.ArgumentParser(prog='idrac_addchassis.py',usage='%(prog)s [options]', | |
description="""Add chassis of nodes to cobbler system by dumping via idrac admin commands. | |
Currently only configures eth0.""", | |
epilog=naminginfo) | |
parser.add_argument("idracip", | |
help="The IP of the iDrac Chassis Device you wish to use for adding machines to your cluster.", | |
action="store",type=str) | |
parser.add_argument("clustername", help="The name of the cluster you wish to add the chassis nodes to.", | |
action="store",type=str) | |
parser.add_argument("firstip", help="The IP you wish to give Node 1 in the Chassis, all other nodes will be incremented off this IP") | |
parser.add_argument("-c","--chassisnum", help="Add supplied chassis number to the node name", | |
default="", action="store", type=str) | |
parser.add_argument("-r","--racknum", help="Add supplied rack number to the node name",action="store",type=str) | |
parser.add_argument("-s","--subnet", help="Subnet you wish nodes to be added with. This defaults to 255.255.240.0.", | |
default="255.255.240.0",action="store",type=str) | |
parser.add_argument("-S","--serverlist", help="If you only want to import a subset of the chassis, provide a comma seperated list of the nodes you want.", | |
action="store",type=str) | |
parser.add_argument("-u","--idracuser",help="iDrac User required to access iDrac device. This defaults to root.", | |
action="store",type=str,default="root") | |
parser.add_argument("-p","--password", help="iDrac Password, this can be supplied here or interactively",action="store",type=str,default="") | |
parser.add_argument("-n","--dryrun",help="Do a dry run, this will output the changes to be made but won't actually issues the commands", | |
action="store_true") | |
parser.add_argument("-b","--netboot",help="Disable netboot for hosts. This defaults to enabled",action="store_false",default=True) | |
parser.add_argument("--cobblerloc", help="""Change the location of the cobbler command line tool. | |
This defaults to /usr/bin/cobbler""", | |
default="/usr/bin/cobbler",action="store",type=str) | |
parser.add_argument("--racadmloc", help="""Change location of racadm tool provided by dell open manager. | |
This defaults to /opt/dell/srvadmin/sbin/racadm""", | |
default="/opt/dell/srvadmin/sbin/racadm", action="store", type=str) | |
parser.add_argument("--static", help="Set interface as static", default=False,action="store_true") | |
parser.add_argument("--gateway", help="Set Gateway of primary interface, default is 10.2.144.1 (pni blue net default)", | |
type=str, default="10.2.144.1", action="store") | |
parser.add_argument("-P", "--profile", help="Set profile name for created hosts.", | |
default='spock-node',action="store",type=str) | |
#output manipulation tweaks | |
parser.add_argument("-v","--verbose", help="verbose output (currently unimplemented)", action="store_true") | |
parser.add_argument("-q","--quiet", help="silence output, simply return success or failure.", action="store_true") | |
parser.add_argument("--debug", help="Enable Debug Output", action="store_true") | |
return parser | |
""" | |
Retrieve Server List from iDrac | |
""" | |
def runracadm(racadmloc,idracuser,password,idracip): | |
logger = logging.getLogger('idracCobblerImport') | |
"""check_output generates an exception that will kill the process | |
if not captured""" | |
command="{} -r {} -u {} -p {} getmacaddress".format(racadmloc,idracip,idracuser,password) | |
#print command | |
logger.debug("racadm command run: " + command) | |
try: | |
racoutput = sp.check_output(command, shell=True) | |
logger.debug("racoutput: " + racoutput) | |
except sp.CalledProcessError as e: | |
logger.error("Racadm failed with return code: "+str(e.returncode)) | |
logger.debug("Racadm failed return output: " + e.output) | |
return None | |
return racoutput.splitlines() | |
""" | |
Unspool list of server entries | |
""" | |
def processIDrac(idrac_output): | |
logger = logging.getLogger('idracCobblerImport') | |
idrac_dict=collections.defaultdict(dict) | |
for line in idrac_output: | |
logger.debug("line: {}".format(line)) | |
#is there a server line on this line? | |
if(line.find("Server",0,10) != -1): | |
brokeline=line.split() #the below split is only valid for 'present' servers | |
logger.debug("brokeline: {}".format(brokeline)) | |
if not brokeline == [''] and brokeline[1]=="Present": | |
servnum=brokeline[0].split('-') #entry zero has 'server-x', we want x | |
idrac_dict[servnum[1]]['idracmac'] = brokeline[2] | |
idrac_dict[servnum[1]]['eth0mac'] = brokeline[3] #extracts the mac addresses from the line | |
return idrac_dict | |
""" | |
add node details | |
- name | |
- ip | |
""" | |
def addDetails(serverdict,args): | |
logger = logging.getLogger('idracCobblerImport') | |
ipbreak=args.firstip.split('.') | |
for server in serverdict: | |
calcip='.'.join(ipbreak[0:3])+'.'+str(int(ipbreak[3])+int(server)+-1) | |
logger.debug("calculated ip:" + calcip) | |
serverdict[server]['eth0ip']=calcip | |
name=[] | |
name.append(args.clustername) | |
if args.racknum: | |
name.append('r'+args.racknum) | |
if args.chassisnum: | |
name.append('c'+args.chassisnum) | |
name.append(server) | |
name='-'.join(name) | |
logger.debug("calculated name: " + name) | |
serverdict[server]['name']=name | |
return serverdict | |
""" | |
basic function to run the cobbler command. | |
""" | |
def createcobblersystems(serverdict,netboot,profile,cobblerloc,gateway,static,dryrun): | |
#static can't be passed thru directly must be translated to 0/1 | |
logger = logging.getLogger('idracCobblerImport') | |
statictest = lambda x: 1 if x else 0 | |
staticval = statictest(static) | |
for skey, server in serverdict.iteritems(): | |
command = """{} system add --name={} --profile={} --interface=em1 --netmask=255.255.240 \ | |
--mac-address={} --ip-address={} --hostname={} --netboot-enabled={} --gateway={} --static={} \ | |
""".format(cobblerloc, server['name'], profile, \ | |
server['eth0mac'],server['eth0ip'],server['name'],netboot, gateway, staticval) | |
#lambda should set static to 0 for false, 1 for true. <- Must Test | |
logger.debug("cobbler command:" + command) | |
if not dryrun: | |
output=sp.check_output(command, shell=True) | |
logger.debug("output of command" + output) | |
return | |
""" | |
main function | |
""" | |
def run(args=None): | |
parser = inputparser() | |
args = parser.parse_args() | |
# retrieve logger | |
logger = logging.getLogger('idracCobblerImport') | |
#create format info | |
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s %(lineno)d - %(message)s' | |
formatter = logging.Formatter(FORMAT) | |
#configure stdout based handler | |
shandler = logging.StreamHandler(sys.stdout) | |
shandler.setFormatter(formatter) | |
logger.setLevel(logging.INFO) | |
if args.debug: | |
logger.setLevel(logging.DEBUG) | |
#only add handling for output if the quiet flag isn't set | |
if not args.quiet: | |
logger.addHandler(shandler) | |
logger.debug("input args: {}".format(args)) | |
#check that required tools exist and quit out if they don't. | |
if not os.path.exists(args.cobblerloc): | |
logger.critical("Cobbler tool not found at provided location") | |
parser.print_help() | |
return; | |
if not os.path.exists(args.racadmloc): | |
logger.critical("racadm tool not found at provided location") | |
parser.print_help() | |
return; | |
if not args.password: | |
password=raw_input("iDrac Password: ") | |
else: | |
password=args.password | |
#Run racadm and | |
idrac_output = runracadm(args.racadmloc,args.idracuser,password,args.idracip) | |
if idrac_output == None: | |
logger.debug("iDrac output == None") | |
return; | |
#process output of racadm request if return isn't bad, produce base dictionary | |
serverdict = processIDrac(idrac_output) | |
logger.debug("serverdict: {}".format(serverdict)) | |
#add ips and subnets to dictionary | |
serverdict = addDetails(serverdict, args) | |
logger.debug("serverdict: {}".format(serverdict)) | |
#pass fully loaded dictionary to create script, added gateway and static info | |
createcobblersystems(serverdict,args.netboot,args.profile,args.cobblerloc,args.gateway,args.static,args.dryrun) | |
return 0 | |
if __name__ == '__main__': | |
sys.exit(run()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment