6 # Copyright (c) 2003 Intel Corporation
9 # Copyright (c) 2004-2006 The Trustees of Princeton University
10 # All rights reserved.
11 # expected /proc/partitions format
13 import sys, os, traceback
19 from Exceptions import *
20 import BootServerRequest
22 import notify_messages
23 import UpdateRunLevelWithPLC
26 # two possible names of the configuration files
27 NEW_CONF_FILE_NAME= "plnode.txt"
28 OLD_CONF_FILE_NAME= "planet.cnf"
33 read the machines node configuration file, which contains
34 the node key and the node_id for this machine.
36 these files can exist in several different locations with
37 several different names. Below is the search order:
39 filename floppy flash ramdisk cd
40 plnode.txt 1 2 4 (/) 5 (/usr/boot), 6 (/usr)
43 The locations will be searched in the above order, plnode.txt
44 will be checked first, then planet.cnf. Flash devices will only
45 be searched on 3.0 cds.
47 Because some of the earlier
48 boot cds don't validate the configuration file (which results
49 in a file named /tmp/planet-clean.cnf), and some do, lets
50 bypass this, and mount and attempt to read in the conf
51 file ourselves. If it doesn't exist, we cannot continue, and a
52 BootManagerException will be raised. If the configuration file is found
55 Expect the following variables from the store:
57 Sets the following variables from the configuration file:
58 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
59 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
60 NONE_ID The db node_id for this machine
61 NODE_KEY The key for this node
62 INTERFACE_SETTINGS A dictionary of the values from the network
63 configuration file. keys set:
69 broadcast IP_BROADCASTADDR
74 domainname DOMAIN_NAME
77 iwconfig WLAN_IWCONFIG
79 the mac address is read from the machine unless it exists in the
83 log.write( "\n\nStep: Reading node configuration file.\n" )
86 # make sure we have the variables we need
88 INTERFACE_SETTINGS= {}
89 INTERFACE_SETTINGS['method']= "dhcp"
90 INTERFACE_SETTINGS['ip']= ""
91 INTERFACE_SETTINGS['mac']= ""
92 INTERFACE_SETTINGS['gateway']= ""
93 INTERFACE_SETTINGS['network']= ""
94 INTERFACE_SETTINGS['broadcast']= ""
95 INTERFACE_SETTINGS['netmask']= ""
96 INTERFACE_SETTINGS['dns1']= ""
97 INTERFACE_SETTINGS['dns2']= ""
98 INTERFACE_SETTINGS['hostname']= "localhost"
99 INTERFACE_SETTINGS['domainname']= "localdomain"
100 vars['INTERFACE_SETTINGS']= INTERFACE_SETTINGS
105 vars['WAS_NODE_ID_IN_CONF']= 0
106 vars['WAS_NODE_KEY_IN_CONF']= 0
108 vars['DISCONNECTED_OPERATION']= ''
110 # for any devices that need to be mounted to get the configuration
111 # file, mount them here.
112 mount_point= "/tmp/conffilemount"
113 utils.makedirs( mount_point )
115 old_conf_file_contents= None
116 conf_file_contents= None
119 # 1. check the regular floppy device
120 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
122 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
123 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
126 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
128 log.write( "Checking for existence of %s\n" % conf_file_path )
129 if os.access( conf_file_path, os.R_OK ):
131 conf_file= file(conf_file_path,"r")
132 conf_file_contents= conf_file.read()
134 log.write( "Read in contents of file %s\n" % conf_file_path )
136 log.write( "Unable to read file %s\n" % conf_file_path )
139 utils.sysexec_noerr( "umount %s" % mount_point, log )
140 if __parse_configuration_file( vars, log, conf_file_contents):
143 raise BootManagerException( "Found configuration file plnode.txt " \
144 "on floppy, but was unable to parse it." )
147 # try the old file name, same device. its actually number 3 on the search
148 # order, but do it now to save mounting/unmounting the disk twice.
149 # try to parse it later...
150 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
152 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
153 if os.access( conf_file_path, os.R_OK ):
155 old_conf_file= file(conf_file_path,"r")
156 old_conf_file_contents= old_conf_file.read()
157 old_conf_file.close()
158 log.write( "Read in contents of file %s\n" % conf_file_path )
160 log.write( "Unable to read file %s\n" % conf_file_path )
163 utils.sysexec_noerr( "umount %s" % mount_point, log )
165 # 2. check flash devices on 3.0 based cds
166 log.write( "Checking flash devices for plnode.txt file.\n" )
168 # this is done the same way the 3.0 cds do it, by attempting
169 # to mount and sd*1 devices that are removable
170 devices= os.listdir("/sys/block/")
172 for device in devices:
173 if device[:2] != "sd":
174 log.write( "Skipping non-scsi device %s\n" % device )
178 removable_file_path= "/sys/block/%s/removable" % device
180 removable= int(file(removable_file_path,"r").read().strip())
181 except ValueError, e:
187 log.write( "Skipping non-removable device %s\n" % device )
190 log.write( "Checking removable device %s\n" % device )
192 partitions= file("/proc/partitions", "r")
193 for line in partitions:
197 if not re.search("%s[0-9]*$" % device, line):
201 # major minor #blocks name
202 parts= string.split(line)
204 # ok, try to mount it and see if we have a conf file.
205 full_device= "/dev/%s" % parts[3]
206 except IndexError, e:
207 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
210 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
212 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
213 % (full_device,mount_point), log )
214 except BootManagerException, e:
215 log.write( "Unable to mount, trying next partition\n" )
218 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
220 log.write( "Checking for existence of %s\n" % conf_file_path )
221 if os.access( conf_file_path, os.R_OK ):
223 conf_file= file(conf_file_path,"r")
224 conf_file_contents= conf_file.read()
227 log.write( "Read in contents of file %s\n" % \
230 if __parse_configuration_file( vars, log, \
234 log.write( "Unable to read file %s\n" % conf_file_path )
236 utils.sysexec_noerr( "umount %s" % mount_point, log )
241 raise BootManagerException( \
242 "Found configuration file plnode.txt " \
243 "on floppy, but was unable to parse it.")
247 # 3. check standard floppy disk for old file name planet.cnf
248 log.write( "Checking standard floppy disk for planet.cnf file " \
249 "(for legacy nodes).\n" )
251 if old_conf_file_contents:
252 if __parse_configuration_file( vars, log, old_conf_file_contents):
255 raise BootManagerException( "Found configuration file planet.cnf " \
256 "on floppy, but was unable to parse it." )
259 # 4. check for plnode.txt in / (ramdisk)
260 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
262 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
264 log.write( "Checking for existence of %s\n" % conf_file_path )
265 if os.access(conf_file_path,os.R_OK):
267 conf_file= file(conf_file_path,"r")
268 conf_file_contents= conf_file.read()
270 log.write( "Read in contents of file %s\n" % conf_file_path )
272 log.write( "Unable to read file %s\n" % conf_file_path )
275 if __parse_configuration_file( vars, log, conf_file_contents):
278 raise BootManagerException( "Found configuration file plnode.txt " \
279 "in /, but was unable to parse it.")
282 # 5. check for plnode.txt in /usr/boot (mounted already)
283 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
285 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
287 log.write( "Checking for existence of %s\n" % conf_file_path )
288 if os.access(conf_file_path,os.R_OK):
290 conf_file= file(conf_file_path,"r")
291 conf_file_contents= conf_file.read()
293 log.write( "Read in contents of file %s\n" % conf_file_path )
295 log.write( "Unable to read file %s\n" % conf_file_path )
298 if __parse_configuration_file( vars, log, conf_file_contents):
301 raise BootManagerException( "Found configuration file plnode.txt " \
302 "in /usr/boot, but was unable to parse it.")
306 # 6. check for plnode.txt in /usr (mounted already)
307 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
309 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
311 log.write( "Checking for existence of %s\n" % conf_file_path )
312 if os.access(conf_file_path,os.R_OK):
314 conf_file= file(conf_file_path,"r")
315 conf_file_contents= conf_file.read()
317 log.write( "Read in contents of file %s\n" % conf_file_path )
319 log.write( "Unable to read file %s\n" % conf_file_path )
322 if __parse_configuration_file( vars, log, conf_file_contents):
325 raise BootManagerException( "Found configuration file plnode.txt " \
326 "in /usr, but was unable to parse it.")
329 raise BootManagerException, "Unable to find and read a node configuration file."
334 def __parse_configuration_file( vars, log, file_contents ):
336 parse a configuration file, set keys in var INTERFACE_SETTINGS
337 in vars (see comment for function ReadNodeConfiguration). this
338 also reads the mac address from the machine if successful parsing
339 of the configuration file is completed.
342 INTERFACE_SETTINGS= vars["INTERFACE_SETTINGS"]
344 if file_contents is None:
345 log.write( "__parse_configuration_file called with no file contents\n" )
350 for line in file_contents.split("\n"):
352 line_num = line_num + 1
354 # if its a comment or a whitespace line, ignore
355 if line[:1] == "#" or string.strip(line) == "":
358 # file is setup as name="value" pairs
359 parts= string.split(line, "=", 1)
361 name= string.strip(parts[0])
362 value= string.strip(parts[1])
364 # make sure value starts and ends with
365 # single or double quotes
366 quotes= value[0] + value[len(value)-1]
367 if quotes != "''" and quotes != '""':
368 log.write( "Invalid line %d in configuration file:\n" % line_num )
369 log.write( line + "\n" )
372 # get rid of the quotes around the value
373 value= string.strip(value[1:len(value)-1])
375 if name == "NODE_ID":
377 vars['NODE_ID']= int(value)
378 vars['WAS_NODE_ID_IN_CONF']= 1
379 except ValueError, e:
380 log.write( "Non-numeric node_id in configuration file.\n" )
383 if name == "NODE_KEY":
384 vars['NODE_KEY']= value
385 vars['WAS_NODE_KEY_IN_CONF']= 1
387 if name == "IP_METHOD":
388 value= string.lower(value)
389 if value != "static" and value != "dhcp":
390 log.write( "Invalid IP_METHOD in configuration file:\n" )
391 log.write( line + "\n" )
393 INTERFACE_SETTINGS['method']= value.strip()
395 if name == "IP_ADDRESS":
396 INTERFACE_SETTINGS['ip']= value.strip()
398 if name == "IP_GATEWAY":
399 INTERFACE_SETTINGS['gateway']= value.strip()
401 if name == "IP_NETMASK":
402 INTERFACE_SETTINGS['netmask']= value.strip()
404 if name == "IP_NETADDR":
405 INTERFACE_SETTINGS['network']= value.strip()
407 if name == "IP_BROADCASTADDR":
408 INTERFACE_SETTINGS['broadcast']= value.strip()
410 if name == "IP_DNS1":
411 INTERFACE_SETTINGS['dns1']= value.strip()
413 if name == "IP_DNS2":
414 INTERFACE_SETTINGS['dns2']= value.strip()
416 if name == "HOST_NAME":
417 INTERFACE_SETTINGS['hostname']= string.lower(value)
419 if name == "DOMAIN_NAME":
420 INTERFACE_SETTINGS['domainname']= string.lower(value)
422 if name == "NET_DEVICE":
423 INTERFACE_SETTINGS['mac']= string.upper(value)
425 if name == "DISCONNECTED_OPERATION":
426 vars['DISCONNECTED_OPERATION']= value.strip()
428 except IndexError, e:
429 log.write( "Unable to parse configuration file\n" )
432 # now if we are set to dhcp, clear out any fields
433 # that don't make sense
434 if INTERFACE_SETTINGS["method"] == "dhcp":
435 INTERFACE_SETTINGS["ip"]= ""
436 INTERFACE_SETTINGS["gateway"]= ""
437 INTERFACE_SETTINGS["netmask"]= ""
438 INTERFACE_SETTINGS["network"]= ""
439 INTERFACE_SETTINGS["broadcast"]= ""
440 INTERFACE_SETTINGS["dns1"]= ""
441 INTERFACE_SETTINGS["dns2"]= ""
443 log.write("Successfully read and parsed node configuration file.\n" )
445 # if the mac wasn't specified, read it in from the system.
446 if INTERFACE_SETTINGS["mac"] == "":
448 mac_addr= utils.get_mac_from_interface(device)
451 log.write( "Could not get mac address for device eth0.\n" )
454 INTERFACE_SETTINGS["mac"]= string.upper(mac_addr)
456 log.write( "Got mac address %s for device %s\n" %
457 (INTERFACE_SETTINGS["mac"],device) )
460 # now, if the conf file didn't contain a node id, post the mac address
461 # to plc to get the node_id value
462 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
463 log.write( "Configuration file does not contain the node_id value.\n" )
464 log.write( "Querying PLC for node_id.\n" )
466 bs_request= BootServerRequest.BootServerRequest(vars)
468 postVars= {"mac_addr" : INTERFACE_SETTINGS["mac"]}
469 result= bs_request.DownloadFile( "/boot/getnodeid.php",
470 None, postVars, 1, 1,
473 log.write( "Unable to make request to get node_id.\n" )
477 node_id_file= file("/tmp/node_id","r")
478 node_id= string.strip(node_id_file.read())
481 log.write( "Unable to read node_id from /tmp/node_id\n" )
485 node_id= int(string.strip(node_id))
487 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
491 log.write( "Got node_id, but it returned -1\n\n" )
493 log.write( "------------------------------------------------------\n" )
494 log.write( "This indicates that this node could not be identified\n" )
495 log.write( "by PLC. You will need to add the node to your site,\n" )
496 log.write( "and regenerate the network configuration file.\n" )
497 log.write( "See the Technical Contact guide for node setup\n" )
498 log.write( "procedures.\n\n" )
499 log.write( "Boot process canceled until this is completed.\n" )
500 log.write( "------------------------------------------------------\n" )
502 cancel_boot_flag= "/tmp/CANCEL_BOOT"
503 # this will make the initial script stop requesting scripts from PLC
504 utils.sysexec( "touch %s" % cancel_boot_flag, log )
508 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
509 vars['NODE_ID']= node_id
513 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
514 log.write( "Configuration file does not contain a node_key value.\n" )
515 log.write( "Using boot nonce instead.\n" )
517 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
518 # can be read and used directly. 2.x cds stored in the same place
519 # but in binary form, so we need to convert it to ascii the same
520 # way the old boot scripts did so it matches whats in the db
525 nonce_file= file("/tmp/nonce",read_mode)
526 nonce= nonce_file.read()
529 log.write( "Unable to read nonce from /tmp/nonce\n" )
532 nonce= string.strip(nonce)
534 log.write( "Read nonce, using as key.\n" )
535 vars['NODE_KEY']= nonce
538 # at this point, we've read the network configuration file.
539 # if we were setup using dhcp, get this system's current ip
540 # address and update the vars key ip, because it
541 # is needed for future api calls.
543 # at the same time, we can check to make sure that the hostname
544 # in the configuration file matches the ip address. if it fails
547 hostname= INTERFACE_SETTINGS['hostname'] + "." + \
548 INTERFACE_SETTINGS['domainname']
550 # set to 0 if any part of the hostname resolution check fails
551 hostname_resolve_ok= 1
553 # set to 0 if the above fails, and, we are using dhcp in which
554 # case we don't know the ip of this machine (without having to
555 # parse ifconfig or something). In that case, we won't be able
556 # to make api calls, so printing a message to the screen will
560 log.write( "Checking that hostname %s resolves\n" % hostname )
562 # try a regular dns lookup first
564 resolved_node_ip= socket.gethostbyname(hostname)
565 except socket.gaierror, e:
566 hostname_resolve_ok= 0
569 if INTERFACE_SETTINGS['method'] == "dhcp":
570 if hostname_resolve_ok:
571 INTERFACE_SETTINGS['ip']= resolved_node_ip
572 node_ip= resolved_node_ip
576 node_ip= INTERFACE_SETTINGS['ip']
578 # make sure the dns lookup matches what the configuration file says
579 if hostname_resolve_ok:
580 if node_ip != resolved_node_ip:
581 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
582 (hostname,node_ip,resolved_node_ip) )
583 hostname_resolve_ok= 0
585 log.write( "Hostname %s correctly resolves to %s:\n" %
589 vars["INTERFACE_SETTINGS"]= INTERFACE_SETTINGS
591 if (not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION'] and
592 'NAT_MODE' not in vars):
593 log.write( "Hostname does not resolve correctly, will not continue.\n" )
595 if can_make_api_call:
596 log.write( "Notifying contacts of problem.\n" )
598 vars['RUN_LEVEL']= 'failboot'
599 vars['STATE_CHANGE_NOTIFY']= 1
600 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
601 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
603 UpdateRunLevelWithPLC.Run( vars, log )
606 log.write( "The hostname and/or ip in the network configuration\n" )
607 log.write( "file do not resolve and match.\n" )
608 log.write( "Please make sure the hostname set in the network\n" )
609 log.write( "configuration file resolves to the ip also specified\n" )
610 log.write( "there.\n\n" )
611 log.write( "Debug mode is being started on this cd. When the above\n" )
612 log.write( "is corrected, reboot the machine to try again.\n" )
614 raise BootManagerException, \
615 "Configured node hostname does not resolve."