source: trunk/pxeconfig.in @ 105

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

pxeconfig.in:

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