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 UpdateBootStateWithPLC
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:
53 BOOT_CD_VERSION A tuple of the current bootcd version
54 SUPPORT_FILE_DIR directory on the boot servers containing
55 scripts and support files
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 NETWORK_SETTINGS A dictionary of the values from the network
63 configuration file. keys set:
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 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
86 if BOOT_CD_VERSION == "":
87 raise ValueError, "BOOT_CD_VERSION"
89 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
90 if SUPPORT_FILE_DIR == None:
91 raise ValueError, "SUPPORT_FILE_DIR"
94 raise BootManagerException, "Missing variable in vars: %s\n" % var
95 except ValueError, var:
96 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
100 NETWORK_SETTINGS['method']= "dhcp"
101 NETWORK_SETTINGS['ip']= ""
102 NETWORK_SETTINGS['mac']= ""
103 NETWORK_SETTINGS['gateway']= ""
104 NETWORK_SETTINGS['network']= ""
105 NETWORK_SETTINGS['broadcast']= ""
106 NETWORK_SETTINGS['netmask']= ""
107 NETWORK_SETTINGS['dns1']= ""
108 NETWORK_SETTINGS['dns2']= ""
109 NETWORK_SETTINGS['hostname']= "localhost"
110 NETWORK_SETTINGS['domainname']= "localdomain"
111 vars['NETWORK_SETTINGS']= NETWORK_SETTINGS
116 vars['WAS_NODE_ID_IN_CONF']= 0
117 vars['WAS_NODE_KEY_IN_CONF']= 0
119 # for any devices that need to be mounted to get the configuration
120 # file, mount them here.
121 mount_point= "/tmp/conffilemount"
122 utils.makedirs( mount_point )
124 old_conf_file_contents= None
125 conf_file_contents= None
128 # 1. check the regular floppy device
129 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
131 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
132 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
135 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
137 log.write( "Checking for existence of %s\n" % conf_file_path )
138 if os.access( conf_file_path, os.R_OK ):
140 conf_file= file(conf_file_path,"r")
141 conf_file_contents= conf_file.read()
143 log.write( "Read in contents of file %s\n" % conf_file_path )
145 log.write( "Unable to read file %s\n" % conf_file_path )
148 utils.sysexec_noerr( "umount %s" % mount_point, log )
149 if __parse_configuration_file( vars, log, conf_file_contents):
152 raise BootManagerException( "Found configuration file plnode.txt " \
153 "on floppy, but was unable to parse it." )
156 # try the old file name, same device. its actually number 3 on the search
157 # order, but do it now to save mounting/unmounting the disk twice.
158 # try to parse it later...
159 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
161 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
162 if os.access( conf_file_path, os.R_OK ):
164 old_conf_file= file(conf_file_path,"r")
165 old_conf_file_contents= old_conf_file.read()
166 old_conf_file.close()
167 log.write( "Read in contents of file %s\n" % conf_file_path )
169 log.write( "Unable to read file %s\n" % conf_file_path )
172 utils.sysexec_noerr( "umount %s" % mount_point, log )
176 if BOOT_CD_VERSION[0] == 3:
177 # 2. check flash devices on 3.0 based cds
178 log.write( "Checking flash devices for plnode.txt file.\n" )
180 # this is done the same way the 3.0 cds do it, by attempting
181 # to mount and sd*1 devices that are removable
182 devices= os.listdir("/sys/block/")
184 for device in devices:
185 if device[:2] != "sd":
186 log.write( "Skipping non-scsi device %s\n" % device )
190 removable_file_path= "/sys/block/%s/removable" % device
192 removable= int(file(removable_file_path,"r").read().strip())
193 except ValueError, e:
199 log.write( "Skipping non-removable device %s\n" % device )
202 log.write( "Checking removable device %s\n" % device )
204 partitions= file("/proc/partitions", "r")
205 for line in partitions:
209 if not re.search("%s[0-9]*$" % device, line):
213 # major minor #blocks name
214 parts= string.split(line)
216 # ok, try to mount it and see if we have a conf file.
217 full_device= "/dev/%s" % parts[3]
218 except IndexError, e:
219 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
222 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
224 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
225 % (full_device,mount_point), log )
226 except BootManagerException, e:
227 log.write( "Unable to mount, trying next partition\n" )
230 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
232 log.write( "Checking for existence of %s\n" % conf_file_path )
233 if os.access( conf_file_path, os.R_OK ):
235 conf_file= file(conf_file_path,"r")
236 conf_file_contents= conf_file.read()
239 log.write( "Read in contents of file %s\n" % \
242 if __parse_configuration_file( vars, log, \
246 log.write( "Unable to read file %s\n" % conf_file_path )
248 utils.sysexec_noerr( "umount %s" % mount_point, log )
253 raise BootManagerException( \
254 "Found configuration file plnode.txt " \
255 "on floppy, but was unable to parse it.")
259 # 3. check standard floppy disk for old file name planet.cnf
260 log.write( "Checking standard floppy disk for planet.cnf file " \
263 if old_conf_file_contents:
264 if __parse_configuration_file( vars, log, old_conf_file_contents):
267 raise BootManagerException( "Found configuration file planet.cnf " \
268 "on floppy, but was unable to parse it." )
271 # 4. check for plnode.txt in / (ramdisk)
272 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
274 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
276 log.write( "Checking for existence of %s\n" % conf_file_path )
277 if os.access(conf_file_path,os.R_OK):
279 conf_file= file(conf_file_path,"r")
280 conf_file_contents= conf_file.read()
282 log.write( "Read in contents of file %s\n" % conf_file_path )
284 log.write( "Unable to read file %s\n" % conf_file_path )
287 if __parse_configuration_file( vars, log, conf_file_contents):
290 raise BootManagerException( "Found configuration file plnode.txt " \
291 "in /, but was unable to parse it.")
294 # 5. check for plnode.txt in /usr/boot (mounted already)
295 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
297 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
299 log.write( "Checking for existence of %s\n" % conf_file_path )
300 if os.access(conf_file_path,os.R_OK):
302 conf_file= file(conf_file_path,"r")
303 conf_file_contents= conf_file.read()
305 log.write( "Read in contents of file %s\n" % conf_file_path )
307 log.write( "Unable to read file %s\n" % conf_file_path )
310 if __parse_configuration_file( vars, log, conf_file_contents):
313 raise BootManagerException( "Found configuration file plnode.txt " \
314 "in /usr/boot, but was unable to parse it.")
318 # 6. check for plnode.txt in /usr (mounted already)
319 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
321 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
323 log.write( "Checking for existence of %s\n" % conf_file_path )
324 if os.access(conf_file_path,os.R_OK):
326 conf_file= file(conf_file_path,"r")
327 conf_file_contents= conf_file.read()
329 log.write( "Read in contents of file %s\n" % conf_file_path )
331 log.write( "Unable to read file %s\n" % conf_file_path )
334 if __parse_configuration_file( vars, log, conf_file_contents):
337 raise BootManagerException( "Found configuration file plnode.txt " \
338 "in /usr, but was unable to parse it.")
341 raise BootManagerException, "Unable to find and read a node configuration file."
346 def __parse_configuration_file( vars, log, file_contents ):
348 parse a configuration file, set keys in var NETWORK_SETTINGS
349 in vars (see comment for function ReadNodeConfiguration). this
350 also reads the mac address from the machine if successful parsing
351 of the configuration file is completed.
354 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
355 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
356 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
358 if file_contents is None:
359 log.write( "__parse_configuration_file called with no file contents\n" )
364 for line in file_contents.split("\n"):
366 line_num = line_num + 1
368 # if its a comment or a whitespace line, ignore
369 if line[:1] == "#" or string.strip(line) == "":
372 # file is setup as name="value" pairs
373 parts= string.split(line,"=")
375 log.write( "Invalid line %d in configuration file:\n" % line_num )
376 log.write( line + "\n" )
379 name= string.strip(parts[0])
380 value= string.strip(parts[1])
382 # make sure value starts and ends with
383 # single or double quotes
384 quotes= value[0] + value[len(value)-1]
385 if quotes != "''" and quotes != '""':
386 log.write( "Invalid line %d in configuration file:\n" % line_num )
387 log.write( line + "\n" )
390 # get rid of the quotes around the value
391 value= string.strip(value[1:len(value)-1])
393 if name == "NODE_ID":
395 vars['NODE_ID']= int(value)
396 vars['WAS_NODE_ID_IN_CONF']= 1
397 except ValueError, e:
398 log.write( "Non-numeric node_id in configuration file.\n" )
401 if name == "NODE_KEY":
402 vars['NODE_KEY']= value
403 vars['WAS_NODE_KEY_IN_CONF']= 1
405 if name == "IP_METHOD":
406 value= string.lower(value)
407 if value != "static" and value != "dhcp":
408 log.write( "Invalid IP_METHOD in configuration file:\n" )
409 log.write( line + "\n" )
411 NETWORK_SETTINGS['method']= value.strip()
413 if name == "IP_ADDRESS":
414 NETWORK_SETTINGS['ip']= value.strip()
416 if name == "IP_GATEWAY":
417 NETWORK_SETTINGS['gateway']= value.strip()
419 if name == "IP_NETMASK":
420 NETWORK_SETTINGS['netmask']= value.strip()
422 if name == "IP_NETADDR":
423 NETWORK_SETTINGS['network']= value.strip()
425 if name == "IP_BROADCASTADDR":
426 NETWORK_SETTINGS['broadcast']= value.strip()
428 if name == "IP_DNS1":
429 NETWORK_SETTINGS['dns1']= value.strip()
431 if name == "IP_DNS2":
432 NETWORK_SETTINGS['dns2']= value.strip()
434 if name == "HOST_NAME":
435 NETWORK_SETTINGS['hostname']= string.lower(value)
437 if name == "DOMAIN_NAME":
438 NETWORK_SETTINGS['domainname']= string.lower(value)
440 if name == "NET_DEVICE":
441 NETWORK_SETTINGS['mac']= string.upper(value)
444 except IndexError, e:
445 log.write( "Unable to parse configuration file\n" )
448 # now if we are set to dhcp, clear out any fields
449 # that don't make sense
450 if NETWORK_SETTINGS["method"] == "dhcp":
451 NETWORK_SETTINGS["ip"]= ""
452 NETWORK_SETTINGS["gateway"]= ""
453 NETWORK_SETTINGS["netmask"]= ""
454 NETWORK_SETTINGS["network"]= ""
455 NETWORK_SETTINGS["broadcast"]= ""
456 NETWORK_SETTINGS["dns1"]= ""
457 NETWORK_SETTINGS["dns2"]= ""
459 log.write("Successfully read and parsed node configuration file.\n" )
461 # if the mac wasn't specified, read it in from the system.
462 if NETWORK_SETTINGS["mac"] == "":
464 mac_addr= utils.get_mac_from_interface(device)
467 log.write( "Could not get mac address for device eth0.\n" )
470 NETWORK_SETTINGS["mac"]= string.upper(mac_addr)
472 log.write( "Got mac address %s for device %s\n" %
473 (NETWORK_SETTINGS["mac"],device) )
476 # now, if the conf file didn't contain a node id, post the mac address
477 # to plc to get the node_id value
478 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
479 log.write( "Configuration file does not contain the node_id value.\n" )
480 log.write( "Querying PLC for node_id.\n" )
482 bs_request= BootServerRequest.BootServerRequest()
484 postVars= {"mac_addr" : NETWORK_SETTINGS["mac"]}
485 result= bs_request.DownloadFile( "%s/getnodeid.php" %
487 None, postVars, 1, 1,
490 log.write( "Unable to make request to get node_id.\n" )
494 node_id_file= file("/tmp/node_id","r")
495 node_id= string.strip(node_id_file.read())
498 log.write( "Unable to read node_id from /tmp/node_id\n" )
502 node_id= int(string.strip(node_id))
504 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
508 log.write( "Got node_id, but it returned -1\n\n" )
510 log.write( "------------------------------------------------------\n" )
511 log.write( "This indicates that this node could not be identified\n" )
512 log.write( "by PLC. You will need to add the node to your site,\n" )
513 log.write( "and regenerate the network configuration file.\n" )
514 log.write( "See the Technical Contact guide for node setup\n" )
515 log.write( "procedures.\n\n" )
516 log.write( "Boot process canceled until this is completed.\n" )
517 log.write( "------------------------------------------------------\n" )
519 cancel_boot_flag= "/tmp/CANCEL_BOOT"
520 # this will make the initial script stop requesting scripts from PLC
521 utils.sysexec( "touch %s" % cancel_boot_flag, log )
525 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
526 vars['NODE_ID']= node_id
530 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
531 log.write( "Configuration file does not contain a node_key value.\n" )
532 log.write( "Using boot nonce instead.\n" )
534 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
535 # can be read and used directly. 2.x cds stored in the same place
536 # but in binary form, so we need to convert it to ascii the same
537 # way the old boot scripts did so it matches whats in the db
539 if BOOT_CD_VERSION[0] == 2:
545 nonce_file= file("/tmp/nonce",read_mode)
546 nonce= nonce_file.read()
549 log.write( "Unable to read nonce from /tmp/nonce\n" )
552 if BOOT_CD_VERSION[0] == 2:
553 nonce= nonce.encode('hex')
555 # there is this nice bug in the php that currently accepts the
556 # nonce for the old scripts, in that if the nonce contains
557 # null chars (2.x cds sent as binary), then
558 # the nonce is truncated. so, do the same here, truncate the nonce
559 # at the first null ('00'). This could leave us with an empty string.
560 nonce_len= len(nonce)
561 for byte_index in range(0,nonce_len,2):
562 if nonce[byte_index:byte_index+2] == '00':
563 nonce= nonce[:byte_index]
566 nonce= string.strip(nonce)
568 log.write( "Read nonce, using as key.\n" )
569 vars['NODE_KEY']= nonce
572 # at this point, we've read the network configuration file.
573 # if we were setup using dhcp, get this system's current ip
574 # address and update the vars key ip, because it
575 # is needed for future api calls.
577 # at the same time, we can check to make sure that the hostname
578 # in the configuration file matches the ip address. if it fails
581 hostname= NETWORK_SETTINGS['hostname'] + "." + \
582 NETWORK_SETTINGS['domainname']
584 # set to 0 if any part of the hostname resolution check fails
585 hostname_resolve_ok= 1
587 # set to 0 if the above fails, and, we are using dhcp in which
588 # case we don't know the ip of this machine (without having to
589 # parse ifconfig or something). In that case, we won't be able
590 # to make api calls, so printing a message to the screen will
594 log.write( "Checking that hostname %s resolves\n" % hostname )
596 # try a regular dns lookup first
598 resolved_node_ip= socket.gethostbyname(hostname)
599 except socket.gaierror, e:
600 hostname_resolve_ok= 0
603 if NETWORK_SETTINGS['method'] == "dhcp":
604 if hostname_resolve_ok:
605 NETWORK_SETTINGS['ip']= resolved_node_ip
606 node_ip= resolved_node_ip
610 node_ip= NETWORK_SETTINGS['ip']
612 # make sure the dns lookup matches what the configuration file says
613 if hostname_resolve_ok:
614 if node_ip != resolved_node_ip:
615 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
616 (hostname,node_ip,resolved_node_ip) )
617 hostname_resolve_ok= 0
619 log.write( "Hostname %s correctly resolves to %s:\n" %
623 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
625 if not hostname_resolve_ok:
626 log.write( "Hostname does not resolve correctly, will not continue.\n" )
628 if can_make_api_call:
629 log.write( "Notifying contacts of problem.\n" )
631 vars['BOOT_STATE']= 'dbg'
632 vars['STATE_CHANGE_NOTIFY']= 1
633 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
634 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
636 UpdateBootStateWithPLC.Run( vars, log )
639 log.write( "The hostname and/or ip in the network configuration\n" )
640 log.write( "file do not resolve and match.\n" )
641 log.write( "Please make sure the hostname set in the network\n" )
642 log.write( "configuration file resolves to the ip also specified\n" )
643 log.write( "there.\n\n" )
644 log.write( "Debug mode is being started on this cd. When the above\n" )
645 log.write( "is corrected, reboot the machine to try again.\n" )
647 raise BootManagerException, \
648 "Configured node hostname does not resolve."