source: trunk/pxeconfig.in @ 125

Last change on this file since 125 was 125, checked in by bas, 16 years ago

pxeconfig.in:

  • renamed some functions
  • added some verbose commands
  • Property keywords set to Id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 14.9 KB
RevLine 
[78]1#!@PYTHON@
[2]2#
[104]3# set ts=4, sw=4
4#
[2]5# Author: Bas van der Vlies <basv@sara.nl>
6# Date  : 16 February 2002
7#
8# Tester: Walter de Jong <walter@sara.nl>
9#
[29]10# SVN info
11#  $Id: pxeconfig.in 125 2008-04-13 16:46:59Z bas $
[2]12#
[14]13# Copyright (C) 2002
[6]14#
[14]15# This file is part of the pxeconfig utils
[6]16#
[14]17# This program is free software; you can redistribute it and/or modify it
18# under the terms of the GNU General Public License as published by the
19# Free Software Foundation; either version 2, or (at your option) any
20# later version.
[6]21#
[14]22# This program is distributed in the hope that it will be useful,
23# but WITHOUT ANY WARRANTY; without even the implied warranty of
24# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25# GNU General Public License for more details.
[6]26#
[14]27# You should have received a copy of the GNU General Public License
28# along with this program; if not, write to the Free Software
29# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
30#
[2]31"""
[19]32Usage: pxeconfig
[49]33
[123]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>
[10]38
[123]39there are serveal ways to create a range of hostnames. they all start with:
40 * -b,--base-name <string>
41
42The 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
47opt:
[118]48 -r,--remove
[123]49 -w,--equal-width
[118]50
[123]51If leading zeros are used for <number>, then host names will be padded
52with zeros. --equal-width option will be enabled.
53
[19]54With this program you can configure which PXE configuration file a node
55will use when it boots. The program can be started interactivly or via
56command line options.
57
58When this program is started interactive it will ask the following questions:
[2]59  1) Network address (Class C-network address only)
[9]60  2) Starting number
[2]61  3) Ending number
[9]62  4) Which PXE config file to use
[2]63
64For example, if the answers are:
65  1) 10.168.44
66  2) 2
67  3) 4
68  4) default.node_install
69
70Then the result is that is create symbolic links in /tftpboot/pxelinux.cfg:
71  0AA82C02 ----> default.node_install
72  0AA82C03 ----> default.node_install
73  0AA82C04 ----> default.node_install
74"""
75
76import string
77import sys
78import os
79import glob
[3]80import getopt
[47]81import socket
[74]82import ConfigParser
[123]83import re
[2]84
85# DEBUG
86#
[105]87DEBUG=0
[117]88VERBOSE=0
[2]89
90# Constants
91#
[4]92PXE_CONF_DIR='/tftpboot/pxelinux.cfg'
[19]93NETWORK='network'
[104]94BASENAME='basename'
[19]95FILENAME='filename'
96START='start'
97END='end'
[53]98REMOVE='remove'
[123]99RACK='rack'
100NODE='node'
[55]101INTERACTIVE='interactive'
[117]102EQUALWIDTH='equalwidth'
[120]103VERSION='2.0.0'
[2]104
[123]105SHORTOPT_LIST='b:e:f:hin:s:rwvN:H:R:V'
[117]106LONGOPT_LIST=[ 'basename=', 'debug', 'end=', 'equal-width',
[119]107    'file=', 'help', 'host-range=', 'interactive', 'net=',
[123]108    'node=', 'start=', 'rack=', 'remove', 'verbose',
109    'version', 'equal-width'
[117]110        ]
[19]111
[123]112# regex definition
113#
114# A regulare expression to parse string for rack/node
115id_re = re.compile(r"""
116        (?P<basename>[a-zA-Z-_]*)
117        (?P<number>[0-9]+)
118     """, re.VERBOSE)
119
[117]120def verbose(str):
121        if VERBOSE:
[120]122                print '%s' %str
[117]123
[119]124class PxeConfig(Exception):
125        def __init__(self, msg=''):
126                self.msg = msg
127                Exception.__init__(self, msg)
128
129        def __repr__(self):
130                return self.msg
131
132
[75]133def ReadConfig(file):
[74]134        """
135        Parse the config file
136        """
137        if not os.path.isfile(file):
138                print 'File %s does not exist' %file
139                sys.exit(1)
140
[75]141        config = ConfigParser.RawConfigParser()
[74]142        try:
143                config.read(file)
144        except ConfigParser.MissingSectionHeaderError,detail:
145                print detail
146                sys.exit(1)
147
[75]148        # Not yet uses
[74]149        #
[75]150        #projects = {}
151        #for section in config.sections():
152        #       projects[section] = {}
153        #       for option in  config.options(section):
154        #       projects[section][option] = config.get(section, option)
[74]155
[75]156        stanza = config.defaults()
157        return stanza
[74]158
[19]159def select_pxe_configfile():
[2]160  """
[9]161  Let user choose which pxeconfig file to use.
[2]162  """
163
[4]164  os.chdir(PXE_CONF_DIR)
[2]165
[9]166  # Try to determine to which file the default symlink points, and
167  # if it exists, remove it from the list.
[2]168  #
169  try:
170    default_file = os.readlink('default')
171  except OSError:
172    default_file = None
173    pass
174
175  files = glob.glob('default.*')
176  if not files:
177    print 'There are no pxe config files starting with: default.'
178    sys.exit(1)
179
180  if default_file:
181    files.remove(default_file)
[98]182
183  # sort the files
184  #
185  files.sort()
[2]186 
187  print 'Which pxe config file must we use: ?'
188  i = 1   
189  for file in files:
190    print "%d : %s" %(i,file)
191    i = i +1
192
193  while 1:
[16]194    index = raw_input('Select a number: ')
[2]195    try:
196      index = int(index)
197    except ValueError:
198      index = len(files) + 1
199
200    # Is the user smart enough to select
[9]201    # the right value??
[2]202    #
203    if 0 < index <= len(files): break
204
205  return files[index-1]
206
[53]207def manage_links(dict):
[2]208  """
[4]209  Create the links in the PXE_CONF_DIR,
[9]210    list : A list containing: network hex address, pxe config file,
[2]211           start number, end number
212  """
[125]213  str = 'manage_links'
214  verbose(str)
215
[4]216  os.chdir(PXE_CONF_DIR)
[19]217  naddr = dict[NETWORK]
218  pxe_filename = dict[FILENAME]
[122]219
[19]220  for i in range(dict[START], dict[END]+1):
[2]221    haddr = '%s%02X' %(naddr, i)
[15]222
[123]223    if dict[REMOVE] == True:
[53]224       if DEBUG:
225          print 'removing %s/%s' %(PXE_CONF_DIR, haddr)
226       if os.path.exists(haddr):
227          os.unlink(haddr)
228    else:
229       if DEBUG:
230          print 'linking %s to %s' %(haddr, pxe_filename)
231       if os.path.exists(haddr):
232          os.unlink(haddr)
233       os.symlink(pxe_filename, haddr)
[15]234
[125]235def net_2_hex(net):
[119]236        """
237        This function checks if the give network is a Class C-network and will
238        convert the network address to a hex address if true.
239        """
[125]240        str = 'net_2_hex : %s' %(net)
[119]241        verbose(str)
242       
243        d = string.split(net, '.')
244        if len(d) != 3:
245                error = '%s is not a valid  C-class network address' %(net)
246                raise PxeConfig, error
[2]247
[119]248        # Check if we have valid network values
249        r = ''
250        for i in d:
251                i = check_number(i, True)
252                r = '%s%02X' %(r,i)
[2]253
[119]254        if DEBUG:
[123]255                print 'C-network in hex: ', r
[19]256
[119]257        return r
[19]258
[119]259def check_number(number_str, network):
[105]260        """
[119]261        number : a string. If string starts with a zero (0) then
262                 EQUALWIDTH wil be set.
263        network: if true then number must ben between 0 < number < 255
264                 else it must be a valid number.
[105]265        """
266        try:
[119]267                n = int(number_str)
[105]268        except ValueError, detail:
[119]269                error = "%s : is not a valid number" %number_str
270                raise PxeConfig, error
[2]271
[105]272        if not network:
273                return n
[19]274
[105]275        # Check if it is a correct network value
276        #
277        if 0 <= n <= 255:
278                return n
279        else:
[119]280                error = '%s is not a valid network number, must be between 0 and 255' %n
281                raise PxeConfig, error
[19]282
283def interactive(binfo):
[105]284        print __doc__
285       
[119]286
287        while 1:
288                network = raw_input('Give network address (xxx.xxx.xxx): ')
289
290                try:
[125]291                        naddr = net_2_hex(network)
[119]292                        break
293                except PxeConfig, detail:
294                        print '%s : not a valid C-class network number' %(network)
295                        continue
296
297        while 1:
298                start = raw_input('Starting number: ')
299
300                try:
301                        start = check_number(start, True)
302                        break
303                except PxeConfig, detail:
304                        print detail
305                        continue
306               
307        while 1:
308                end = raw_input('Ending number: ')
309
310                try:
311                        end = check_number(end, True)
312                        break
313                except PxeConfig, detail:
314                        print detail
315                        continue
[105]316       
317       
318        pxe_filename = select_pxe_configfile()
319       
320        binfo[NETWORK] = naddr
321        binfo[START] = start
322        binfo[END] = end
323        binfo[FILENAME] = pxe_filename
324       
325        if DEBUG:
326                print network, binfo
[2]327
[105]328        manage_links(binfo)
[2]329
[120]330def check_args(binfo, hostnames):
[105]331        """
332        Do you we have the right and propper values
333        """
[114]334        ### check_filename
[105]335        #
[120]336        str = 'check_args: '
337        verbose(str)   
[105]338        try:
339                if not os.path.isfile(os.path.join(PXE_CONF_DIR, binfo[FILENAME])):
[119]340                        error =  '%s: Filename does not exists' %binfo[FILENAME]
341                        raise Pxeconfig, detail
[105]342        except KeyError, detail:
[114]343                if binfo[REMOVE] :
344                        binfo[FILENAME] = 'Does not matter'
345                else:
346                        binfo[FILENAME] = select_pxe_configfile()
[2]347
[105]348        if hostnames:
[125]349                host_2_hex(hostnames, binfo)
[105]350                sys.exit(0)
351               
352        if binfo.has_key(BASENAME) and binfo.has_key(NETWORK):
[119]353                error =  "The option -n/--net and -b/--basename are mutually exclusive"
354                raise PxeConfig, error 
[2]355
[105]356        if binfo.has_key(BASENAME):
[123]357                if binfo[RACK] and binfo[NODE]:
358                        create_links = rack_2_net
359                else:
360                        set_padding(binfo)
[125]361                        create_links =  base_2_host
[2]362
[105]363        elif binfo.has_key(NETWORK):
[120]364                binfo[START] = check_number(binfo[START], True)
365                binfo[END] = check_number(binfo[END], True)
[105]366                create_links = manage_links
[119]367
[105]368        else:
[119]369                error = 'You have to specifiy -b,--basename or -n,--net'
370                raise PxeConfig, error
[2]371
[105]372        if DEBUG:
373                print binfo
[6]374
[105]375        create_links(binfo)
[19]376
[120]377def set_padding(binfo):
[119]378        """
[120]379        binfo          : boot info
380        network_number : must we check if  start,end values are
381                         valid network numbers
382        return:
383                - if equal_width is requested then the length will be set to end value
384                - if start value length > 1 and start with a zero (0), width is set to
385                  the end value
386                - if end value starts with a zero (0), width will be set to the end
387                  value
[119]388        """
[120]389        start_str = binfo[START]
390        end_str = binfo[END]
[105]391
[120]392        start = check_number(start_str, False)
393        end = check_number(end_str, False)
394
395        if binfo[EQUALWIDTH][0] == True:
396                binfo[EQUALWIDTH][1] = len(end_str)
397
398        elif len(start_str) > 1 and start_str[0] == '0':
399                binfo[EQUALWIDTH] = [ True, len(end_str) ]
400
401        elif end_str[0] == '0':
402                binfo[EQUALWIDTH] = [ True, len(end_str) ]
403
404        binfo[START] = start
405        binfo[END] = end
406
407
[123]408def parse_number_range(binfo, arg):
[119]409        """
410        Parse if arg is of format <digit-digit>, if it starts
411        with a zero (0) then set EQUALWIDTH
412        """
413        str = 'parse_hostrange %s' %(arg)
414        verbose(str)
415
416        l = arg.split('-')
417        if len(l) < 2:
418                error =  'hostrange syntax not valid: %s (number-number)' %(arg)
419                raise PxeConfig, error
420
[120]421        binfo[START] = l[0]
422        binfo[END] = l[1]
[119]423
[123]424def parse_string_range(binfo, arg, id):
425        """
426        Parse if arg is of format <(alpha)(digit)>-(alpha)(digit)>
427        if digit starts with a zero (0) then set EQUALWIDTH
428        """
429        str = 'parse_string_range: %s %s' %(arg, id)
430        verbose(str)
431
432        l = arg.split('-')
433        if len(l) < 2:
434                error =  '%s : range syntax not valid,eg <string>-<string>)' %(arg)
435                raise PxeConfig, error
[119]436       
[123]437        binfo[id] = dict()
438        binfo[id][EQUALWIDTH] = [False, 0]
439
440        i = 0
441        for item in l:
442                result = id_re.match(item)
443
444                if result:
445                        basename = result.group('basename')
446                        number = result.group('number')
447
448                        if DEBUG:
449                                print 'basename = %s, number = %s' %(basename, number)
450       
451                        if i == 0:
452                                binfo[id][BASENAME] = basename
453                                binfo[id][START] = number
454                                i += 1
455                        else:
456                                binfo[id][END] = number
457
458                else:
459                        error = '%s : string syntax is not valid, eg: <alpa>*<digit>+' %(item)
460                        raise PxeConfig, error
461
462        set_padding(binfo[id]) 
463       
[120]464def parse_args(argv, binfo):
[104]465        """
466        This function parses the command line options and returns the rest as
467        an list of hostnames:
468        argv     : a list of command line options.
469        binfo    : returning a dict with the netinfo. if used non-interactively
470        hostnames: the rest of the command lines options that are not-parseble.
471        """
472        try:
473                opts, args = getopt.gnu_getopt(argv[1:], SHORTOPT_LIST, LONGOPT_LIST)
474        except getopt.error, detail:
475                print __doc__
476                print detail
477                sys.exit(1)
478       
479        global DEBUG
[117]480        global VERBOSE
[3]481
[105]482        # if nothing is specified then print usage and exit
483        #
484        if not opts and not args:
485                print __doc__
486                sys.exit(1)
487
[123]488        # init vars
489        #
490        hostrange = node = rack = None
491
[104]492        # Check given options
493        #
494        for opt,value in opts:
495                       
[105]496                if opt in ['-b', '--basename']:
[104]497                        binfo[BASENAME] = value
498                       
499                elif opt in ['--debug']:
500                        DEBUG = 1
501                       
502                elif opt in ['-e', '--end']:
[105]503                        binfo[END] = value
[104]504                       
505                elif opt in ['-f', '--file']:
506                        binfo[FILENAME] = value
507                       
508                elif opt in ['-h', '--help']:
509                        print __doc__
510                        sys.exit(0)
[105]511
512                elif opt in ['-i', '--interactive']:
513                        interactive(binfo)
514                        sys.exit(0)
515
[104]516                elif opt in ['-n', '--net']:
[125]517                        binfo[NETWORK] = net_2_hex(value)
[104]518                       
519                elif opt in ['-r', '--remove']:
520                        binfo[REMOVE] = 1
521                       
522                elif opt in ['-s', '--start']:
[105]523                        binfo[START] = value
[55]524
[117]525                elif opt in ['-w', '--equal-width']:
[120]526                        binfo[EQUALWIDTH] = [True, 0]
[117]527
528                elif opt in ['-v', '--verbose']:
529                        VERBOSE = 1
530
[119]531                elif opt in ['-H', '--host-range']:
[123]532                        hostrange = value
533
534                elif opt in ['-N', '--node']:
535                        node = value
536
537                elif opt in ['-R', '--rack']:
538                        rack = value
[119]539                       
[104]540                elif opt in ['-V', '--version']:
541                        print VERSION
542                        sys.exit(0)
[4]543
[123]544        if node and rack:
545                        parse_string_range(binfo, node, NODE)
546                        parse_string_range(binfo, rack, RACK)
547        elif hostrange:
548                        parse_number_range(binfo, hostrange)
549               
550
[120]551        check_args(binfo, args)
[105]552
[125]553def host_2_hex(hosts, binfo):
[104]554        """
[117]555        Convert hostname(s) to a net address that can be handled by manage_links function
[104]556        """
[125]557        str = 'host_2_hex: %s' %hosts
558        verbose(str)
559
[47]560        for host in hosts:
561                try:
562                        addr = socket.gethostbyname(host)
563                except socket.error,detail:
[119]564                        error =  '%s not an valid hostname: %s' %(host,detail)
565                        raise PxeConfig, error
[47]566                       
567                net = string.splitfields(addr, '.')
568                cnet = string.joinfields(net[0:3], '.')
569
[125]570                binfo[NETWORK] = net_2_hex(cnet)
[47]571                binfo[START] = int(net[3])
572                binfo[END] =  int(net[3])
[53]573                manage_links(binfo)
[47]574
[125]575def base_2_host(binfo):
[104]576        """
577        Construct hostname(s) from the supplied basename and start and end numbers
578        """
[125]579        str = 'base_2_host'
580        verbose(str)
581
[119]582        start = binfo[START]
583        end = binfo[END]
[47]584
[119]585        if start > end:
586                error = '%d >= %d : start value is greater then end value' %(start, end)
587                raise PxeConfig, error
588
[104]589        hostnames = list()
[119]590        for i in xrange(start, end + 1):
[120]591                if binfo[EQUALWIDTH][0] == True:
592                        hostname = '%s%0*d' %(binfo[BASENAME],  binfo[EQUALWIDTH][1], i)
[117]593                else:
594                        hostname = '%s%d' %(binfo[BASENAME], i)
595
[104]596                if DEBUG:
[105]597                        print 'host = %s, Basename = %s, number = %d' %(hostname, binfo[BASENAME], i)
[104]598                hostnames.append(hostname)
[105]599
[125]600        host_2_hex(hostnames,binfo)
[123]601
602def rack_2_net(binfo):
603        """
604        """
[125]605        str = 'rack_2_net'
606        verbose(str)
[123]607        basename = binfo[BASENAME]
608        start = binfo[RACK][START]
609        end = binfo[RACK][END]
610
611        if start > end:
612                error = '%d >= %d : start value is greater then end value' %(start, end)
613                raise PxeConfig, error
614
615        for i in xrange(start, end + 1):
616                if binfo[RACK][EQUALWIDTH][0] == True:
617                        new_base = '%s%s%0*d' %(basename,  binfo[RACK][BASENAME], binfo[RACK][EQUALWIDTH][1], i)
618                else:
619                        new_base = '%s%s%d' %(basename, binfo[RACK][BASENAME], i)
620
621                # Make copy and file in the appropiate values for creating/removing links
622                #
623                new_bootinfo = binfo[NODE].copy()
624                new_bootinfo[BASENAME] = '%s%s' %(new_base, binfo[NODE][BASENAME])
625                new_bootinfo[FILENAME] = binfo[FILENAME]
626                new_bootinfo[REMOVE] = binfo[REMOVE]
627
628                if DEBUG:
629                        print 'rack ', new_bootinfo
630
[125]631                base_2_host(new_bootinfo)
[123]632
[2]633def main():
[104]634        # A dictionary holding the boot info
635        #
636        global DEBUG
[117]637        global PXE_CONF_DIR
[104]638       
639        bootinfo = {}
[123]640        bootinfo[NODE] = None
641        bootinfo[RACK] = None
[117]642        bootinfo[REMOVE] = False
[120]643        bootinfo[EQUALWIDTH] = [ False, 0 ]
[123]644
[104]645        configfile = '@pxeconfig_conf@'
646        settings = ReadConfig(configfile)
647       
648        try:
649                PXE_CONF_DIR = settings['pxe_config_dir']
[105]650                if not DEBUG:
651                        DEBUG = int(settings['debug'])
[117]652
[104]653        except KeyError:
654                pass
[105]655
[118]656        PXE_CONF_DIR = os.path.realpath(PXE_CONF_DIR)
[117]657        if not os.path.isdir(PXE_CONF_DIR):
[120]658                error =  'pxeconfig directory: %s does not exists' %(PXE_CONF_DIR)
659                raise PxeConfig, error
[117]660
[120]661        parse_args(sys.argv, bootinfo)
662       
663if __name__ == '__main__':
[119]664        try:
[120]665                main()
[119]666        except PxeConfig, detail:
667                print detail
668                sys.exit(1)
Note: See TracBrowser for help on using the repository browser.