3 # Copyright (c) 2003 Intel Corporation
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
8 # expected /proc/partitions format
10 import sys, os, traceback
16 from Exceptions import *
17 import BootServerRequest
19 import notify_messages
20 import UpdateRunLevelWithPLC
23 # two possible names of the configuration files
24 NEW_CONF_FILE_NAME= "plnode.txt"
25 OLD_CONF_FILE_NAME= "planet.cnf"
30 read the machines node configuration file, which contains
31 the node key and the node_id for this machine.
33 these files can exist in several different locations with
34 several different names. Below is the search order:
36 filename floppy flash ramdisk cd
37 plnode.txt 1 2 4 (/) 5 (/usr/boot), 6 (/usr)
40 The locations will be searched in the above order, plnode.txt
41 will be checked first, then planet.cnf. Flash devices will only
42 be searched on 3.0 cds.
44 Because some of the earlier
45 boot cds don't validate the configuration file (which results
46 in a file named /tmp/planet-clean.cnf), and some do, lets
47 bypass this, and mount and attempt to read in the conf
48 file ourselves. If it doesn't exist, we cannot continue, and a
49 BootManagerException will be raised. If the configuration file is found
52 Expect the following variables from the store:
54 Sets the following variables from the configuration file:
55 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
56 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
57 NONE_ID The db node_id for this machine
58 NODE_KEY The key for this node
59 INTERFACE_SETTINGS A dictionary of the values from the network
60 configuration file. keys set:
66 broadcast IP_BROADCASTADDR
71 domainname DOMAIN_NAME
74 iwconfig WLAN_IWCONFIG
76 the mac address is read from the machine unless it exists in the
80 log.write( "\n\nStep: Reading node configuration file.\n" )
83 # make sure we have the variables we need
85 INTERFACE_SETTINGS= {}
86 INTERFACE_SETTINGS['method']= "dhcp"
87 INTERFACE_SETTINGS['ip']= ""
88 INTERFACE_SETTINGS['mac']= ""
89 INTERFACE_SETTINGS['gateway']= ""
90 INTERFACE_SETTINGS['network']= ""
91 INTERFACE_SETTINGS['broadcast']= ""
92 INTERFACE_SETTINGS['netmask']= ""
93 INTERFACE_SETTINGS['dns1']= ""
94 INTERFACE_SETTINGS['dns2']= ""
95 INTERFACE_SETTINGS['hostname']= "localhost"
96 INTERFACE_SETTINGS['domainname']= "localdomain"
97 vars['INTERFACE_SETTINGS']= INTERFACE_SETTINGS
102 vars['WAS_NODE_ID_IN_CONF']= 0
103 vars['WAS_NODE_KEY_IN_CONF']= 0
105 vars['DISCONNECTED_OPERATION']= ''
107 # for any devices that need to be mounted to get the configuration
108 # file, mount them here.
109 mount_point= "/tmp/conffilemount"
110 utils.makedirs( mount_point )
112 old_conf_file_contents= None
113 conf_file_contents= None
116 # 1. check the regular floppy device
117 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
119 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
120 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
123 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
125 # log.write( "Checking for existence of %s\n" % conf_file_path )
126 if os.access( conf_file_path, os.R_OK ):
128 conf_file= file(conf_file_path,"r")
129 conf_file_contents= conf_file.read()
131 log.write( "Read in contents of file %s\n" % conf_file_path )
133 log.write( "Unable to read file %s\n" % conf_file_path )
136 utils.sysexec_noerr( "umount %s" % mount_point, log )
137 if __parse_configuration_file( vars, log, conf_file_contents):
138 log.write("ReadNodeConfiguration: [1] using %s from floppy /dev/fd0\n"%NEW_CONF_FILE_NAME)
141 raise BootManagerException( "Found configuration file plnode.txt " \
142 "on floppy, but was unable to parse it." )
145 # try the old file name, same device. its actually number 3 on the search
146 # order, but do it now to save mounting/unmounting the disk twice.
147 # try to parse it later...
148 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
150 # this message really does not convey any useful information
151 # log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
152 if os.access( conf_file_path, os.R_OK ):
154 old_conf_file= file(conf_file_path,"r")
155 old_conf_file_contents= old_conf_file.read()
156 old_conf_file.close()
157 log.write( "Read in contents of file %s\n" % conf_file_path )
159 log.write( "Unable to read file %s\n" % conf_file_path )
162 utils.sysexec_noerr( "umount %s" % mount_point, log )
164 # 2. check flash devices on 3.0 based cds
165 log.write( "Checking flash devices for plnode.txt file.\n" )
167 # this is done the same way the 3.0 cds do it, by attempting
168 # to mount and sd*1 devices that are removable
169 devices= os.listdir("/sys/block/")
171 for device in devices:
172 if device[:2] != "sd":
173 log.write( "Skipping non-scsi device %s\n" % device )
177 removable_file_path= "/sys/block/%s/removable" % device
179 removable= int(file(removable_file_path,"r").read().strip())
180 except ValueError, e:
186 log.write( "Skipping non-removable device %s\n" % device )
189 log.write( "Checking removable device %s\n" % device )
191 partitions= file("/proc/partitions", "r")
192 for line in partitions:
196 if not re.search("%s[0-9]*$" % device, line):
200 # major minor #blocks name
201 parts= string.split(line)
203 # ok, try to mount it and see if we have a conf file.
204 full_device= "/dev/%s" % parts[3]
205 except IndexError, e:
206 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
209 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
211 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
212 % (full_device,mount_point), log )
213 except BootManagerException, e:
214 log.write( "Unable to mount, trying next partition\n" )
217 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
219 log.write( "Checking for existence of %s\n" % conf_file_path )
220 if os.access( conf_file_path, os.R_OK ):
222 conf_file= file(conf_file_path,"r")
223 conf_file_contents= conf_file.read()
226 log.write( "Read in contents of file %s\n" % \
229 if __parse_configuration_file( vars, log, conf_file_contents):
232 log.write( "Unable to read file %s\n" % conf_file_path )
234 utils.sysexec_noerr( "umount %s" % mount_point, log )
237 log.write("ReadNodeConfiguration: [2] using %s from partition %s\n"%\
238 (NEW_CONF_FILE_NAME,full_device))
241 raise BootManagerException( \
242 "Found configuration file on %s, but was unable to parse it."%full_device)
246 # 3. check standard floppy disk for old file name planet.cnf
247 log.write( "Checking standard floppy disk for planet.cnf file (for legacy nodes).\n" )
249 if old_conf_file_contents:
250 if __parse_configuration_file( vars, log, old_conf_file_contents):
251 log.write("ReadNodeConfiguration: [3] using %s from floppy /dev/fd0\n"%OLD_CONF_FILE_NAME)
254 raise BootManagerException( "Found configuration file planet.cnf " \
255 "on floppy, but was unable to parse it." )
258 # 4. check for plnode.txt in / (ramdisk)
259 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
261 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
263 log.write( "Checking for existence of %s\n" % conf_file_path )
264 if os.access(conf_file_path,os.R_OK):
266 conf_file= file(conf_file_path,"r")
267 conf_file_contents= conf_file.read()
269 log.write( "Read in contents of file %s\n" % conf_file_path )
271 log.write( "Unable to read file %s\n" % conf_file_path )
274 if __parse_configuration_file( vars, log, conf_file_contents):
275 log.write("ReadNodeConfiguration: [4] using %s from ramdisk\n"%NEW_CONF_FILE_NAME)
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):
299 log.write("ReadNodeConfiguration: [5] using %s from CD in /usr/boot\n"%NEW_CONF_FILE_NAME)
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):
324 log.write("ReadNodeConfiguration: [6] using %s from /usr\n"%NEW_CONF_FILE_NAME)
327 raise BootManagerException( "Found configuration file plnode.txt " \
328 "in /usr, but was unable to parse it.")
331 raise BootManagerException, "Unable to find and read a node configuration file."
336 def __parse_configuration_file( vars, log, file_contents ):
338 parse a configuration file, set keys in var INTERFACE_SETTINGS
339 in vars (see comment for function ReadNodeConfiguration). this
340 also reads the mac address from the machine if successful parsing
341 of the configuration file is completed.
344 INTERFACE_SETTINGS= vars["INTERFACE_SETTINGS"]
346 if file_contents is None:
347 log.write( "__parse_configuration_file called with no file contents\n" )
352 for line in file_contents.split("\n"):
354 line_num = line_num + 1
356 # if its a comment or a whitespace line, ignore
357 if line[:1] == "#" or string.strip(line) == "":
360 # file is setup as name="value" pairs
361 parts= string.split(line, "=", 1)
363 name= string.strip(parts[0])
364 value= string.strip(parts[1])
366 # make sure value starts and ends with
367 # single or double quotes
368 quotes= value[0] + value[len(value)-1]
369 if quotes != "''" and quotes != '""':
370 log.write( "Invalid line %d in configuration file:\n" % line_num )
371 log.write( line + "\n" )
374 # get rid of the quotes around the value
375 value= string.strip(value[1:len(value)-1])
377 if name == "NODE_ID":
379 vars['NODE_ID']= int(value)
380 vars['WAS_NODE_ID_IN_CONF']= 1
381 except ValueError, e:
382 log.write( "Non-numeric node_id in configuration file.\n" )
385 if name == "NODE_KEY":
386 vars['NODE_KEY']= value
387 vars['WAS_NODE_KEY_IN_CONF']= 1
389 if name == "IP_METHOD":
390 value= string.lower(value)
391 if value != "static" and value != "dhcp":
392 log.write( "Invalid IP_METHOD in configuration file:\n" )
393 log.write( line + "\n" )
395 INTERFACE_SETTINGS['method']= value.strip()
397 if name == "IP_ADDRESS":
398 INTERFACE_SETTINGS['ip']= value.strip()
400 if name == "IP_GATEWAY":
401 INTERFACE_SETTINGS['gateway']= value.strip()
403 if name == "IP_NETMASK":
404 INTERFACE_SETTINGS['netmask']= value.strip()
406 if name == "IP_NETADDR":
407 INTERFACE_SETTINGS['network']= value.strip()
409 if name == "IP_BROADCASTADDR":
410 INTERFACE_SETTINGS['broadcast']= value.strip()
412 if name == "IP_DNS1":
413 INTERFACE_SETTINGS['dns1']= value.strip()
415 if name == "IP_DNS2":
416 INTERFACE_SETTINGS['dns2']= value.strip()
418 if name == "HOST_NAME":
419 INTERFACE_SETTINGS['hostname']= string.lower(value)
421 if name == "DOMAIN_NAME":
422 INTERFACE_SETTINGS['domainname']= string.lower(value)
424 if name == "NET_DEVICE":
425 INTERFACE_SETTINGS['mac']= string.upper(value)
427 if name == "DISCONNECTED_OPERATION":
428 vars['DISCONNECTED_OPERATION']= value.strip()
430 except IndexError, e:
431 log.write( "Unable to parse configuration file\n" )
434 # now if we are set to dhcp, clear out any fields
435 # that don't make sense
436 if INTERFACE_SETTINGS["method"] == "dhcp":
437 INTERFACE_SETTINGS["ip"]= ""
438 INTERFACE_SETTINGS["gateway"]= ""
439 INTERFACE_SETTINGS["netmask"]= ""
440 INTERFACE_SETTINGS["network"]= ""
441 INTERFACE_SETTINGS["broadcast"]= ""
442 INTERFACE_SETTINGS["dns1"]= ""
443 INTERFACE_SETTINGS["dns2"]= ""
445 log.write("Successfully read and parsed node configuration file.\n" )
447 # if the mac wasn't specified, read it in from the system.
448 if INTERFACE_SETTINGS["mac"] == "":
450 mac_addr= utils.get_mac_from_interface(device)
453 log.write( "Could not get mac address for device eth0.\n" )
456 INTERFACE_SETTINGS["mac"]= string.upper(mac_addr)
458 log.write( "Got mac address %s for device %s\n" %
459 (INTERFACE_SETTINGS["mac"],device) )
462 # now, if the conf file didn't contain a node id, post the mac address
463 # to plc to get the node_id value
464 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
465 log.write( "Configuration file does not contain the node_id value.\n" )
466 log.write( "Querying PLC for node_id.\n" )
468 bs_request= BootServerRequest.BootServerRequest(vars)
470 postVars= {"mac_addr" : INTERFACE_SETTINGS["mac"]}
471 result= bs_request.DownloadFile( "/boot/getnodeid.php",
472 None, postVars, 1, 1,
475 log.write( "Unable to make request to get node_id.\n" )
479 node_id_file= file("/tmp/node_id","r")
480 node_id= string.strip(node_id_file.read())
483 log.write( "Unable to read node_id from /tmp/node_id\n" )
487 node_id= int(string.strip(node_id))
489 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
493 log.write( "Got node_id, but it returned -1\n\n" )
495 log.write( "------------------------------------------------------\n" )
496 log.write( "This indicates that this node could not be identified\n" )
497 log.write( "by PLC. You will need to add the node to your site,\n" )
498 log.write( "and regenerate the network configuration file.\n" )
499 log.write( "See the Technical Contact guide for node setup\n" )
500 log.write( "procedures.\n\n" )
501 log.write( "Boot process canceled until this is completed.\n" )
502 log.write( "------------------------------------------------------\n" )
504 cancel_boot_flag= "/tmp/CANCEL_BOOT"
505 # this will make the initial script stop requesting scripts from PLC
506 utils.sysexec( "touch %s" % cancel_boot_flag, log )
510 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
511 vars['NODE_ID']= node_id
515 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
516 log.write( "Configuration file does not contain a node_key value.\n" )
517 log.write( "Using boot nonce instead.\n" )
519 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
520 # can be read and used directly. 2.x cds stored in the same place
521 # but in binary form, so we need to convert it to ascii the same
522 # way the old boot scripts did so it matches whats in the db
527 nonce_file= file("/tmp/nonce",read_mode)
528 nonce= nonce_file.read()
531 log.write( "Unable to read nonce from /tmp/nonce\n" )
534 nonce= string.strip(nonce)
536 log.write( "Read nonce, using as key.\n" )
537 vars['NODE_KEY']= nonce
540 # at this point, we've read the network configuration file.
541 # if we were setup using dhcp, get this system's current ip
542 # address and update the vars key ip, because it
543 # is needed for future api calls.
545 # at the same time, we can check to make sure that the hostname
546 # in the configuration file matches the ip address. if it fails
549 hostname= INTERFACE_SETTINGS['hostname'] + "." + \
550 INTERFACE_SETTINGS['domainname']
552 # set to 0 if any part of the hostname resolution check fails
553 hostname_resolve_ok= 1
555 # set to 0 if the above fails, and, we are using dhcp in which
556 # case we don't know the ip of this machine (without having to
557 # parse ifconfig or something). In that case, we won't be able
558 # to make api calls, so printing a message to the screen will
562 log.write( "Checking that hostname %s resolves\n" % hostname )
564 # try a regular dns lookup first
566 resolved_node_ip= socket.gethostbyname(hostname)
567 except socket.gaierror, e:
568 hostname_resolve_ok= 0
571 if INTERFACE_SETTINGS['method'] == "dhcp":
572 if hostname_resolve_ok:
573 INTERFACE_SETTINGS['ip']= resolved_node_ip
574 node_ip= resolved_node_ip
578 node_ip= INTERFACE_SETTINGS['ip']
580 # make sure the dns lookup matches what the configuration file says
581 if hostname_resolve_ok:
582 if node_ip != resolved_node_ip:
583 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
584 (hostname,node_ip,resolved_node_ip) )
585 hostname_resolve_ok= 0
587 log.write( "Hostname %s correctly resolves to %s:\n" %
591 vars["INTERFACE_SETTINGS"]= INTERFACE_SETTINGS
593 if (not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION'] and
594 'NAT_MODE' not in vars):
595 log.write( "Hostname does not resolve correctly, will not continue.\n" )
597 if can_make_api_call:
598 log.write( "Notifying contacts of problem.\n" )
600 vars['RUN_LEVEL']= 'failboot'
601 vars['STATE_CHANGE_NOTIFY']= 1
602 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
603 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
605 UpdateRunLevelWithPLC.Run( vars, log )
608 log.write( "The hostname and/or ip in the network configuration\n" )
609 log.write( "file do not resolve and match.\n" )
610 log.write( "Please make sure the hostname set in the network\n" )
611 log.write( "configuration file resolves to the ip also specified\n" )
612 log.write( "there.\n\n" )
613 log.write( "Debug mode is being started on this cd. When the above\n" )
614 log.write( "is corrected, reboot the machine to try again.\n" )
616 raise BootManagerException, \
617 "Configured node hostname does not resolve."
620 log.write("Using NODE_ID %d\n"%vars['NODE_ID'])
622 log.write("Unknown NODE_ID")