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
20 from Exceptions import *
21 import BootServerRequest
23 import notify_messages
24 import UpdateRunLevelWithPLC
27 # two possible names of the configuration files
28 NEW_CONF_FILE_NAME= "plnode.txt"
29 OLD_CONF_FILE_NAME= "planet.cnf"
34 read the machines node configuration file, which contains
35 the node key and the node_id for this machine.
37 these files can exist in several different locations with
38 several different names. Below is the search order:
40 filename floppy flash ramdisk cd
41 plnode.txt 1 2 4 (/) 5 (/usr/boot), 6 (/usr)
44 The locations will be searched in the above order, plnode.txt
45 will be checked first, then planet.cnf. Flash devices will only
46 be searched on 3.0 cds.
48 Because some of the earlier
49 boot cds don't validate the configuration file (which results
50 in a file named /tmp/planet-clean.cnf), and some do, lets
51 bypass this, and mount and attempt to read in the conf
52 file ourselves. If it doesn't exist, we cannot continue, and a
53 BootManagerException will be raised. If the configuration file is found
56 Expect the following variables from the store:
58 Sets the following variables from the configuration file:
59 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
60 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
61 NONE_ID The db node_id for this machine
62 NODE_KEY The key for this node
63 INTERFACE_SETTINGS A dictionary of the values from the network
64 configuration file. keys set:
70 broadcast IP_BROADCASTADDR
75 domainname DOMAIN_NAME
78 iwconfig WLAN_IWCONFIG
80 the mac address is read from the machine unless it exists in the
84 log.write( "\n\nStep: Reading node configuration file.\n" )
87 # make sure we have the variables we need
89 INTERFACE_SETTINGS= {}
90 INTERFACE_SETTINGS['method']= "dhcp"
91 INTERFACE_SETTINGS['ip']= ""
92 INTERFACE_SETTINGS['mac']= ""
93 INTERFACE_SETTINGS['gateway']= ""
94 INTERFACE_SETTINGS['network']= ""
95 INTERFACE_SETTINGS['broadcast']= ""
96 INTERFACE_SETTINGS['netmask']= ""
97 INTERFACE_SETTINGS['dns1']= ""
98 INTERFACE_SETTINGS['dns2']= ""
99 INTERFACE_SETTINGS['hostname']= "localhost"
100 INTERFACE_SETTINGS['domainname']= "localdomain"
101 vars['INTERFACE_SETTINGS']= INTERFACE_SETTINGS
106 vars['WAS_NODE_ID_IN_CONF']= 0
107 vars['WAS_NODE_KEY_IN_CONF']= 0
109 vars['DISCONNECTED_OPERATION']= ''
111 # for any devices that need to be mounted to get the configuration
112 # file, mount them here.
113 mount_point= "/tmp/conffilemount"
114 utils.makedirs( mount_point )
116 old_conf_file_contents= None
117 conf_file_contents= None
120 # 1. check the regular floppy device
121 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
123 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
124 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
127 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
129 log.write( "Checking for existence of %s\n" % conf_file_path )
130 if os.access( conf_file_path, os.R_OK ):
132 conf_file= file(conf_file_path,"r")
133 conf_file_contents= conf_file.read()
135 log.write( "Read in contents of file %s\n" % conf_file_path )
137 log.write( "Unable to read file %s\n" % conf_file_path )
140 utils.sysexec_noerr( "umount %s" % mount_point, log )
141 if __parse_configuration_file( vars, log, conf_file_contents):
144 raise BootManagerException( "Found configuration file plnode.txt " \
145 "on floppy, but was unable to parse it." )
148 # try the old file name, same device. its actually number 3 on the search
149 # order, but do it now to save mounting/unmounting the disk twice.
150 # try to parse it later...
151 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
153 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
154 if os.access( conf_file_path, os.R_OK ):
156 old_conf_file= file(conf_file_path,"r")
157 old_conf_file_contents= old_conf_file.read()
158 old_conf_file.close()
159 log.write( "Read in contents of file %s\n" % conf_file_path )
161 log.write( "Unable to read file %s\n" % conf_file_path )
164 utils.sysexec_noerr( "umount %s" % mount_point, log )
166 # 2. check flash devices on 3.0 based cds
167 log.write( "Checking flash devices for plnode.txt file.\n" )
169 # this is done the same way the 3.0 cds do it, by attempting
170 # to mount and sd*1 devices that are removable
171 devices= os.listdir("/sys/block/")
173 for device in devices:
174 if device[:2] != "sd":
175 log.write( "Skipping non-scsi device %s\n" % device )
179 removable_file_path= "/sys/block/%s/removable" % device
181 removable= int(file(removable_file_path,"r").read().strip())
182 except ValueError, e:
188 log.write( "Skipping non-removable device %s\n" % device )
191 log.write( "Checking removable device %s\n" % device )
193 partitions= file("/proc/partitions", "r")
194 for line in partitions:
198 if not re.search("%s[0-9]*$" % device, line):
202 # major minor #blocks name
203 parts= string.split(line)
205 # ok, try to mount it and see if we have a conf file.
206 full_device= "/dev/%s" % parts[3]
207 except IndexError, e:
208 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
211 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
213 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
214 % (full_device,mount_point), log )
215 except BootManagerException, e:
216 log.write( "Unable to mount, trying next partition\n" )
219 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
221 log.write( "Checking for existence of %s\n" % conf_file_path )
222 if os.access( conf_file_path, os.R_OK ):
224 conf_file= file(conf_file_path,"r")
225 conf_file_contents= conf_file.read()
228 log.write( "Read in contents of file %s\n" % \
231 if __parse_configuration_file( vars, log, \
235 log.write( "Unable to read file %s\n" % conf_file_path )
237 utils.sysexec_noerr( "umount %s" % mount_point, log )
242 raise BootManagerException( \
243 "Found configuration file plnode.txt " \
244 "on floppy, but was unable to parse it.")
248 # 3. check standard floppy disk for old file name planet.cnf
249 log.write( "Checking standard floppy disk for planet.cnf file " \
250 "(for legacy nodes).\n" )
252 if old_conf_file_contents:
253 if __parse_configuration_file( vars, log, old_conf_file_contents):
256 raise BootManagerException( "Found configuration file planet.cnf " \
257 "on floppy, but was unable to parse it." )
260 # 4. check for plnode.txt in / (ramdisk)
261 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
263 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
265 log.write( "Checking for existence of %s\n" % conf_file_path )
266 if os.access(conf_file_path,os.R_OK):
268 conf_file= file(conf_file_path,"r")
269 conf_file_contents= conf_file.read()
271 log.write( "Read in contents of file %s\n" % conf_file_path )
273 log.write( "Unable to read file %s\n" % conf_file_path )
276 if __parse_configuration_file( vars, log, conf_file_contents):
279 raise BootManagerException( "Found configuration file plnode.txt " \
280 "in /, but was unable to parse it.")
283 # 5. check for plnode.txt in /usr/boot (mounted already)
284 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
286 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
288 log.write( "Checking for existence of %s\n" % conf_file_path )
289 if os.access(conf_file_path,os.R_OK):
291 conf_file= file(conf_file_path,"r")
292 conf_file_contents= conf_file.read()
294 log.write( "Read in contents of file %s\n" % conf_file_path )
296 log.write( "Unable to read file %s\n" % conf_file_path )
299 if __parse_configuration_file( vars, log, conf_file_contents):
302 raise BootManagerException( "Found configuration file plnode.txt " \
303 "in /usr/boot, but was unable to parse it.")
307 # 6. check for plnode.txt in /usr (mounted already)
308 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
310 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
312 log.write( "Checking for existence of %s\n" % conf_file_path )
313 if os.access(conf_file_path,os.R_OK):
315 conf_file= file(conf_file_path,"r")
316 conf_file_contents= conf_file.read()
318 log.write( "Read in contents of file %s\n" % conf_file_path )
320 log.write( "Unable to read file %s\n" % conf_file_path )
323 if __parse_configuration_file( vars, log, conf_file_contents):
326 raise BootManagerException( "Found configuration file plnode.txt " \
327 "in /usr, but was unable to parse it.")
330 raise BootManagerException, "Unable to find and read a node configuration file."
335 def __parse_configuration_file( vars, log, file_contents ):
337 parse a configuration file, set keys in var INTERFACE_SETTINGS
338 in vars (see comment for function ReadNodeConfiguration). this
339 also reads the mac address from the machine if successful parsing
340 of the configuration file is completed.
343 INTERFACE_SETTINGS= vars["INTERFACE_SETTINGS"]
345 if file_contents is None:
346 log.write( "__parse_configuration_file called with no file contents\n" )
351 for line in file_contents.split("\n"):
353 line_num = line_num + 1
355 # if its a comment or a whitespace line, ignore
356 if line[:1] == "#" or string.strip(line) == "":
359 # file is setup as name="value" pairs
360 parts= string.split(line, "=", 1)
362 name= string.strip(parts[0])
363 value= string.strip(parts[1])
365 # make sure value starts and ends with
366 # single or double quotes
367 quotes= value[0] + value[len(value)-1]
368 if quotes != "''" and quotes != '""':
369 log.write( "Invalid line %d in configuration file:\n" % line_num )
370 log.write( line + "\n" )
373 # get rid of the quotes around the value
374 value= string.strip(value[1:len(value)-1])
376 if name == "NODE_ID":
378 vars['NODE_ID']= int(value)
379 vars['WAS_NODE_ID_IN_CONF']= 1
380 except ValueError, e:
381 log.write( "Non-numeric node_id in configuration file.\n" )
384 if name == "NODE_KEY":
385 vars['NODE_KEY']= value
386 vars['WAS_NODE_KEY_IN_CONF']= 1
388 if name == "IP_METHOD":
389 value= string.lower(value)
390 if value != "static" and value != "dhcp":
391 log.write( "Invalid IP_METHOD in configuration file:\n" )
392 log.write( line + "\n" )
394 INTERFACE_SETTINGS['method']= value.strip()
396 if name == "IP_ADDRESS":
397 INTERFACE_SETTINGS['ip']= value.strip()
399 if name == "IP_GATEWAY":
400 INTERFACE_SETTINGS['gateway']= value.strip()
402 if name == "IP_NETMASK":
403 INTERFACE_SETTINGS['netmask']= value.strip()
405 if name == "IP_NETADDR":
406 INTERFACE_SETTINGS['network']= value.strip()
408 if name == "IP_BROADCASTADDR":
409 INTERFACE_SETTINGS['broadcast']= value.strip()
411 if name == "IP_DNS1":
412 INTERFACE_SETTINGS['dns1']= value.strip()
414 if name == "IP_DNS2":
415 INTERFACE_SETTINGS['dns2']= value.strip()
417 if name == "HOST_NAME":
418 INTERFACE_SETTINGS['hostname']= string.lower(value)
420 if name == "DOMAIN_NAME":
421 INTERFACE_SETTINGS['domainname']= string.lower(value)
423 if name == "NET_DEVICE":
424 INTERFACE_SETTINGS['mac']= string.upper(value)
426 if name == "DISCONNECTED_OPERATION":
427 vars['DISCONNECTED_OPERATION']= value.strip()
429 except IndexError, e:
430 log.write( "Unable to parse configuration file\n" )
433 # now if we are set to dhcp, clear out any fields
434 # that don't make sense
435 if INTERFACE_SETTINGS["method"] == "dhcp":
436 INTERFACE_SETTINGS["ip"]= ""
437 INTERFACE_SETTINGS["gateway"]= ""
438 INTERFACE_SETTINGS["netmask"]= ""
439 INTERFACE_SETTINGS["network"]= ""
440 INTERFACE_SETTINGS["broadcast"]= ""
441 INTERFACE_SETTINGS["dns1"]= ""
442 INTERFACE_SETTINGS["dns2"]= ""
444 log.write("Successfully read and parsed node configuration file.\n" )
446 # if the mac wasn't specified, read it in from the system.
447 if INTERFACE_SETTINGS["mac"] == "":
449 mac_addr= utils.get_mac_from_interface(device)
452 log.write( "Could not get mac address for device eth0.\n" )
455 INTERFACE_SETTINGS["mac"]= string.upper(mac_addr)
457 log.write( "Got mac address %s for device %s\n" %
458 (INTERFACE_SETTINGS["mac"],device) )
461 # now, if the conf file didn't contain a node id, post the mac address
462 # to plc to get the node_id value
463 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
464 log.write( "Configuration file does not contain the node_id value.\n" )
465 log.write( "Querying PLC for node_id.\n" )
467 bs_request= BootServerRequest.BootServerRequest(vars)
469 postVars= {"mac_addr" : INTERFACE_SETTINGS["mac"]}
470 result= bs_request.DownloadFile( "/boot/getnodeid.php",
471 None, postVars, 1, 1,
474 log.write( "Unable to make request to get node_id.\n" )
478 node_id_file= file("/tmp/node_id","r")
479 node_id= string.strip(node_id_file.read())
482 log.write( "Unable to read node_id from /tmp/node_id\n" )
486 node_id= int(string.strip(node_id))
488 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
492 log.write( "Got node_id, but it returned -1\n\n" )
494 log.write( "------------------------------------------------------\n" )
495 log.write( "This indicates that this node could not be identified\n" )
496 log.write( "by PLC. You will need to add the node to your site,\n" )
497 log.write( "and regenerate the network configuration file.\n" )
498 log.write( "See the Technical Contact guide for node setup\n" )
499 log.write( "procedures.\n\n" )
500 log.write( "Boot process canceled until this is completed.\n" )
501 log.write( "------------------------------------------------------\n" )
503 cancel_boot_flag= "/tmp/CANCEL_BOOT"
504 # this will make the initial script stop requesting scripts from PLC
505 utils.sysexec( "touch %s" % cancel_boot_flag, log )
509 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
510 vars['NODE_ID']= node_id
514 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
515 log.write( "Configuration file does not contain a node_key value.\n" )
516 log.write( "Using boot nonce instead.\n" )
518 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
519 # can be read and used directly. 2.x cds stored in the same place
520 # but in binary form, so we need to convert it to ascii the same
521 # way the old boot scripts did so it matches whats in the db
526 nonce_file= file("/tmp/nonce",read_mode)
527 nonce= nonce_file.read()
530 log.write( "Unable to read nonce from /tmp/nonce\n" )
533 nonce= string.strip(nonce)
535 log.write( "Read nonce, using as key.\n" )
536 vars['NODE_KEY']= nonce
539 # at this point, we've read the network configuration file.
540 # if we were setup using dhcp, get this system's current ip
541 # address and update the vars key ip, because it
542 # is needed for future api calls.
544 # at the same time, we can check to make sure that the hostname
545 # in the configuration file matches the ip address. if it fails
548 hostname= INTERFACE_SETTINGS['hostname'] + "." + \
549 INTERFACE_SETTINGS['domainname']
551 # set to 0 if any part of the hostname resolution check fails
552 hostname_resolve_ok= 1
554 # set to 0 if the above fails, and, we are using dhcp in which
555 # case we don't know the ip of this machine (without having to
556 # parse ifconfig or something). In that case, we won't be able
557 # to make api calls, so printing a message to the screen will
561 log.write( "Checking that hostname %s resolves\n" % hostname )
563 # try a regular dns lookup first
565 resolved_node_ip= socket.gethostbyname(hostname)
566 except socket.gaierror, e:
567 # sleep 5 minutes and try again
570 resolved_node_ip= socket.gethostbyname(hostname)
571 except socket.gaierror, e:
572 hostname_resolve_ok= 0
575 if INTERFACE_SETTINGS['method'] == "dhcp":
576 if hostname_resolve_ok:
577 INTERFACE_SETTINGS['ip']= resolved_node_ip
578 node_ip= resolved_node_ip
582 node_ip= INTERFACE_SETTINGS['ip']
584 # make sure the dns lookup matches what the configuration file says
585 if hostname_resolve_ok:
586 if node_ip != resolved_node_ip:
587 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
588 (hostname,node_ip,resolved_node_ip) )
589 hostname_resolve_ok= 0
591 log.write( "Hostname %s correctly resolves to %s:\n" %
595 vars["INTERFACE_SETTINGS"]= INTERFACE_SETTINGS
597 if (not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION'] and
598 'NAT_MODE' not in vars):
599 log.write( "Hostname does not resolve correctly, will not continue.\n" )
601 if can_make_api_call:
602 log.write( "Notifying contacts of problem.\n" )
604 vars['RUN_LEVEL']= 'failboot'
605 vars['STATE_CHANGE_NOTIFY']= 1
606 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
607 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
609 UpdateRunLevelWithPLC.Run( vars, log )
612 log.write( "The hostname and/or ip in the network configuration\n" )
613 log.write( "file do not resolve and match.\n" )
614 log.write( "Please make sure the hostname set in the network\n" )
615 log.write( "configuration file resolves to the ip also specified\n" )
616 log.write( "there.\n\n" )
617 log.write( "Debug mode is being started on this cd. When the above\n" )
618 log.write( "is corrected, reboot the machine to try again.\n" )
620 raise BootManagerException, \
621 "Configured node hostname does not resolve."