source: trunk/pxeconfig.in @ 118

Last change on this file since 118 was 118, checked in by bas, 14 years ago

pxeconfig.in:

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