fix regex that identifies which device to attempt to mount for removable
[bootmanager.git] / source / steps / ReadNodeConfiguration.py
1 # Copyright (c) 2003 Intel Corporation
2 # All rights reserved.
3
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7
8 #     * Redistributions of source code must retain the above copyright
9 #       notice, this list of conditions and the following disclaimer.
10
11 #     * Redistributions in binary form must reproduce the above
12 #       copyright notice, this list of conditions and the following
13 #       disclaimer in the documentation and/or other materials provided
14 #       with the distribution.
15
16 #     * Neither the name of the Intel Corporation nor the names of its
17 #       contributors may be used to endorse or promote products derived
18 #       from this software without specific prior written permission.
19
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
24 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 # EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
33 # YOUR JURISDICTION. It is licensee's responsibility to comply with any
34 # export regulations applicable in licensee's jurisdiction. Under
35 # CURRENT (May 2000) U.S. export regulations this software is eligible
36 # for export from the U.S. and can be downloaded by or otherwise
37 # exported or reexported worldwide EXCEPT to U.S. embargoed destinations
38 # which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
39 # Afghanistan and any other country to which the U.S. has embargoed
40 # goods and services.
41
42
43 import sys, os, traceback
44 import string
45 import socket
46 import re
47
48 import utils
49 from Exceptions import *
50 import BootServerRequest
51 import BootAPI
52 import StartDebug
53 import notify_messages
54 import UpdateBootStateWithPLC
55
56
57 # two possible names of the configuration files
58 NEW_CONF_FILE_NAME= "plnode.txt"
59 OLD_CONF_FILE_NAME= "planet.cnf"
60
61
62 def Run( vars, log ):   
63     """
64     read the machines node configuration file, which contains
65     the node key and the node_id for this machine.
66     
67     these files can exist in several different locations with
68     several different names. Below is the search order:
69
70     filename      floppy   flash    cd
71     plnode.txt      1        2      4 (/usr/boot), 5 (/usr)
72     planet.cnf      3
73
74     The locations will be searched in the above order, plnode.txt
75     will be checked first, then planet.cnf. Flash devices will only
76     be searched on 3.0 cds.
77
78     Because some of the earlier
79     boot cds don't validate the configuration file (which results
80     in a file named /tmp/planet-clean.cnf), and some do, lets
81     bypass this, and mount and attempt to read in the conf
82     file ourselves. If it doesn't exist, we cannot continue, and a
83     BootManagerException will be raised. If the configuration file is found
84     and read, return 1.
85
86     Expect the following variables from the store:
87     BOOT_CD_VERSION          A tuple of the current bootcd version
88     SUPPORT_FILE_DIR         directory on the boot servers containing
89                              scripts and support files
90     
91     Sets the following variables from the configuration file:
92     WAS_NODE_ID_IN_CONF         Set to 1 if the node id was in the conf file
93     WAS_NODE_KEY_IN_CONF         Set to 1 if the node key was in the conf file
94     NONE_ID                     The db node_id for this machine
95     NODE_KEY                    The key for this node
96     NETWORK_SETTINGS            A dictionary of the values from the network
97                                 configuration file. keys set:
98                                    method
99                                    ip        
100                                    mac       
101                                    gateway   
102                                    network   
103                                    broadcast 
104                                    netmask   
105                                    dns1      
106                                    dns2      
107                                    hostname  
108                                    domainname
109
110     the mac address is read from the machine unless it exists in the
111     configuration file.
112     """
113
114     log.write( "\n\nStep: Reading node configuration file.\n" )
115
116
117     # make sure we have the variables we need
118     try:
119         BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
120         if BOOT_CD_VERSION == "":
121             raise ValueError, "BOOT_CD_VERSION"
122
123         SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
124         if SUPPORT_FILE_DIR == None:
125             raise ValueError, "SUPPORT_FILE_DIR"
126
127     except KeyError, var:
128         raise BootManagerException, "Missing variable in vars: %s\n" % var
129     except ValueError, var:
130         raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
131
132
133     NETWORK_SETTINGS= {}
134     NETWORK_SETTINGS['method']= "dhcp"
135     NETWORK_SETTINGS['ip']= ""
136     NETWORK_SETTINGS['mac']= ""
137     NETWORK_SETTINGS['gateway']= ""
138     NETWORK_SETTINGS['network']= ""
139     NETWORK_SETTINGS['broadcast']= ""
140     NETWORK_SETTINGS['netmask']= ""
141     NETWORK_SETTINGS['dns1']= ""
142     NETWORK_SETTINGS['dns2']= ""
143     NETWORK_SETTINGS['hostname']= "localhost"
144     NETWORK_SETTINGS['domainname']= "localdomain"
145     vars['NETWORK_SETTINGS']= NETWORK_SETTINGS
146
147     vars['NODE_ID']= 0
148     vars['NODE_KEY']= ""
149
150     vars['WAS_NODE_ID_IN_CONF']= 0
151     vars['WAS_NODE_KEY_IN_CONF']= 0
152
153     # for any devices that need to be mounted to get the configuration
154     # file, mount them here.
155     mount_point= "/tmp/conffilemount"
156     utils.makedirs( mount_point )
157
158     old_conf_file_contents= None
159     conf_file_contents= None
160     
161     
162     # 1. check the regular floppy device
163     log.write( "Checking standard floppy disk for plnode.txt file.\n" )
164
165     log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
166     utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
167                          % mount_point, log )
168
169     conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
170     
171     log.write( "Checking for existance of %s\n" % conf_file_path )
172     if os.access( conf_file_path, os.R_OK ):
173         try:
174             conf_file= file(conf_file_path,"r")
175             conf_file_contents= conf_file.read()
176             conf_file.close()
177             log.write( "Read in contents of file %s\n" % conf_file_path )
178         except IOError, e:
179             log.write( "Unable to read file %s\n" % conf_file_path )
180             pass
181
182         utils.sysexec_noerr( "umount %s" % mount_point, log )
183         if __parse_configuration_file( vars, log, conf_file_contents):
184             return 1
185         else:
186             raise BootManagerException( "Found configuration file plnode.txt " \
187                                         "on floppy, but was unable to parse it." )
188
189
190     # try the old file name, same device. its actually number 3 on the search
191     # order, but do it now to save mounting/unmounting the disk twice.
192     # try to parse it later...
193     conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
194
195     log.write( "Checking for existance of %s (used later)\n" % conf_file_path )
196     if os.access( conf_file_path, os.R_OK ):
197         try:
198             old_conf_file= file(conf_file_path,"r")
199             old_conf_file_contents= old_conf_file.read()
200             old_conf_file.close()
201             log.write( "Read in contents of file %s\n" % conf_file_path )
202         except IOError, e:
203             log.write( "Unable to read file %s\n" % conf_file_path )
204             pass
205         
206     utils.sysexec_noerr( "umount %s" % mount_point, log )
207
208
209
210     if BOOT_CD_VERSION[0] == 3:
211         # 2. check flash devices on 3.0 based cds
212         log.write( "Checking flash devices for plnode.txt file.\n" )
213
214         # this is done the same way the 3.0 cds do it, by attempting
215         # to mount and sd*1 devices that are removable
216         devices= os.listdir("/sys/block/")
217
218         for device in devices:
219             if device[:2] != "sd":
220                 log.write( "Skipping non-scsi device %s\n" % device )
221                 continue
222
223             # test removable
224             removable_file_path= "/sys/block/%s/removable" % device
225             try:
226                 removable= int(file(removable_file_path,"r").read().strip())
227             except ValueError, e:
228                 continue
229             except IOError, e:
230                 continue
231
232             if not removable:
233                 log.write( "Skipping non-removable device %s\n" % device )
234                 continue
235
236             log.write( "Checking removable device %s\n" % device )
237
238             partitions= file("/proc/partitions", "r")
239             for line in partitions:
240                 if not re.search("%s[0-9]+$" % device, line):
241                     continue
242
243                 try:
244                     # major minor  #blocks  name
245                     parts= string.split(line)
246
247                     # ok, try to mount it and see if we have a conf file.
248                     full_device= "/dev/%s" % parts[3]
249                 except IndexError, e:
250                     log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
251                     continue
252
253                 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
254                 try:
255                     utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
256                                    % (full_device,mount_point), log )
257                 except BootManagerException, e:
258                     log.write( "Unable to mount, trying next partition\n" )
259                     continue
260
261                 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
262
263                 log.write( "Checking for existance of %s\n" % conf_file_path )
264                 if os.access( conf_file_path, os.R_OK ):
265                     try:
266                         conf_file= file(conf_file_path,"r")
267                         conf_file_contents= conf_file.read()
268                         conf_file.close()
269                         log.write( "Read in contents of file %s\n" % \
270                                    conf_file_path )
271                     except IOError, e:
272                         log.write( "Unable to read file %s\n" % conf_file_path )
273                         pass
274
275                 utils.sysexec_noerr( "umount %s" % mount_point, log )
276                 if __parse_configuration_file( vars, log, conf_file_contents):
277                     return 1
278                 else:
279                     raise BootManagerException("Found configuration file plnode.txt " \
280                                                "on floppy, but was unable to parse it.")
281
282
283             
284     # 3. check standard floppy disk for old file name planet.cnf
285     log.write( "Checking standard floppy disk for planet.cnf file " \
286                "(from earlier.\n" )
287
288     if old_conf_file_contents:
289         if __parse_configuration_file( vars, log, old_conf_file_contents):
290             return 1
291         else:
292             raise BootManagerException( "Found configuration file planet.cnf " \
293                                         "on floppy, but was unable to parse it." )
294
295
296     # 4. check for plnode.txt in /usr/boot (mounted already)
297     log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
298     
299     conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
300
301     log.write( "Checking for existance of %s\n" % conf_file_path )
302     if os.access(conf_file_path,os.R_OK):
303         try:
304             conf_file= file(conf_file_path,"r")
305             conf_file_contents= conf_file.read()
306             conf_file.close()
307             log.write( "Read in contents of file %s\n" % conf_file_path )
308         except IOError, e:
309             log.write( "Unable to read file %s\n" % conf_file_path )
310             pass
311     
312         if __parse_configuration_file( vars, log, conf_file_contents):            
313             return 1
314         else:
315             raise BootManagerException( "Found configuration file plnode.txt " \
316                                         "in /usr/boot, but was unable to parse it.")
317
318
319
320     # 5. check for plnode.txt in /usr (mounted already)
321     log.write( "Checking /usr (cd) for plnode.txt file.\n" )
322     
323     conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
324
325     log.write( "Checking for existance of %s\n" % conf_file_path )
326     if os.access(conf_file_path,os.R_OK):
327         try:
328             conf_file= file(conf_file_path,"r")
329             conf_file_contents= conf_file.read()
330             conf_file.close()
331             log.write( "Read in contents of file %s\n" % conf_file_path )
332         except IOError, e:
333             log.write( "Unable to read file %s\n" % conf_file_path )
334             pass    
335     
336         if __parse_configuration_file( vars, log, conf_file_contents):            
337             return 1
338         else:
339             raise BootManagerException( "Found configuration file plnode.txt " \
340                                         "in /usr, but was unable to parse it.")
341
342
343     raise BootManagerException, "Unable to find and read a node configuration file."
344     
345
346
347
348 def __parse_configuration_file( vars, log, file_contents ):
349     """
350     parse a configuration file, set keys in var NETWORK_SETTINGS
351     in vars (see comment for function ReadNodeConfiguration). this
352     also reads the mac address from the machine if successful parsing
353     of the configuration file is completed.
354     """
355
356     BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
357     SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
358     NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
359     
360     if file_contents is None:
361         log.write( "__parse_configuration_file called with no file contents\n" )
362         return 0
363     
364     try:
365         line_num= 0
366         for line in file_contents.split("\n"):
367
368             line_num = line_num + 1
369             
370             # if its a comment or a whitespace line, ignore
371             if line[:1] == "#" or string.strip(line) == "":
372                 continue
373
374             # file is setup as name="value" pairs
375             parts= string.split(line,"=")
376             if len(parts) != 2:
377                 log.write( "Invalid line %d in configuration file:\n" % line_num )
378                 log.write( line + "\n" )
379                 return 0
380
381             name= string.strip(parts[0])
382             value= string.strip(parts[1])
383
384             # make sure value starts and ends with
385             # single or double quotes
386             quotes= value[0] + value[len(value)-1]
387             if quotes != "''" and quotes != '""':
388                 log.write( "Invalid line %d in configuration file:\n" % line_num )
389                 log.write( line + "\n" )
390                 return 0
391
392             # get rid of the quotes around the value
393             value= string.strip(value[1:len(value)-1])
394
395             if name == "NODE_ID":
396                 try:
397                     vars['NODE_ID']= int(value)
398                     vars['WAS_NODE_ID_IN_CONF']= 1
399                 except ValueError, e:
400                     log.write( "Non-numeric node_id in configuration file.\n" )
401                     return 0
402
403             if name == "NODE_KEY":
404                 vars['NODE_KEY']= value
405                 vars['WAS_NODE_KEY_IN_CONF']= 1
406
407             if name == "IP_METHOD":
408                 value= string.lower(value)
409                 if value != "static" and value != "dhcp":
410                     log.write( "Invalid IP_METHOD in configuration file:\n" )
411                     log.write( line + "\n" )
412                     return 0
413                 NETWORK_SETTINGS['method']= value.strip()
414
415             if name == "IP_ADDRESS":
416                 NETWORK_SETTINGS['ip']= value.strip()
417
418             if name == "IP_GATEWAY":
419                 NETWORK_SETTINGS['gateway']= value.strip()
420
421             if name == "IP_NETMASK":
422                 NETWORK_SETTINGS['netmask']= value.strip()
423
424             if name == "IP_NETADDR":
425                 NETWORK_SETTINGS['network']= value.strip()
426
427             if name == "IP_BROADCASTADDR":
428                 NETWORK_SETTINGS['broadcast']= value.strip()
429
430             if name == "IP_DNS1":
431                 NETWORK_SETTINGS['dns1']= value.strip()
432
433             if name == "IP_DNS2":
434                 NETWORK_SETTINGS['dns2']= value.strip()
435
436             if name == "HOST_NAME":
437                 NETWORK_SETTINGS['hostname']= string.lower(value)
438
439             if name == "DOMAIN_NAME":
440                 NETWORK_SETTINGS['domainname']= string.lower(value)
441
442             if name == "NET_DEVICE":
443                 NETWORK_SETTINGS['mac']= string.upper(value)
444                 
445
446     except IndexError, e:
447         log.write( "Unable to parse configuration file\n" )
448         return 0
449
450     # now if we are set to dhcp, clear out any fields
451     # that don't make sense
452     if NETWORK_SETTINGS["method"] == "dhcp":
453         NETWORK_SETTINGS["ip"]= ""
454         NETWORK_SETTINGS["gateway"]= ""     
455         NETWORK_SETTINGS["netmask"]= ""
456         NETWORK_SETTINGS["network"]= ""
457         NETWORK_SETTINGS["broadcast"]= ""
458         NETWORK_SETTINGS["dns1"]= ""
459         NETWORK_SETTINGS["dns2"]= ""
460
461     log.write("Successfully read and parsed node configuration file.\n" )
462
463     # if the mac wasn't specified, read it in from the system.
464     if NETWORK_SETTINGS["mac"] == "":
465         device= "eth0"
466         mac_addr= utils.get_mac_from_interface(device)
467
468         if mac_addr is None:
469             log.write( "Could not get mac address for device eth0.\n" )
470             return 0
471
472         NETWORK_SETTINGS["mac"]= string.upper(mac_addr)
473
474         log.write( "Got mac address %s for device %s\n" %
475                    (NETWORK_SETTINGS["mac"],device) )
476         
477
478     # now, if the conf file didn't contain a node id, post the mac address
479     # to plc to get the node_id value
480     if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
481         log.write( "Configuration file does not contain the node_id value.\n" )
482         log.write( "Querying PLC for node_id.\n" )
483
484         bs_request= BootServerRequest.BootServerRequest()
485         
486         postVars= {"mac_addr" : NETWORK_SETTINGS["mac"]}
487         result= bs_request.DownloadFile( "%s/getnodeid.php" %
488                                          SUPPORT_FILE_DIR,
489                                          None, postVars, 1, 1,
490                                          "/tmp/node_id")
491         if result == 0:
492             log.write( "Unable to make request to get node_id.\n" )
493             return 0
494
495         try:
496             node_id_file= file("/tmp/node_id","r")
497             node_id= string.strip(node_id_file.read())
498             node_id_file.close()
499         except IOError:
500             log.write( "Unable to read node_id from /tmp/node_id\n" )
501             return 0
502
503         try:
504             node_id= int(string.strip(node_id))
505         except ValueError:
506             log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
507             return 0
508
509         if node_id == -1:
510             log.write( "Got node_id, but it returned -1\n\n" )
511
512             log.write( "------------------------------------------------------\n" )
513             log.write( "This indicates that this node could not be identified\n" )
514             log.write( "by PLC. You will need to add the node to your site,\n" )
515             log.write( "and regenerate the network configuration file.\n" )
516             log.write( "See the Technical Contact guide for node setup\n" )
517             log.write( "procedures.\n\n" )
518             log.write( "Boot process canceled until this is completed.\n" )
519             log.write( "------------------------------------------------------\n" )
520             
521             cancel_boot_flag= "/tmp/CANCEL_BOOT"
522             # this will make the initial script stop requesting scripts from PLC
523             utils.sysexec( "touch %s" % cancel_boot_flag, log )
524
525             return 0
526
527         log.write( "Got node_id from PLC: %s\n" % str(node_id) )
528         vars['NODE_ID']= node_id
529
530
531
532     if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
533         log.write( "Configuration file does not contain a node_key value.\n" )
534         log.write( "Using boot nonce instead.\n" )
535
536         # 3.x cds stored the file in /tmp/nonce in ascii form, so they
537         # can be read and used directly. 2.x cds stored in the same place
538         # but in binary form, so we need to convert it to ascii the same
539         # way the old boot scripts did so it matches whats in the db
540         # (php uses bin2hex, 
541         if BOOT_CD_VERSION[0] == 2:
542             read_mode= "rb"
543         else:
544             read_mode= "r"
545             
546         try:
547             nonce_file= file("/tmp/nonce",read_mode)
548             nonce= nonce_file.read()
549             nonce_file.close()
550         except IOError:
551             log.write( "Unable to read nonce from /tmp/nonce\n" )
552             return 0
553
554         if BOOT_CD_VERSION[0] == 2:
555             nonce= nonce.encode('hex')
556
557             # there is this nice bug in the php that currently accepts the
558             # nonce for the old scripts, in that if the nonce contains
559             # null chars (2.x cds sent as binary), then
560             # the nonce is truncated. so, do the same here, truncate the nonce
561             # at the first null ('00'). This could leave us with an empty string.
562             nonce_len= len(nonce)
563             for byte_index in range(0,nonce_len,2):
564                 if nonce[byte_index:byte_index+2] == '00':
565                     nonce= nonce[:byte_index]
566                     break
567         else:
568             nonce= string.strip(nonce)
569
570         log.write( "Read nonce, using as key.\n" )
571         vars['NODE_KEY']= nonce
572         
573         
574     # at this point, we've read the network configuration file.
575     # if we were setup using dhcp, get this system's current ip
576     # address and update the vars key ip, because it
577     # is needed for future api calls.
578
579     # at the same time, we can check to make sure that the hostname
580     # in the configuration file matches the ip address. if it fails
581     # notify the owners
582
583     hostname= NETWORK_SETTINGS['hostname'] + "." + \
584               NETWORK_SETTINGS['domainname']
585
586     # set to 0 if any part of the hostname resolution check fails
587     hostname_resolve_ok= 1
588
589     # set to 0 if the above fails, and, we are using dhcp in which
590     # case we don't know the ip of this machine (without having to
591     # parse ifconfig or something). In that case, we won't be able
592     # to make api calls, so printing a message to the screen will
593     # have to suffice.
594     can_make_api_call= 1
595
596     log.write( "Checking that hostname %s resolves\n" % hostname )
597
598     # try a regular dns lookup first
599     try:
600         resolved_node_ip= socket.gethostbyname(hostname)
601     except socket.gaierror, e:
602         hostname_resolve_ok= 0
603         
604
605     if NETWORK_SETTINGS['method'] == "dhcp":
606         if hostname_resolve_ok:
607             NETWORK_SETTINGS['ip']= resolved_node_ip
608             node_ip= resolved_node_ip
609         else:
610             can_make_api_call= 0
611     else:
612         node_ip= NETWORK_SETTINGS['ip']
613
614     # make sure the dns lookup matches what the configuration file says
615     if hostname_resolve_ok:
616         if node_ip != resolved_node_ip:
617             log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
618                        (hostname,node_ip,resolved_node_ip) )
619             hostname_resolve_ok= 0
620         else:
621             log.write( "Hostname %s correctly resolves to %s:\n" %
622                        (hostname,node_ip) )
623
624         
625     vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
626
627     if not hostname_resolve_ok:
628         log.write( "Hostname does not resolve correctly, will not continue.\n" )
629
630         StartDebug.Run( vars, log )
631
632         if can_make_api_call:
633             log.write( "Notifying contacts of problem.\n" )
634
635             vars['BOOT_STATE']= 'dbg'
636             vars['STATE_CHANGE_NOTIFY']= 1
637             vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
638                                      notify_messages.MSG_HOSTNAME_NOT_RESOLVE
639             
640             UpdateBootStateWithPLC.Run( vars, log )
641                     
642         log.write( "\n\n" )
643         log.write( "The hostname and/or ip in the network configuration\n" )
644         log.write( "file do not resolve and match.\n" )
645         log.write( "Please make sure the hostname set in the network\n" )
646         log.write( "configuration file resolves to the ip also specified\n" )
647         log.write( "there.\n\n" )
648         log.write( "Debug mode is being started on this cd. When the above\n" )
649         log.write( "is corrected, reboot the machine to try again.\n" )
650         
651         raise BootManagerException, \
652               "Configured node hostname does not resolve."
653     
654     return 1