Changeset 133 for trunk/pxeconfig.in
- Timestamp:
- 04/14/09 15:58:42 (15 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/pxeconfig.in
r132 r133 30 30 # 31 31 """ 32 Usage: pxeconfig 33 34 -V,--version 35 [-r,--remove] -i,--interactive 36 [-r,--remove] -f,--file <filename> <hostname(s)> 37 [-r,--remove] -n,--net <C-net> -s,--start <number> -e,--end <number> -f,--file <filename> 38 39 There are several ways to create a range of hostnames. They all start with: 40 * -b,--basename <string> 41 42 The different formats are: 43 1 [opt] -H,--host-range <number>-<number> -f,--file <filename> 44 2 [opt] -s,--start <number> -e,--end <number> -f,--file <filename> 45 3 [opt] -R,--rack <string>-<string> -N,--node <string>-<string> -f,--file <filename> 46 47 opt: 48 -r,--remove 49 -w,--equal-width 50 --dry-run 51 52 If leading zeros are used for <number>, then host names will be padded 53 with zeros. --equal-width option will be enabled. 32 Usage: pxeconfig [-f,--filename <name>] <hosts> 33 34 Specifying hostnames: 35 To specify a range use the [] to indicate a range, 36 a couple of examples herebelow. 37 38 The first five nodes of rack 16 39 - gb-r16n[1-5] 40 41 The first five nodes and node 12 and 18 of rack 16 to 20 42 - gb-r[16-20]n[1-5,12,18] 43 44 The first five nodes de in rack 16 with a padding enabled 45 - gb-r[16]n[01-5] 46 47 The ranges ([]) are not only limited to numbers, letters can also be used. 54 48 55 49 With this program you can configure which PXE configuration file a node 56 will use when it boots. The program can be started interactivly or via 57 command line options. 50 will use when it boots. 58 51 59 52 See following link for usage and examples: … … 73 66 from sara import AdvancedParser 74 67 75 # Global Variables76 #77 DEBUG = False78 VERBOSE = False79 DRY_RUN = False80 81 68 # Constants 82 69 # 83 PXE_CONF_DIR='/tftpboot/pxelinux.cfg' 84 NETWORK='network' 85 BASENAME='basename' 86 FILENAME='filename' 87 START='start' 88 END='end' 89 REMOVE='remove' 90 RACK='rack' 91 NODE='node' 92 INTERACTIVE='interactive' 93 EQUALWIDTH='equalwidth' 94 VERSION='2.0.0' 95 96 SHORTOPT_LIST='b:e:f:hin:s:rwvN:H:R:V' 97 LONGOPT_LIST=[ 'basename=', 'debug', 'dry-run', 'end=', 'equal-width', 98 'file=', 'help', 'host-range=', 'interactive', 'net=', 'node=', 99 'start=', 'rack=', 'remove', 'verbose', 'version', 'equal-width' 100 ] 101 102 # regex definition 103 # 104 # A regulare expression to parse string for rack/node 105 id_re = re.compile(r""" 106 (?P<basename>[a-zA-Z-_]*) 107 (?P<number>[0-9]+) 108 """, re.VERBOSE) 109 110 def verbose(str): 111 if VERBOSE: 112 print '%s' %str 70 PXE_CONF_DIR = '/tftpboot/pxelinux.cfg' 71 NETWORK = 'network' 72 START = 'start' 73 END = 'end' 74 VERSION = '3.0.0' 113 75 114 76 class PxeConfig(Exception): … … 119 81 def __repr__(self): 120 82 return self.msg 121 122 83 123 84 def ReadConfig(file): … … 148 109 149 110 def select_pxe_configfile(): 150 151 152 """ 153 154 os.chdir(PXE_CONF_DIR) 155 156 157 158 # 159 160 161 162 163 pass164 165 166 167 168 169 170 171 172 173 # sort the files174 175 111 """ 112 Let user choose which pxeconfig file to use. 113 """ 114 115 os.chdir(PXE_CONF_DIR) 116 117 # Try to determine to which file the default symlink points, and 118 # if it exists, remove it from the list. 119 # 120 try: 121 default_file = os.readlink('default') 122 except OSError: 123 default_file = None 124 pass 125 126 files = glob.glob('default.*') 127 if not files: 128 print 'There are no pxe config files starting with: default.' 129 sys.exit(1) 130 131 if default_file: 132 files.remove(default_file) 133 134 # sort the files 135 # 136 files.sort() 176 137 177 print 'Which pxe config file must we use: ?' 178 i = 1 179 for file in files: 180 print "%d : %s" %(i,file) 181 i = i +1 182 183 while 1: 184 index = raw_input('Select a number: ') 185 try: 186 index = int(index) 187 except ValueError: 188 index = len(files) + 1 189 190 # Is the user smart enough to select 191 # the right value?? 192 # 193 if 0 < index <= len(files): break 194 195 return files[index-1] 196 197 def manage_links(dict): 138 print 'Which pxe config file must we use: ?' 139 i = 1 140 for file in files: 141 print "%d : %s" %(i,file) 142 i = i + 1 143 144 while 1: 145 index = raw_input('Select a number: ') 146 try: 147 index = int(index) 148 except ValueError: 149 index = len(files) + 1 150 151 # Is the user smart enough to select 152 # the right value?? 153 # 154 if 0 < index <= len(files): 155 break 156 157 return files[index-1] 158 159 def manage_links(dict, options): 198 160 """ 199 161 Create the links in the PXE_CONF_DIR, … … 201 163 start number, end number 202 164 """ 203 str = 'manage_links'204 verbose(str)165 if options.VERBOSE: 166 print 'manage_links()' 205 167 206 168 os.chdir(PXE_CONF_DIR) 207 169 naddr = dict[NETWORK] 208 pxe_filename = dict[FILENAME]170 pxe_filename = options.filename 209 171 210 172 for i in range(dict[START], dict[END]+1): 211 173 haddr = '%s%02X' %(naddr, i) 212 213 if dict[REMOVE] == True:214 if DEBUG orDRY_RUN:174 175 if options.REMOVE: 176 if options.DEBUG or options.DRY_RUN: 215 177 print 'removing %s/%s' %(PXE_CONF_DIR, haddr) 216 178 217 if os.path.exists(haddr) and not DRY_RUN:179 if os.path.exists(haddr) and not options.DRY_RUN: 218 180 os.unlink(haddr) 219 181 220 182 else: 221 if DEBUG orDRY_RUN:183 if options.DEBUG or options.DRY_RUN: 222 184 print 'linking %s to %s' %(haddr, pxe_filename) 223 185 224 if not DRY_RUN:186 if not options.DRY_RUN: 225 187 if os.path.exists(haddr): 226 188 os.unlink(haddr) 227 189 os.symlink(pxe_filename, haddr) 228 190 229 def net_2_hex(net ):191 def net_2_hex(net, options): 230 192 """ 231 193 This function checks if the give network is a Class C-network and will 232 194 convert the network address to a hex address if true. 233 195 """ 234 str = 'net_2_hex : %s' %(net) 235 verbose(str) 196 if options.VERBOSE: 197 str = 'net_2_hex : %s' %(net) 198 print str 236 199 237 200 d = string.split(net, '.') … … 243 206 r = '' 244 207 for i in d: 245 i = check_number(i, True) 246 r = '%s%02X' %(r,i) 247 248 if DEBUG: 249 print 'C-network in hex: ', r 208 r = '%s%02X' %(r, int(i)) 209 210 if options.DEBUG: 211 print 'C-network converted to hex: ', r 250 212 251 213 return r 252 253 def check_number(number_str, network):254 """255 number : a string. If string starts with a zero (0) then256 EQUALWIDTH wil be set.257 network: if true then number must ben between 0 < number < 255258 else it must be a valid number.259 """260 try:261 n = int(number_str)262 except ValueError, detail:263 error = "%s : is not a valid number" %number_str264 raise PxeConfig, error265 266 if not network:267 return n268 269 # Check if it is a correct network value270 #271 if 0 <= n <= 255:272 return n273 else:274 error = '%s is not a valid network number, must be between 0 and 255' %n275 raise PxeConfig, error276 277 def interactive(binfo):278 print __doc__279 280 281 while 1:282 network = raw_input('Give network address (xxx.xxx.xxx): ')283 284 try:285 naddr = net_2_hex(network)286 break287 except PxeConfig, detail:288 print '%s : not a valid C-class network number' %(network)289 continue290 291 while 1:292 start = raw_input('Starting number: ')293 294 try:295 start = check_number(start, True)296 break297 except PxeConfig, detail:298 print detail299 continue300 301 while 1:302 end = raw_input('Ending number: ')303 304 try:305 end = check_number(end, True)306 break307 except PxeConfig, detail:308 print detail309 continue310 311 312 pxe_filename = select_pxe_configfile()313 314 binfo[NETWORK] = naddr315 binfo[START] = start316 binfo[END] = end317 binfo[FILENAME] = pxe_filename318 319 if DEBUG:320 print network, binfo321 322 manage_links(binfo)323 324 def check_args(binfo, hostnames):325 """326 Do you we have the right and propper values327 """328 ### check_filename329 #330 str = 'check_args: '331 verbose(str)332 try:333 if not os.path.isfile(os.path.join(PXE_CONF_DIR, binfo[FILENAME])):334 error = '%s: Filename does not exists' %binfo[FILENAME]335 raise Pxeconfig, detail336 except KeyError, detail:337 if binfo[REMOVE] :338 binfo[FILENAME] = 'Does not matter'339 else:340 binfo[FILENAME] = select_pxe_configfile()341 342 if hostnames:343 host_2_hex(hostnames, binfo)344 sys.exit(0)345 346 if binfo.has_key(BASENAME) and binfo.has_key(NETWORK):347 error = "The option -n/--net and -b/--basename are mutually exclusive"348 raise PxeConfig, error349 350 if binfo.has_key(BASENAME):351 if binfo[RACK] and binfo[NODE]:352 create_links = rack_2_net353 else:354 set_padding(binfo)355 create_links = base_2_host356 357 elif binfo.has_key(NETWORK):358 binfo[START] = check_number(binfo[START], True)359 binfo[END] = check_number(binfo[END], True)360 create_links = manage_links361 362 else:363 error = 'You have to specifiy -b,--basename or -n,--net'364 raise PxeConfig, error365 366 if DEBUG:367 print binfo368 369 create_links(binfo)370 371 def set_padding(binfo):372 """373 binfo : boot info374 network_number : must we check if start,end values are375 valid network numbers376 return:377 - if equal_width is requested then the length will be set to end value378 - if start value length > 1 and start with a zero (0), width is set to379 the end value380 - if end value starts with a zero (0), width will be set to the end381 value382 """383 start_str = binfo[START]384 end_str = binfo[END]385 386 start = check_number(start_str, False)387 end = check_number(end_str, False)388 389 if binfo[EQUALWIDTH][0] == True:390 binfo[EQUALWIDTH][1] = len(end_str)391 392 elif len(start_str) > 1 and start_str[0] == '0':393 binfo[EQUALWIDTH] = [ True, len(end_str) ]394 395 elif end_str[0] == '0':396 binfo[EQUALWIDTH] = [ True, len(end_str) ]397 398 binfo[START] = start399 binfo[END] = end400 401 402 def parse_number_range(binfo, arg):403 """404 Parse if arg is of format <digit-digit>, if it starts405 with a zero (0) then set EQUALWIDTH406 """407 str = 'parse_hostrange %s' %(arg)408 verbose(str)409 410 l = arg.split('-')411 if len(l) < 2:412 error = 'hostrange syntax not valid: %s (number-number)' %(arg)413 raise PxeConfig, error414 415 binfo[START] = l[0]416 binfo[END] = l[1]417 418 def parse_string_range(binfo, arg, id):419 """420 Parse if arg is of format <(alpha)(digit)>-(alpha)(digit)>421 if digit starts with a zero (0) then set EQUALWIDTH422 """423 str = 'parse_string_range: %s %s' %(arg, id)424 verbose(str)425 426 l = arg.split('-')427 if len(l) < 2:428 error = '%s : range syntax not valid,eg <string>-<string>)' %(arg)429 raise PxeConfig, error430 431 binfo[id] = dict()432 binfo[id][EQUALWIDTH] = [False, 0]433 434 i = 0435 for item in l:436 result = id_re.match(item)437 438 if result:439 basename = result.group('basename')440 number = result.group('number')441 442 if DEBUG:443 print 'basename = %s, number = %s' %(basename, number)444 445 if i == 0:446 binfo[id][BASENAME] = basename447 binfo[id][START] = number448 i += 1449 else:450 binfo[id][END] = number451 452 else:453 error = '%s : string syntax is not valid, eg: <alpa>*<digit>+' %(item)454 raise PxeConfig, error455 456 set_padding(binfo[id])457 214 458 215 def add_options(p): … … 468 225 ) 469 226 227 p.add_option('-n', '--dry-run', 228 action = 'store_true', 229 dest = 'DRY_RUN', 230 help = 'Do not execute any command' 231 ) 232 470 233 p.add_option('-f', '--filename', 471 234 action = 'store', … … 480 243 ) 481 244 245 p.add_option('-v', '--verbose', 246 action = 'store_true', 247 dest = 'VERBOSE', 248 help = 'Make the program more verbose' 249 ) 250 482 251 p.add_option('-V', '--version', 483 252 action = 'store_true', 484 253 dest = 'VERSION', 485 help = 'Removes the PXE filename for a host(s)' 486 ) 487 488 489 def new_parser(argv): 490 """ 491 Make use of sara advance parser module 492 """ 493 parser = AdvancedParser.AdvancedParser(version='Version: 3.0.0', usage=__doc__) 254 help = 'Print the program version number and exit' 255 ) 256 257 def parser(argv): 258 """ 259 Make use of sara advance parser module. It can handle regex in hostnames 260 """ 261 parser = AdvancedParser.AdvancedParser(usage=__doc__) 494 262 add_options(parser) 495 263 496 options, args = parser.parse_args() 497 return (options, args) 498 499 500 def parse_args(argv, binfo): 501 """ 502 This function parses the command line options and returns the rest as 503 an list of hostnames: 504 argv : a list of command line options. 505 binfo : returning a dict with the netinfo. if used non-interactively 506 hostnames: the rest of the command lines options that are not-parseble. 507 """ 508 try: 509 opts, args = getopt.gnu_getopt(argv[1:], SHORTOPT_LIST, LONGOPT_LIST) 510 except getopt.error, detail: 511 print __doc__ 512 print detail 513 sys.exit(1) 514 515 global DEBUG 516 global VERBOSE 517 global DRY_RUN 518 519 # if nothing is specified then print usage and exit 520 # 521 if not opts and not args: 522 print __doc__ 523 sys.exit(1) 524 525 # init vars 526 # 527 hostrange = node = rack = None 528 529 # Check given options 530 # 531 for opt,value in opts: 532 533 if opt in ['-b', '--basename']: 534 binfo[BASENAME] = value 535 536 elif opt in ['--debug']: 537 DEBUG = True 538 539 elif opt in ['--dry-run']: 540 DRY_RUN = True 541 542 elif opt in ['-e', '--end']: 543 binfo[END] = value 544 545 elif opt in ['-f', '--file']: 546 binfo[FILENAME] = value 547 548 elif opt in ['-h', '--help']: 549 print __doc__ 550 sys.exit(0) 551 552 elif opt in ['-i', '--interactive']: 553 interactive(binfo) 554 sys.exit(0) 555 556 elif opt in ['-n', '--net']: 557 binfo[NETWORK] = net_2_hex(value) 558 559 elif opt in ['-r', '--remove']: 560 binfo[REMOVE] = 1 561 562 elif opt in ['-s', '--start']: 563 binfo[START] = value 564 565 elif opt in ['-w', '--equal-width']: 566 binfo[EQUALWIDTH] = [True, 0] 567 568 elif opt in ['-v', '--verbose']: 569 VERBOSE = True 570 571 elif opt in ['-H', '--host-range']: 572 hostrange = value 573 574 elif opt in ['-N', '--node']: 575 node = value 576 577 elif opt in ['-R', '--rack']: 578 rack = value 579 580 elif opt in ['-V', '--version']: 581 print VERSION 582 sys.exit(0) 583 584 if node and rack: 585 parse_string_range(binfo, node, NODE) 586 parse_string_range(binfo, rack, RACK) 587 elif hostrange: 588 parse_number_range(binfo, hostrange) 589 590 591 check_args(binfo, args) 592 593 def host_2_hex(hosts, binfo): 264 options, args = parser.parse_args() 265 266 if options.VERSION: 267 print VERSION 268 sys.exit(0) 269 270 if options.filename: 271 if not os.path.isfile(os.path.join(PXE_CONF_DIR, options.filename)): 272 error = '%s: Filename does not exists' %(options.filename) 273 print error 274 sys.exit(1) 275 else: 276 options.filename = select_pxe_configfile() 277 278 if options.DEBUG: 279 print args, options 280 281 hosts_2_hex(args, options) 282 283 def hosts_2_hex(hosts, options): 594 284 """ 595 285 Convert hostname(s) to a net address that can be handled by manage_links function 596 286 """ 597 str = 'host_2_hex: %s' %hosts 598 verbose(str) 287 if options.VERBOSE: 288 str = 'host_2_hex: %s' %hosts 289 print str 599 290 600 291 for host in hosts: … … 603 294 except socket.error,detail: 604 295 error = '%s not an valid hostname: %s' %(host,detail) 605 raise PxeConfig, error 606 296 print error 297 sys.exit(1) 298 607 299 net = string.splitfields(addr, '.') 608 300 cnet = string.joinfields(net[0:3], '.') 609 301 610 binfo[NETWORK] = net_2_hex(cnet) 302 binfo = dict() 303 binfo[NETWORK] = net_2_hex(cnet, options) 611 304 binfo[START] = int(net[3]) 612 305 binfo[END] = int(net[3]) 613 manage_links(binfo) 614 615 def base_2_host(binfo): 616 """ 617 Construct hostname(s) from the supplied basename and start and end numbers 618 """ 619 str = 'base_2_host' 620 verbose(str) 621 622 start = binfo[START] 623 end = binfo[END] 624 625 if start > end: 626 error = '%d >= %d : start value is greater then end value' %(start, end) 627 raise PxeConfig, error 628 629 hostnames = list() 630 for i in xrange(start, end + 1): 631 if binfo[EQUALWIDTH][0] == True: 632 hostname = '%s%0*d' %(binfo[BASENAME], binfo[EQUALWIDTH][1], i) 633 else: 634 hostname = '%s%d' %(binfo[BASENAME], i) 635 636 if DEBUG: 637 print 'host = %s, Basename = %s, number = %d' %(hostname, binfo[BASENAME], i) 638 hostnames.append(hostname) 639 640 host_2_hex(hostnames,binfo) 641 642 def rack_2_net(binfo): 643 """ 644 """ 645 str = 'rack_2_net' 646 verbose(str) 647 basename = binfo[BASENAME] 648 start = binfo[RACK][START] 649 end = binfo[RACK][END] 650 651 if start > end: 652 error = '%d >= %d : start value is greater then end value' %(start, end) 653 raise PxeConfig, error 654 655 for i in xrange(start, end + 1): 656 if binfo[RACK][EQUALWIDTH][0] == True: 657 new_base = '%s%s%0*d' %(basename, binfo[RACK][BASENAME], binfo[RACK][EQUALWIDTH][1], i) 658 else: 659 new_base = '%s%s%d' %(basename, binfo[RACK][BASENAME], i) 660 661 # Make copy and file in the appropiate values for creating/removing links 662 # 663 new_bootinfo = binfo[NODE].copy() 664 new_bootinfo[BASENAME] = '%s%s' %(new_base, binfo[NODE][BASENAME]) 665 new_bootinfo[FILENAME] = binfo[FILENAME] 666 new_bootinfo[REMOVE] = binfo[REMOVE] 667 668 if DEBUG: 669 print 'rack ', new_bootinfo 670 671 base_2_host(new_bootinfo) 672 673 674 def new_main(): 306 manage_links(binfo, options) 307 675 308 676 309 def main(): 677 310 # A dictionary holding the boot info 678 311 # 679 global DEBUG680 312 global PXE_CONF_DIR 681 313 682 bootinfo = {}683 bootinfo[NODE] = None684 bootinfo[RACK] = None685 bootinfo[REMOVE] = False686 bootinfo[EQUALWIDTH] = [ False, 0 ]687 688 314 configfile = '@pxeconfig_conf@' 689 315 settings = ReadConfig(configfile) … … 691 317 try: 692 318 PXE_CONF_DIR = settings['pxe_config_dir'] 693 if not DEBUG:694 DEBUG = int(settings['debug'])695 319 696 320 except KeyError: … … 702 326 raise PxeConfig, error 703 327 704 parse _args(sys.argv, bootinfo)328 parser(sys.argv) 705 329 706 330 if __name__ == '__main__': 707 331 try: 708 new_parser(sys.argv) 709 new_main() 332 main() 710 333 except PxeConfig, detail: 711 334 print detail
Note: See TracChangeset
for help on using the changeset viewer.