source: trunk/pxeconfig.in @ 117

Last change on this file since 117 was 117, checked in by bas, 14 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
Line 
1#!@PYTHON@
2#
3# set ts=4, sw=4
4#
5# Author: Bas van der Vlies <basv@sara.nl>
6# Date  : 16 February 2002
7#
8# Tester: Walter de Jong <walter@sara.nl>
9#
10# SVN info
11#  $Id: pxeconfig.in 117 2008-04-10 16:41:48Z bas $
12#
13# Copyright (C) 2002
14#
15# This file is part of the pxeconfig utils
16#
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.
21#
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.
26#
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#
31"""
32Usage: pxeconfig
33
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
39
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:
45  1) Network address (Class C-network address only)
46  2) Starting number
47  3) Ending number
48  4) Which PXE config file to use
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
66import getopt
67import socket
68import ConfigParser
69
70# DEBUG
71#
72DEBUG=0
73VERBOSE=0
74
75# Constants
76#
77PXE_CONF_DIR='/tftpboot/pxelinux.cfg'
78NETWORK='network'
79BASENAME='basename'
80FILENAME='filename'
81START='start'
82END='end'
83REMOVE='remove'
84INTERACTIVE='interactive'
85EQUALWIDTH='equalwidth'
86VERSION='1.1.0'
87
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        ]
93
94def verbose(str):
95        if VERBOSE:
96                print '%s'
97
98def ReadConfig(file):
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
106        config = ConfigParser.RawConfigParser()
107        try:
108                config.read(file)
109        except ConfigParser.MissingSectionHeaderError,detail:
110                print detail
111                sys.exit(1)
112
113        # Not yet uses
114        #
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)
120
121        stanza = config.defaults()
122        return stanza
123
124def select_pxe_configfile():
125  """
126  Let user choose which pxeconfig file to use.
127  """
128
129  os.chdir(PXE_CONF_DIR)
130
131  # Try to determine to which file the default symlink points, and
132  # if it exists, remove it from the list.
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)
147
148  # sort the files
149  #
150  files.sort()
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:
159    index = raw_input('Select a number: ')
160    try:
161      index = int(index)
162    except ValueError:
163      index = len(files) + 1
164
165    # Is the user smart enough to select
166    # the right value??
167    #
168    if 0 < index <= len(files): break
169
170  return files[index-1]
171
172def manage_links(dict):
173  """
174  Create the links in the PXE_CONF_DIR,
175    list : A list containing: network hex address, pxe config file,
176           start number, end number
177  """
178  os.chdir(PXE_CONF_DIR)
179  naddr = dict[NETWORK]
180  pxe_filename = dict[FILENAME]
181  for i in range(dict[START], dict[END]+1):
182    haddr = '%s%02X' %(naddr, i)
183
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)
195
196def convert_network(net, prefix=''):
197  """
198  This function checks if the give network is a Class C-network and will
199  convert the network address to a hex address if true.
200  """
201  d = string.split(net, '.')
202
203  if len(d) != 3:
204    if prefix:
205      net = prefix + ' : ' + net
206     
207    print '%s is not a valid  C-class network address!!!' %net
208    sys.exit(1)
209
210
211  # Display right message for interactive/commandline
212  if prefix:
213    prefix = prefix + ' ' + net
214  else:
215    prefix = net
216
217  # Check if we have valid network values
218  r = ''
219  for i in d:
220    i = check_number(i, prefix)
221    r = '%s%02X' %(r,i)
222
223  if DEBUG:
224    print r
225
226  return r
227
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)
239
240        if not network:
241                return n
242
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)
253
254def interactive(binfo):
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
275
276        manage_links(binfo)
277
278def check_command_line(binfo, hostnames):
279        """
280        Do you we have the right and propper values
281        """
282        ### check_filename
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:
289                if binfo[REMOVE] :
290                        binfo[FILENAME] = 'Does not matter'
291                else:
292                        binfo[FILENAME] = select_pxe_configfile()
293
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)
302
303        if binfo.has_key(BASENAME):
304                network_number = False
305                create_links =  base_2_net
306
307        elif binfo.has_key(NETWORK):
308                network_number = True
309                create_links = manage_links
310        else:
311                print __doc__
312                sys.exit(1)
313
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)
320
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)
327
328        if DEBUG:
329                print binfo
330
331        create_links(binfo)
332
333
334def check_args(argv, binfo):
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
350        global VERBOSE
351
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
358        # Check given options
359        #
360        for opt,value in opts:
361                       
362                if opt in ['-b', '--basename']:
363                        binfo[BASENAME] = value
364                       
365                elif opt in ['--debug']:
366                        DEBUG = 1
367                       
368                elif opt in ['-e', '--end']:
369                        binfo[END] = value
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)
377
378                elif opt in ['-i', '--interactive']:
379                        interactive(binfo)
380                        sys.exit(0)
381
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']:
390                        binfo[START] = value
391
392                elif opt in ['-w', '--equal-width']:
393                        binfo[EQUALWIDTH] = True
394
395                elif opt in ['-v', '--verbose']:
396                        VERBOSE = 1
397
398                elif opt in ['-V', '--version']:
399                        print VERSION
400                        sys.exit(0)
401
402        check_command_line(binfo, args)
403
404def host_2_net(hosts, binfo):
405        """
406        Convert hostname(s) to a net address that can be handled by manage_links function
407        """
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])
421                manage_links(binfo)
422
423def base_2_net(binfo):
424        """
425        Construct hostname(s) from the supplied basename and start and end numbers
426        """
427        if binfo[START] >= binfo[END]:
428                print __doc__
429                print "Supplied wrong values for start (%d) and end (%d)" %(binfo[START], binfo[END])
430                sys.exit(1)
431
432        if binfo[EQUALWIDTH]:
433                width = len(str(binfo[END]))
434
435        hostnames = list()
436        for i in xrange(binfo[START], binfo[END] + 1):
437                if binfo[EQUALWIDTH]:
438                        hostname = '%s%0*d' %(binfo[BASENAME], width, i)
439                else:
440                        hostname = '%s%d' %(binfo[BASENAME], i)
441
442                if DEBUG:
443                        print 'host = %s, Basename = %s, number = %d' %(hostname, binfo[BASENAME], i)
444                hostnames.append(hostname)
445
446        host_2_net(hostnames,binfo)
447               
448def main():
449        # A dictionary holding the boot info
450        #
451        global DEBUG
452        global PXE_CONF_DIR
453       
454        bootinfo = {}
455        bootinfo[REMOVE] = False
456        bootinfo[EQUALWIDTH] = False
457       
458        configfile = '@pxeconfig_conf@'
459        settings = ReadConfig(configfile)
460       
461        try:
462                PXE_CONF_DIR = settings['pxe_config_dir']
463                if not DEBUG:
464                        DEBUG = int(settings['debug'])
465
466        except KeyError:
467                pass
468
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
473        check_args(sys.argv, bootinfo)
474       
475if __name__ == '__main__':
476        main()
Note: See TracBrowser for help on using the repository browser.