source: trunk/pxeconfig.in @ 121

Last change on this file since 121 was 121, checked in by bas, 13 years ago

pxeconfig.in:

  • removed obsolete print statements
  • Property keywords set to Id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 12.1 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 121 2008-04-11 08:50:21Z 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='2.0.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' %str
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        try:
242                n = int(number_str)
243        except ValueError, detail:
244                error = "%s : is not a valid number" %number_str
245                raise PxeConfig, error
246
247        if not network:
248                return n
249
250        # Check if it is a correct network value
251        #
252        if 0 <= n <= 255:
253                return n
254        else:
255                error = '%s is not a valid network number, must be between 0 and 255' %n
256                raise PxeConfig, error
257
258def interactive(binfo):
259        print __doc__
260       
261
262        while 1:
263                network = raw_input('Give network address (xxx.xxx.xxx): ')
264
265                try:
266                        naddr = convert_network(network)
267                        break
268                except PxeConfig, detail:
269                        print '%s : not a valid C-class network number' %(network)
270                        continue
271
272        while 1:
273                start = raw_input('Starting number: ')
274
275                try:
276                        start = check_number(start, True)
277                        break
278                except PxeConfig, detail:
279                        print detail
280                        continue
281               
282        while 1:
283                end = raw_input('Ending number: ')
284
285                try:
286                        end = check_number(end, True)
287                        break
288                except PxeConfig, detail:
289                        print detail
290                        continue
291       
292       
293        pxe_filename = select_pxe_configfile()
294       
295        binfo[NETWORK] = naddr
296        binfo[START] = start
297        binfo[END] = end
298        binfo[FILENAME] = pxe_filename
299       
300        if DEBUG:
301                print network, binfo
302
303        manage_links(binfo)
304
305def check_args(binfo, hostnames):
306        """
307        Do you we have the right and propper values
308        """
309        ### check_filename
310        #
311        str = 'check_args: '
312        verbose(str)   
313        try:
314                if not os.path.isfile(os.path.join(PXE_CONF_DIR, binfo[FILENAME])):
315                        error =  '%s: Filename does not exists' %binfo[FILENAME]
316                        raise Pxeconfig, detail
317        except KeyError, detail:
318                if binfo[REMOVE] :
319                        binfo[FILENAME] = 'Does not matter'
320                else:
321                        binfo[FILENAME] = select_pxe_configfile()
322
323        if hostnames:
324                host_2_net(hostnames, binfo)
325                sys.exit(0)
326               
327        if binfo.has_key(BASENAME) and binfo.has_key(NETWORK):
328                error =  "The option -n/--net and -b/--basename are mutually exclusive"
329                raise PxeConfig, error 
330
331        if binfo.has_key(BASENAME):
332                set_padding(binfo)
333                create_links =  base_2_net
334
335        elif binfo.has_key(NETWORK):
336                binfo[START] = check_number(binfo[START], True)
337                binfo[END] = check_number(binfo[END], True)
338                create_links = manage_links
339
340        else:
341                error = 'You have to specifiy -b,--basename or -n,--net'
342                raise PxeConfig, error
343
344        if DEBUG:
345                print binfo
346
347        create_links(binfo)
348
349def set_padding(binfo):
350        """
351        binfo          : boot info
352        network_number : must we check if  start,end values are
353                         valid network numbers
354        return:
355                - if equal_width is requested then the length will be set to end value
356                - if start value length > 1 and start with a zero (0), width is set to
357                  the end value
358                - if end value starts with a zero (0), width will be set to the end
359                  value
360        """
361        start_str = binfo[START]
362        end_str = binfo[END]
363
364        start = check_number(start_str, False)
365        end = check_number(end_str, False)
366
367        if binfo[EQUALWIDTH][0] == True:
368                binfo[EQUALWIDTH][1] = len(end_str)
369
370        elif len(start_str) > 1 and start_str[0] == '0':
371                binfo[EQUALWIDTH] = [ True, len(end_str) ]
372
373        elif end_str[0] == '0':
374                binfo[EQUALWIDTH] = [ True, len(end_str) ]
375
376        binfo[START] = start
377        binfo[END] = end
378
379
380def parse_host_range(binfo, arg):
381        """
382        Parse if arg is of format <digit-digit>, if it starts
383        with a zero (0) then set EQUALWIDTH
384        """
385        str = 'parse_hostrange %s' %(arg)
386        verbose(str)
387
388        l = arg.split('-')
389        if len(l) < 2:
390                error =  'hostrange syntax not valid: %s (number-number)' %(arg)
391                raise PxeConfig, error
392
393        binfo[START] = l[0]
394        binfo[END] = l[1]
395
396       
397def parse_args(argv, binfo):
398        """
399        This function parses the command line options and returns the rest as
400        an list of hostnames:
401        argv     : a list of command line options.
402        binfo    : returning a dict with the netinfo. if used non-interactively
403        hostnames: the rest of the command lines options that are not-parseble.
404        """
405        try:
406                opts, args = getopt.gnu_getopt(argv[1:], SHORTOPT_LIST, LONGOPT_LIST)
407        except getopt.error, detail:
408                print __doc__
409                print detail
410                sys.exit(1)
411       
412        global DEBUG
413        global VERBOSE
414
415        # if nothing is specified then print usage and exit
416        #
417        if not opts and not args:
418                print __doc__
419                sys.exit(1)
420
421        # Check given options
422        #
423        for opt,value in opts:
424                       
425                if opt in ['-b', '--basename']:
426                        binfo[BASENAME] = value
427                       
428                elif opt in ['--debug']:
429                        DEBUG = 1
430                       
431                elif opt in ['-e', '--end']:
432                        binfo[END] = value
433                       
434                elif opt in ['-f', '--file']:
435                        binfo[FILENAME] = value
436                       
437                elif opt in ['-h', '--help']:
438                        print __doc__
439                        sys.exit(0)
440
441                elif opt in ['-i', '--interactive']:
442                        interactive(binfo)
443                        sys.exit(0)
444
445                elif opt in ['-n', '--net']:
446                        binfo[NETWORK] = convert_network(value)
447                       
448                elif opt in ['-r', '--remove']:
449                        binfo[REMOVE] = 1
450                       
451                elif opt in ['-s', '--start']:
452                        binfo[START] = value
453
454                elif opt in ['-w', '--equal-width']:
455                        binfo[EQUALWIDTH] = [True, 0]
456
457                elif opt in ['-v', '--verbose']:
458                        VERBOSE = 1
459
460                elif opt in ['-H', '--host-range']:
461                        parse_host_range(binfo, value)
462                       
463                elif opt in ['-V', '--version']:
464                        print VERSION
465                        sys.exit(0)
466
467        check_args(binfo, args)
468
469def host_2_net(hosts, binfo):
470        """
471        Convert hostname(s) to a net address that can be handled by manage_links function
472        """
473        for host in hosts:
474                try:
475                        addr = socket.gethostbyname(host)
476                except socket.error,detail:
477                        error =  '%s not an valid hostname: %s' %(host,detail)
478                        raise PxeConfig, error
479                       
480                net = string.splitfields(addr, '.')
481                cnet = string.joinfields(net[0:3], '.')
482
483                binfo[NETWORK] = convert_network(cnet)
484                binfo[START] = int(net[3])
485                binfo[END] =  int(net[3])
486                manage_links(binfo)
487
488def base_2_net(binfo):
489        """
490        Construct hostname(s) from the supplied basename and start and end numbers
491        """
492        start = binfo[START]
493        end = binfo[END]
494
495        if start > end:
496                error = '%d >= %d : start value is greater then end value' %(start, end)
497                raise PxeConfig, error
498
499        hostnames = list()
500        for i in xrange(start, end + 1):
501                if binfo[EQUALWIDTH][0] == True:
502                        hostname = '%s%0*d' %(binfo[BASENAME],  binfo[EQUALWIDTH][1], i)
503                else:
504                        hostname = '%s%d' %(binfo[BASENAME], i)
505
506                if DEBUG:
507                        print 'host = %s, Basename = %s, number = %d' %(hostname, binfo[BASENAME], i)
508                hostnames.append(hostname)
509
510        host_2_net(hostnames,binfo)
511               
512def main():
513        # A dictionary holding the boot info
514        #
515        global DEBUG
516        global PXE_CONF_DIR
517       
518        bootinfo = {}
519        bootinfo[REMOVE] = False
520        bootinfo[EQUALWIDTH] = [ False, 0 ]
521       
522        configfile = '@pxeconfig_conf@'
523        settings = ReadConfig(configfile)
524       
525        try:
526                PXE_CONF_DIR = settings['pxe_config_dir']
527                if not DEBUG:
528                        DEBUG = int(settings['debug'])
529
530        except KeyError:
531                pass
532
533        PXE_CONF_DIR = os.path.realpath(PXE_CONF_DIR)
534        if not os.path.isdir(PXE_CONF_DIR):
535                error =  'pxeconfig directory: %s does not exists' %(PXE_CONF_DIR)
536                raise PxeConfig, error
537
538        parse_args(sys.argv, bootinfo)
539       
540if __name__ == '__main__':
541        try:
542                main()
543        except PxeConfig, detail:
544                print detail
545                sys.exit(1)
Note: See TracBrowser for help on using the repository browser.