source: trunk/pxeconfig.in @ 120

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

configure.in:

  • removed C-compiler requirement

debian/changelog:

  • version 2.0.0

pxeconfig.in:

  • working host-range with padding

pxeconfigd.in:

  • new version
  • Property keywords set to Id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 12.2 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 120 2008-04-11 08:48:46Z 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        print 'padding ', type(start_str), type(end_str)
365
366        start = check_number(start_str, False)
367        end = check_number(end_str, False)
368
369
370        print type(start_str), type(end_str)
371
372
373        if binfo[EQUALWIDTH][0] == True:
374                binfo[EQUALWIDTH][1] = len(end_str)
375
376        elif len(start_str) > 1 and start_str[0] == '0':
377                binfo[EQUALWIDTH] = [ True, len(end_str) ]
378
379        elif end_str[0] == '0':
380                binfo[EQUALWIDTH] = [ True, len(end_str) ]
381
382        binfo[START] = start
383        binfo[END] = end
384
385
386def parse_host_range(binfo, arg):
387        """
388        Parse if arg is of format <digit-digit>, if it starts
389        with a zero (0) then set EQUALWIDTH
390        """
391        str = 'parse_hostrange %s' %(arg)
392        verbose(str)
393
394        l = arg.split('-')
395        if len(l) < 2:
396                error =  'hostrange syntax not valid: %s (number-number)' %(arg)
397                raise PxeConfig, error
398
399        binfo[START] = l[0]
400        binfo[END] = l[1]
401
402       
403def parse_args(argv, binfo):
404        """
405        This function parses the command line options and returns the rest as
406        an list of hostnames:
407        argv     : a list of command line options.
408        binfo    : returning a dict with the netinfo. if used non-interactively
409        hostnames: the rest of the command lines options that are not-parseble.
410        """
411        try:
412                opts, args = getopt.gnu_getopt(argv[1:], SHORTOPT_LIST, LONGOPT_LIST)
413        except getopt.error, detail:
414                print __doc__
415                print detail
416                sys.exit(1)
417       
418        global DEBUG
419        global VERBOSE
420
421        # if nothing is specified then print usage and exit
422        #
423        if not opts and not args:
424                print __doc__
425                sys.exit(1)
426
427        # Check given options
428        #
429        for opt,value in opts:
430                       
431                if opt in ['-b', '--basename']:
432                        binfo[BASENAME] = value
433                       
434                elif opt in ['--debug']:
435                        DEBUG = 1
436                       
437                elif opt in ['-e', '--end']:
438                        binfo[END] = value
439                       
440                elif opt in ['-f', '--file']:
441                        binfo[FILENAME] = value
442                       
443                elif opt in ['-h', '--help']:
444                        print __doc__
445                        sys.exit(0)
446
447                elif opt in ['-i', '--interactive']:
448                        interactive(binfo)
449                        sys.exit(0)
450
451                elif opt in ['-n', '--net']:
452                        binfo[NETWORK] = convert_network(value)
453                       
454                elif opt in ['-r', '--remove']:
455                        binfo[REMOVE] = 1
456                       
457                elif opt in ['-s', '--start']:
458                        binfo[START] = value
459
460                elif opt in ['-w', '--equal-width']:
461                        binfo[EQUALWIDTH] = [True, 0]
462
463                elif opt in ['-v', '--verbose']:
464                        VERBOSE = 1
465
466                elif opt in ['-H', '--host-range']:
467                        parse_host_range(binfo, value)
468                       
469                elif opt in ['-V', '--version']:
470                        print VERSION
471                        sys.exit(0)
472
473        check_args(binfo, args)
474
475def host_2_net(hosts, binfo):
476        """
477        Convert hostname(s) to a net address that can be handled by manage_links function
478        """
479        for host in hosts:
480                try:
481                        addr = socket.gethostbyname(host)
482                except socket.error,detail:
483                        error =  '%s not an valid hostname: %s' %(host,detail)
484                        raise PxeConfig, error
485                       
486                net = string.splitfields(addr, '.')
487                cnet = string.joinfields(net[0:3], '.')
488
489                binfo[NETWORK] = convert_network(cnet)
490                binfo[START] = int(net[3])
491                binfo[END] =  int(net[3])
492                manage_links(binfo)
493
494def base_2_net(binfo):
495        """
496        Construct hostname(s) from the supplied basename and start and end numbers
497        """
498        start = binfo[START]
499        end = binfo[END]
500
501        if start > end:
502                error = '%d >= %d : start value is greater then end value' %(start, end)
503                raise PxeConfig, error
504
505        hostnames = list()
506        for i in xrange(start, end + 1):
507                if binfo[EQUALWIDTH][0] == True:
508                        hostname = '%s%0*d' %(binfo[BASENAME],  binfo[EQUALWIDTH][1], i)
509                else:
510                        hostname = '%s%d' %(binfo[BASENAME], i)
511
512                if DEBUG:
513                        print 'host = %s, Basename = %s, number = %d' %(hostname, binfo[BASENAME], i)
514                hostnames.append(hostname)
515
516        host_2_net(hostnames,binfo)
517               
518def main():
519        # A dictionary holding the boot info
520        #
521        global DEBUG
522        global PXE_CONF_DIR
523       
524        bootinfo = {}
525        bootinfo[REMOVE] = False
526        bootinfo[EQUALWIDTH] = [ False, 0 ]
527       
528        configfile = '@pxeconfig_conf@'
529        settings = ReadConfig(configfile)
530       
531        try:
532                PXE_CONF_DIR = settings['pxe_config_dir']
533                if not DEBUG:
534                        DEBUG = int(settings['debug'])
535
536        except KeyError:
537                pass
538
539        PXE_CONF_DIR = os.path.realpath(PXE_CONF_DIR)
540        if not os.path.isdir(PXE_CONF_DIR):
541                error =  'pxeconfig directory: %s does not exists' %(PXE_CONF_DIR)
542                raise PxeConfig, error
543
544        parse_args(sys.argv, bootinfo)
545       
546if __name__ == '__main__':
547        try:
548                main()
549        except PxeConfig, detail:
550                print detail
551                sys.exit(1)
Note: See TracBrowser for help on using the repository browser.