source: trunk/pxeconfig.in @ 119

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

pxeconfig.in:

  • Rewrite of code o use execeptions
  • implemented --host-range, -H
  • added better padding, must still be inproved
  • Property keywords set to Id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 11.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 119 2008-04-10 21:52:45Z 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:rwvH:V'
93LONGOPT_LIST=[ 'basename=', 'debug', 'end=', 'equal-width',
94    'file=', 'help', 'host-range=', 'interactive', 'net=',
95    'start=', 'remove', 'verbose', 'version', 'equal-width'
96        ]
97
98def verbose(str):
99        if VERBOSE:
100                print '%s'
101
102class PxeConfig(Exception):
103        def __init__(self, msg=''):
104                self.msg = msg
105                Exception.__init__(self, msg)
106
107        def __repr__(self):
108                return self.msg
109
110
111def ReadConfig(file):
112        """
113        Parse the config file
114        """
115        if not os.path.isfile(file):
116                print 'File %s does not exist' %file
117                sys.exit(1)
118
119        config = ConfigParser.RawConfigParser()
120        try:
121                config.read(file)
122        except ConfigParser.MissingSectionHeaderError,detail:
123                print detail
124                sys.exit(1)
125
126        # Not yet uses
127        #
128        #projects = {}
129        #for section in config.sections():
130        #       projects[section] = {}
131        #       for option in  config.options(section):
132        #       projects[section][option] = config.get(section, option)
133
134        stanza = config.defaults()
135        return stanza
136
137def select_pxe_configfile():
138  """
139  Let user choose which pxeconfig file to use.
140  """
141
142  os.chdir(PXE_CONF_DIR)
143
144  # Try to determine to which file the default symlink points, and
145  # if it exists, remove it from the list.
146  #
147  try:
148    default_file = os.readlink('default')
149  except OSError:
150    default_file = None
151    pass
152
153  files = glob.glob('default.*')
154  if not files:
155    print 'There are no pxe config files starting with: default.'
156    sys.exit(1)
157
158  if default_file:
159    files.remove(default_file)
160
161  # sort the files
162  #
163  files.sort()
164 
165  print 'Which pxe config file must we use: ?'
166  i = 1   
167  for file in files:
168    print "%d : %s" %(i,file)
169    i = i +1
170
171  while 1:
172    index = raw_input('Select a number: ')
173    try:
174      index = int(index)
175    except ValueError:
176      index = len(files) + 1
177
178    # Is the user smart enough to select
179    # the right value??
180    #
181    if 0 < index <= len(files): break
182
183  return files[index-1]
184
185def manage_links(dict):
186  """
187  Create the links in the PXE_CONF_DIR,
188    list : A list containing: network hex address, pxe config file,
189           start number, end number
190  """
191  print PXE_CONF_DIR
192  os.chdir(PXE_CONF_DIR)
193  naddr = dict[NETWORK]
194  pxe_filename = dict[FILENAME]
195  for i in range(dict[START], dict[END]+1):
196    haddr = '%s%02X' %(naddr, i)
197
198    if dict[REMOVE]:
199       if DEBUG:
200          print 'removing %s/%s' %(PXE_CONF_DIR, haddr)
201       if os.path.exists(haddr):
202          os.unlink(haddr)
203    else:
204       if DEBUG:
205          print 'linking %s to %s' %(haddr, pxe_filename)
206       if os.path.exists(haddr):
207          os.unlink(haddr)
208       os.symlink(pxe_filename, haddr)
209
210def convert_network(net):
211        """
212        This function checks if the give network is a Class C-network and will
213        convert the network address to a hex address if true.
214        """
215        str = 'convert_network : %s' %(net)
216        verbose(str)
217       
218        d = string.split(net, '.')
219        if len(d) != 3:
220                error = '%s is not a valid  C-class network address' %(net)
221                raise PxeConfig, error
222
223        # Check if we have valid network values
224        r = ''
225        for i in d:
226                i = check_number(i, True)
227                r = '%s%02X' %(r,i)
228
229        if DEBUG:
230                print r
231
232        return r
233
234def check_number(number_str, network):
235        """
236        number : a string. If string starts with a zero (0) then
237                 EQUALWIDTH wil be set.
238        network: if true then number must ben between 0 < number < 255
239                 else it must be a valid number.
240        """
241
242        try:
243                n = int(number_str)
244        except ValueError, detail:
245                error = "%s : is not a valid number" %number_str
246                raise PxeConfig, error
247
248        if not network:
249                return n
250
251        # Check if it is a correct network value
252        #
253        if 0 <= n <= 255:
254                return n
255        else:
256                error = '%s is not a valid network number, must be between 0 and 255' %n
257                raise PxeConfig, error
258
259def interactive(binfo):
260        print __doc__
261       
262
263        while 1:
264                network = raw_input('Give network address (xxx.xxx.xxx): ')
265
266                try:
267                        naddr = convert_network(network)
268                        break
269                except PxeConfig, detail:
270                        print '%s : not a valid C-class network number' %(network)
271                        continue
272
273        while 1:
274                start = raw_input('Starting number: ')
275
276                try:
277                        start = check_number(start, True)
278                        break
279                except PxeConfig, detail:
280                        print detail
281                        continue
282               
283        while 1:
284                end = raw_input('Ending number: ')
285
286                try:
287                        end = check_number(end, True)
288                        break
289                except PxeConfig, detail:
290                        print detail
291                        continue
292       
293       
294        pxe_filename = select_pxe_configfile()
295       
296        binfo[NETWORK] = naddr
297        binfo[START] = start
298        binfo[END] = end
299        binfo[FILENAME] = pxe_filename
300       
301        if DEBUG:
302                print network, binfo
303
304        manage_links(binfo)
305
306def check_command_line(binfo, hostnames):
307        """
308        Do you we have the right and propper values
309        """
310        ### check_filename
311        #
312        try:
313                if not os.path.isfile(os.path.join(PXE_CONF_DIR, binfo[FILENAME])):
314                        error =  '%s: Filename does not exists' %binfo[FILENAME]
315                        raise Pxeconfig, detail
316        except KeyError, detail:
317                if binfo[REMOVE] :
318                        binfo[FILENAME] = 'Does not matter'
319                else:
320                        binfo[FILENAME] = select_pxe_configfile()
321
322        if hostnames:
323                host_2_net(hostnames, binfo)
324                sys.exit(0)
325               
326        if binfo.has_key(BASENAME) and binfo.has_key(NETWORK):
327                error =  "The option -n/--net and -b/--basename are mutually exclusive"
328                raise PxeConfig, error 
329
330        if binfo.has_key(BASENAME):
331                network_number = False
332                create_links =  base_2_net
333
334        elif binfo.has_key(NETWORK):
335                network_number = True
336                create_links = manage_links
337
338        else:
339                error = 'You have to specifiy -b,--basename or -n,--net'
340                raise PxeConfig, error
341
342        binfo[START] = check_number(binfo[START], network_number)
343        binfo[END] = check_number(binfo[END], network_number)
344
345        if DEBUG:
346                print binfo
347
348        create_links(binfo)
349
350def set_padding(binfo, start, end):
351        """
352        find out th
353        """
354        if len(start) > 1 and start[0] == '0':
355                binfo[EQUALWIDTH] = True
356        elif end[0] == '0':
357                binfo[EQUALWIDTH] = True
358
359        if len(start) == len(end):
360                a =1   
361               
362
363def parse_hostrange(binfo, arg):
364        """
365        Parse if arg is of format <digit-digit>, if it starts
366        with a zero (0) then set EQUALWIDTH
367        """
368        str = 'parse_hostrange %s' %(arg)
369        verbose(str)
370
371        l = arg.split('-')
372        if len(l) < 2:
373                error =  'hostrange syntax not valid: %s (number-number)' %(arg)
374                raise PxeConfig, error
375
376        start = l[0]
377        end = l[1]
378
379
380        binfo[START] = check_number(start, False)
381        binfo[END] = check_number(end, False)
382       
383def check_args(argv, binfo):
384        """
385        This function parses the command line options and returns the rest as
386        an list of hostnames:
387        argv     : a list of command line options.
388        binfo    : returning a dict with the netinfo. if used non-interactively
389        hostnames: the rest of the command lines options that are not-parseble.
390        """
391        try:
392                opts, args = getopt.gnu_getopt(argv[1:], SHORTOPT_LIST, LONGOPT_LIST)
393        except getopt.error, detail:
394                print __doc__
395                print detail
396                sys.exit(1)
397       
398        global DEBUG
399        global VERBOSE
400
401        # if nothing is specified then print usage and exit
402        #
403        if not opts and not args:
404                print __doc__
405                sys.exit(1)
406
407        # Check given options
408        #
409        for opt,value in opts:
410                       
411                if opt in ['-b', '--basename']:
412                        binfo[BASENAME] = value
413                       
414                elif opt in ['--debug']:
415                        DEBUG = 1
416                       
417                elif opt in ['-e', '--end']:
418                        binfo[END] = value
419                       
420                elif opt in ['-f', '--file']:
421                        binfo[FILENAME] = value
422                       
423                elif opt in ['-h', '--help']:
424                        print __doc__
425                        sys.exit(0)
426
427                elif opt in ['-i', '--interactive']:
428                        interactive(binfo)
429                        sys.exit(0)
430
431                elif opt in ['-n', '--net']:
432                        binfo[NETWORK] = convert_network(value)
433                       
434                elif opt in ['-r', '--remove']:
435                        binfo[REMOVE] = 1
436                       
437                elif opt in ['-s', '--start']:
438                        binfo[START] = value
439
440                elif opt in ['-w', '--equal-width']:
441                        binfo[EQUALWIDTH] = True
442
443                elif opt in ['-v', '--verbose']:
444                        VERBOSE = 1
445
446                elif opt in ['-H', '--host-range']:
447                        parse_hostrange(binfo, value)
448                       
449                elif opt in ['-V', '--version']:
450                        print VERSION
451                        sys.exit(0)
452
453        check_command_line(binfo, args)
454
455def host_2_net(hosts, binfo):
456        """
457        Convert hostname(s) to a net address that can be handled by manage_links function
458        """
459        for host in hosts:
460                try:
461                        addr = socket.gethostbyname(host)
462                except socket.error,detail:
463                        error =  '%s not an valid hostname: %s' %(host,detail)
464                        raise PxeConfig, error
465                       
466                net = string.splitfields(addr, '.')
467                cnet = string.joinfields(net[0:3], '.')
468
469                binfo[NETWORK] = convert_network(cnet)
470                binfo[START] = int(net[3])
471                binfo[END] =  int(net[3])
472                manage_links(binfo)
473
474def base_2_net(binfo):
475        """
476        Construct hostname(s) from the supplied basename and start and end numbers
477        """
478        start = binfo[START]
479        end = binfo[END]
480
481        if start > end:
482                error = '%d >= %d : start value is greater then end value' %(start, end)
483                raise PxeConfig, error
484
485        if binfo[EQUALWIDTH]:
486                width = len(str(end))
487
488        hostnames = list()
489        for i in xrange(start, end + 1):
490                if binfo[EQUALWIDTH]:
491                        hostname = '%s%0*d' %(binfo[BASENAME], width, i)
492                else:
493                        hostname = '%s%d' %(binfo[BASENAME], i)
494
495                if DEBUG:
496                        print 'host = %s, Basename = %s, number = %d' %(hostname, binfo[BASENAME], i)
497                hostnames.append(hostname)
498
499        host_2_net(hostnames,binfo)
500               
501def main():
502        # A dictionary holding the boot info
503        #
504        global DEBUG
505        global PXE_CONF_DIR
506       
507        bootinfo = {}
508        bootinfo[REMOVE] = False
509        bootinfo[EQUALWIDTH] = False
510       
511        configfile = '@pxeconfig_conf@'
512        settings = ReadConfig(configfile)
513       
514        try:
515                PXE_CONF_DIR = settings['pxe_config_dir']
516                if not DEBUG:
517                        DEBUG = int(settings['debug'])
518
519        except KeyError:
520                pass
521
522        PXE_CONF_DIR = os.path.realpath(PXE_CONF_DIR)
523        if not os.path.isdir(PXE_CONF_DIR):
524                print 'pxeconfig directory: %s does not exists' %(PXE_CONF_DIR)
525                sys.exit(1)
526
527        try:
528                check_args(sys.argv, bootinfo)
529        except PxeConfig, detail:
530                print detail
531                sys.exit(1)
532       
533if __name__ == '__main__':
534        main()
Note: See TracBrowser for help on using the repository browser.