Skip to content

Instantly share code, notes, and snippets.

@Wildcarde
Created July 6, 2017 21:06
Show Gist options
  • Save Wildcarde/8ef30cea397a02256074e607674bcd65 to your computer and use it in GitHub Desktop.
Save Wildcarde/8ef30cea397a02256074e607674bcd65 to your computer and use it in GitHub Desktop.
Small script to add entire idrac to cobbler in one step
#!/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