source: trunk/pxeconfig.in @ 117

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

pxeconfig.in:

  • Fixed many small bugs
  • added -w,--equal-width option
  • Property keywords set to Id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 10.6 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 117 2008-04-10 16:41:48Z 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
[105]34 [-r|--remove] -f|--file <filename>] [hostname(s)
35 [-r|--remove] -i|--interactive
36 [-r|--remove] -n|--net <C-net> -s|--start <number> -e|--end <number> -f|--file <filename>
37 [-r|--remove] -b|--basename <string> s|--start <number> -e|--end <number> -f|--file <filename>
38 -V|--version
[10]39
[19]40With this program you can configure which PXE configuration file a node
41will use when it boots. The program can be started interactivly or via
42command line options.
43
44When this program is started interactive it will ask the following questions:
[2]45  1) Network address (Class C-network address only)
[9]46  2) Starting number
[2]47  3) Ending number
[9]48  4) Which PXE config file to use
[2]49
50For example, if the answers are:
51  1) 10.168.44
52  2) 2
53  3) 4
54  4) default.node_install
55
56Then the result is that is create symbolic links in /tftpboot/pxelinux.cfg:
57  0AA82C02 ----> default.node_install
58  0AA82C03 ----> default.node_install
59  0AA82C04 ----> default.node_install
60"""
61
62import string
63import sys
64import os
65import glob
[3]66import getopt
[47]67import socket
[74]68import ConfigParser
[2]69
70# DEBUG
71#
[105]72DEBUG=0
[117]73VERBOSE=0
[2]74
75# Constants
76#
[4]77PXE_CONF_DIR='/tftpboot/pxelinux.cfg'
[19]78NETWORK='network'
[104]79BASENAME='basename'
[19]80FILENAME='filename'
81START='start'
82END='end'
[53]83REMOVE='remove'
[55]84INTERACTIVE='interactive'
[117]85EQUALWIDTH='equalwidth'
86VERSION='1.1.0'
[2]87
[117]88SHORTOPT_LIST='b:e:f:hin:s:rwvV'
89LONGOPT_LIST=[ 'basename=', 'debug', 'end=', 'equal-width',
90    'file=', 'help', 'interactive', 'net=', 'start=',
91        'remove', 'verbose', 'version', 'equal-width'
92        ]
[19]93
[117]94def verbose(str):
95        if VERBOSE:
96                print '%s'
97
[75]98def ReadConfig(file):
[74]99        """
100        Parse the config file
101        """
102        if not os.path.isfile(file):
103                print 'File %s does not exist' %file
104                sys.exit(1)
105
[75]106        config = ConfigParser.RawConfigParser()
[74]107        try:
108                config.read(file)
109        except ConfigParser.MissingSectionHeaderError,detail:
110                print detail
111                sys.exit(1)
112
[75]113        # Not yet uses
[74]114        #
[75]115        #projects = {}
116        #for section in config.sections():
117        #       projects[section] = {}
118        #       for option in  config.options(section):
119        #       projects[section][option] = config.get(section, option)
[74]120
[75]121        stanza = config.defaults()
122        return stanza
[74]123
[19]124def select_pxe_configfile():
[2]125  """
[9]126  Let user choose which pxeconfig file to use.
[2]127  """
128
[4]129  os.chdir(PXE_CONF_DIR)
[2]130
[9]131  # Try to determine to which file the default symlink points, and
132  # if it exists, remove it from the list.
[2]133  #
134  try:
135    default_file = os.readlink('default')
136  except OSError:
137    default_file = None
138    pass
139
140  files = glob.glob('default.*')
141  if not files:
142    print 'There are no pxe config files starting with: default.'
143    sys.exit(1)
144
145  if default_file:
146    files.remove(default_file)
[98]147
148  # sort the files
149  #
150  files.sort()
[2]151 
152  print 'Which pxe config file must we use: ?'
153  i = 1   
154  for file in files:
155    print "%d : %s" %(i,file)
156    i = i +1
157
158  while 1:
[16]159    index = raw_input('Select a number: ')
[2]160    try:
161      index = int(index)
162    except ValueError:
163      index = len(files) + 1
164
165    # Is the user smart enough to select
[9]166    # the right value??
[2]167    #
168    if 0 < index <= len(files): break
169
170  return files[index-1]
171
[53]172def manage_links(dict):
[2]173  """
[4]174  Create the links in the PXE_CONF_DIR,
[9]175    list : A list containing: network hex address, pxe config file,
[2]176           start number, end number
177  """
[4]178  os.chdir(PXE_CONF_DIR)
[19]179  naddr = dict[NETWORK]
180  pxe_filename = dict[FILENAME]
181  for i in range(dict[START], dict[END]+1):
[2]182    haddr = '%s%02X' %(naddr, i)
[15]183
[53]184    if dict[REMOVE]:
185       if DEBUG:
186          print 'removing %s/%s' %(PXE_CONF_DIR, haddr)
187       if os.path.exists(haddr):
188          os.unlink(haddr)
189    else:
190       if DEBUG:
191          print 'linking %s to %s' %(haddr, pxe_filename)
192       if os.path.exists(haddr):
193          os.unlink(haddr)
194       os.symlink(pxe_filename, haddr)
[15]195
[47]196def convert_network(net, prefix=''):
[2]197  """
[9]198  This function checks if the give network is a Class C-network and will
[2]199  convert the network address to a hex address if true.
200  """
201  d = string.split(net, '.')
202
203  if len(d) != 3:
[19]204    if prefix:
205      net = prefix + ' : ' + net
206     
207    print '%s is not a valid  C-class network address!!!' %net
[2]208    sys.exit(1)
209
[19]210
211  # Display right message for interactive/commandline
212  if prefix:
213    prefix = prefix + ' ' + net
214  else:
215    prefix = net
216
[2]217  # Check if we have valid network values
218  r = ''
219  for i in d:
[19]220    i = check_number(i, prefix)
[2]221    r = '%s%02X' %(r,i)
222
223  if DEBUG:
224    print r
225
226  return r
227
[105]228def check_number(number, network, option_str=''):
229        """
230        This functions checks if the input is between 0 < number < 255:
231        number : is a string
232        prefix : a string that must be displayed if an error occurs
233        """
234        try:
235                n = int(number)
236        except ValueError, detail:
237                print option_str, detail
238                sys.exit(1)
[2]239
[105]240        if not network:
241                return n
[19]242
[105]243        # Check if it is a correct network value
244        #
245        if 0 <= n <= 255:
246                return n
247        else:
248                if option_str:
249                        number = option_str +' : ' + number
250                       
251                print '%s is not a valid network number, must be between 0 and 255' %number
252                sys.exit(1)
[19]253
254def interactive(binfo):
[105]255        print __doc__
256       
257        network = raw_input('Give network address (xxx.xxx.xxx): ')
258        naddr = convert_network(network)
259       
260        start = raw_input('Starting number: ')
261        start = check_number(start, True)
262       
263        end = raw_input('Ending number: ')
264        end = check_number(end, True)
265       
266        pxe_filename = select_pxe_configfile()
267       
268        binfo[NETWORK] = naddr
269        binfo[START] = start
270        binfo[END] = end
271        binfo[FILENAME] = pxe_filename
272       
273        if DEBUG:
274                print network, binfo
[2]275
[105]276        manage_links(binfo)
[2]277
[105]278def check_command_line(binfo, hostnames):
279        """
280        Do you we have the right and propper values
281        """
[114]282        ### check_filename
[105]283        #
284        try:
285                if not os.path.isfile(os.path.join(PXE_CONF_DIR, binfo[FILENAME])):
286                        print '%s: Filename does not exists' %binfo[FILENAME]
287                        sys.exit(1)
288        except KeyError, detail:
[114]289                if binfo[REMOVE] :
290                        binfo[FILENAME] = 'Does not matter'
291                else:
292                        binfo[FILENAME] = select_pxe_configfile()
[2]293
[105]294        if hostnames:
295                host_2_net(hostnames, binfo)
296                sys.exit(0)
297               
298        if binfo.has_key(BASENAME) and binfo.has_key(NETWORK):
299                print __doc__
300                print "The option -n/--net and -b/--basename are mutually exclusive"
301                sys.exit(1)
[2]302
[105]303        if binfo.has_key(BASENAME):
304                network_number = False
305                create_links =  base_2_net
[2]306
[105]307        elif binfo.has_key(NETWORK):
308                network_number = True
309                create_links = manage_links
310        else:
311                print __doc__
312                sys.exit(1)
[2]313
[105]314        try:
315                binfo[START] = check_number(binfo[START], network_number, '-s/--start')
316        except KeyError, detail:
317                print __doc__
318                print '-s/--start is missing on the command line' %(detail)
319                sys.exit(1)
[2]320
[105]321        try:
322                binfo[END] = check_number(binfo[END], network_number, '-e/--end')
323        except KeyError, detail:
324                print __doc__
325                print '-e/--end is missing on the command line' %(detail)
326                sys.exit(1)
[2]327
[105]328        if DEBUG:
329                print binfo
[6]330
[105]331        create_links(binfo)
[19]332
[105]333
[19]334def check_args(argv, binfo):
[104]335        """
336        This function parses the command line options and returns the rest as
337        an list of hostnames:
338        argv     : a list of command line options.
339        binfo    : returning a dict with the netinfo. if used non-interactively
340        hostnames: the rest of the command lines options that are not-parseble.
341        """
342        try:
343                opts, args = getopt.gnu_getopt(argv[1:], SHORTOPT_LIST, LONGOPT_LIST)
344        except getopt.error, detail:
345                print __doc__
346                print detail
347                sys.exit(1)
348       
349        global DEBUG
[117]350        global VERBOSE
[3]351
[105]352        # if nothing is specified then print usage and exit
353        #
354        if not opts and not args:
355                print __doc__
356                sys.exit(1)
357
[104]358        # Check given options
359        #
360        for opt,value in opts:
361                       
[105]362                if opt in ['-b', '--basename']:
[104]363                        binfo[BASENAME] = value
364                       
365                elif opt in ['--debug']:
366                        DEBUG = 1
367                       
368                elif opt in ['-e', '--end']:
[105]369                        binfo[END] = value
[104]370                       
371                elif opt in ['-f', '--file']:
372                        binfo[FILENAME] = value
373                       
374                elif opt in ['-h', '--help']:
375                        print __doc__
376                        sys.exit(0)
[105]377
378                elif opt in ['-i', '--interactive']:
379                        interactive(binfo)
380                        sys.exit(0)
381
[104]382                elif opt in ['-n', '--net']:
383                        network = value
384                        binfo[NETWORK] = convert_network(value, opt)
385                       
386                elif opt in ['-r', '--remove']:
387                        binfo[REMOVE] = 1
388                       
389                elif opt in ['-s', '--start']:
[105]390                        binfo[START] = value
[55]391
[117]392                elif opt in ['-w', '--equal-width']:
393                        binfo[EQUALWIDTH] = True
394
395                elif opt in ['-v', '--verbose']:
396                        VERBOSE = 1
397
[104]398                elif opt in ['-V', '--version']:
399                        print VERSION
400                        sys.exit(0)
[4]401
[105]402        check_command_line(binfo, args)
403
[104]404def host_2_net(hosts, binfo):
405        """
[117]406        Convert hostname(s) to a net address that can be handled by manage_links function
[104]407        """
[47]408        for host in hosts:
409                try:
410                        addr = socket.gethostbyname(host)
411                except socket.error,detail:
412                        print '%s not an valid hostname: %s' %(host,detail)
413                        sys.exit(1)
414                       
415                net = string.splitfields(addr, '.')
416                cnet = string.joinfields(net[0:3], '.')
417
418                binfo[NETWORK] = convert_network(cnet)
419                binfo[START] = int(net[3])
420                binfo[END] =  int(net[3])
[53]421                manage_links(binfo)
[47]422
[104]423def base_2_net(binfo):
424        """
425        Construct hostname(s) from the supplied basename and start and end numbers
426        """
[105]427        if binfo[START] >= binfo[END]:
[104]428                print __doc__
[105]429                print "Supplied wrong values for start (%d) and end (%d)" %(binfo[START], binfo[END])
[104]430                sys.exit(1)
[47]431
[117]432        if binfo[EQUALWIDTH]:
433                width = len(str(binfo[END]))
434
[104]435        hostnames = list()
436        for i in xrange(binfo[START], binfo[END] + 1):
[117]437                if binfo[EQUALWIDTH]:
438                        hostname = '%s%0*d' %(binfo[BASENAME], width, i)
439                else:
440                        hostname = '%s%d' %(binfo[BASENAME], i)
441
[104]442                if DEBUG:
[105]443                        print 'host = %s, Basename = %s, number = %d' %(hostname, binfo[BASENAME], i)
[104]444                hostnames.append(hostname)
[105]445
[104]446        host_2_net(hostnames,binfo)
447               
[2]448def main():
[104]449        # A dictionary holding the boot info
450        #
451        global DEBUG
[117]452        global PXE_CONF_DIR
[104]453       
454        bootinfo = {}
[117]455        bootinfo[REMOVE] = False
456        bootinfo[EQUALWIDTH] = False
[104]457       
458        configfile = '@pxeconfig_conf@'
459        settings = ReadConfig(configfile)
460       
461        try:
462                PXE_CONF_DIR = settings['pxe_config_dir']
[105]463                if not DEBUG:
464                        DEBUG = int(settings['debug'])
[117]465
[104]466        except KeyError:
467                pass
[105]468
[117]469        if not os.path.isdir(PXE_CONF_DIR):
470                print 'pxeconfig directory: %s does not exists' %(PXE_CONF_DIR)
471                sys.exit(1)
472
[105]473        check_args(sys.argv, bootinfo)
[104]474       
[2]475if __name__ == '__main__':
[104]476        main()
Note: See TracBrowser for help on using the repository browser.