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 vars['DISCONNECTED_OPERATION']= ''
121 # for any devices that need to be mounted to get the configuration
122 # file, mount them here.
123 mount_point= "/tmp/conffilemount"
124 utils.makedirs( mount_point )
126 old_conf_file_contents= None
127 conf_file_contents= None
130 # 1. check the regular floppy device
131 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
133 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
134 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
137 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
139 log.write( "Checking for existence of %s\n" % conf_file_path )
140 if os.access( conf_file_path, os.R_OK ):
142 conf_file= file(conf_file_path,"r")
143 conf_file_contents= conf_file.read()
145 log.write( "Read in contents of file %s\n" % conf_file_path )
147 log.write( "Unable to read file %s\n" % conf_file_path )
150 utils.sysexec_noerr( "umount %s" % mount_point, log )
151 if __parse_configuration_file( vars, log, conf_file_contents):
154 raise BootManagerException( "Found configuration file plnode.txt " \
155 "on floppy, but was unable to parse it." )
158 # try the old file name, same device. its actually number 3 on the search
159 # order, but do it now to save mounting/unmounting the disk twice.
160 # try to parse it later...
161 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
163 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
164 if os.access( conf_file_path, os.R_OK ):
166 old_conf_file= file(conf_file_path,"r")
167 old_conf_file_contents= old_conf_file.read()
168 old_conf_file.close()
169 log.write( "Read in contents of file %s\n" % conf_file_path )
171 log.write( "Unable to read file %s\n" % conf_file_path )
174 utils.sysexec_noerr( "umount %s" % mount_point, log )
178 if BOOT_CD_VERSION[0] == 3:
179 # 2. check flash devices on 3.0 based cds
180 log.write( "Checking flash devices for plnode.txt file.\n" )
182 # this is done the same way the 3.0 cds do it, by attempting
183 # to mount and sd*1 devices that are removable
184 devices= os.listdir("/sys/block/")
186 for device in devices:
187 if device[:2] != "sd":
188 log.write( "Skipping non-scsi device %s\n" % device )
192 removable_file_path= "/sys/block/%s/removable" % device
194 removable= int(file(removable_file_path,"r").read().strip())
195 except ValueError, e:
201 log.write( "Skipping non-removable device %s\n" % device )
204 log.write( "Checking removable device %s\n" % device )
206 partitions= file("/proc/partitions", "r")
207 for line in partitions:
211 if not re.search("%s[0-9]*$" % device, line):
215 # major minor #blocks name
216 parts= string.split(line)
218 # ok, try to mount it and see if we have a conf file.
219 full_device= "/dev/%s" % parts[3]
220 except IndexError, e:
221 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
224 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
226 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
227 % (full_device,mount_point), log )
228 except BootManagerException, e:
229 log.write( "Unable to mount, trying next partition\n" )
232 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
234 log.write( "Checking for existence of %s\n" % conf_file_path )
235 if os.access( conf_file_path, os.R_OK ):
237 conf_file= file(conf_file_path,"r")
238 conf_file_contents= conf_file.read()
241 log.write( "Read in contents of file %s\n" % \
244 if __parse_configuration_file( vars, log, \
248 log.write( "Unable to read file %s\n" % conf_file_path )
250 utils.sysexec_noerr( "umount %s" % mount_point, log )
255 raise BootManagerException( \
256 "Found configuration file plnode.txt " \
257 "on floppy, but was unable to parse it.")
261 # 3. check standard floppy disk for old file name planet.cnf
262 log.write( "Checking standard floppy disk for planet.cnf file " \
265 if old_conf_file_contents:
266 if __parse_configuration_file( vars, log, old_conf_file_contents):
269 raise BootManagerException( "Found configuration file planet.cnf " \
270 "on floppy, but was unable to parse it." )
273 # 4. check for plnode.txt in / (ramdisk)
274 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
276 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
278 log.write( "Checking for existence of %s\n" % conf_file_path )
279 if os.access(conf_file_path,os.R_OK):
281 conf_file= file(conf_file_path,"r")
282 conf_file_contents= conf_file.read()
284 log.write( "Read in contents of file %s\n" % conf_file_path )
286 log.write( "Unable to read file %s\n" % conf_file_path )
289 if __parse_configuration_file( vars, log, conf_file_contents):
292 raise BootManagerException( "Found configuration file plnode.txt " \
293 "in /, but was unable to parse it.")
296 # 5. check for plnode.txt in /usr/boot (mounted already)
297 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
299 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
301 log.write( "Checking for existence of %s\n" % conf_file_path )
302 if os.access(conf_file_path,os.R_OK):
304 conf_file= file(conf_file_path,"r")
305 conf_file_contents= conf_file.read()
307 log.write( "Read in contents of file %s\n" % conf_file_path )
309 log.write( "Unable to read file %s\n" % conf_file_path )
312 if __parse_configuration_file( vars, log, conf_file_contents):
315 raise BootManagerException( "Found configuration file plnode.txt " \
316 "in /usr/boot, but was unable to parse it.")
320 # 6. check for plnode.txt in /usr (mounted already)
321 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
323 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
325 log.write( "Checking for existence of %s\n" % conf_file_path )
326 if os.access(conf_file_path,os.R_OK):
328 conf_file= file(conf_file_path,"r")
329 conf_file_contents= conf_file.read()
331 log.write( "Read in contents of file %s\n" % conf_file_path )
333 log.write( "Unable to read file %s\n" % conf_file_path )
336 if __parse_configuration_file( vars, log, conf_file_contents):
339 raise BootManagerException( "Found configuration file plnode.txt " \
340 "in /usr, but was unable to parse it.")
343 raise BootManagerException, "Unable to find and read a node configuration file."
348 def __parse_configuration_file( vars, log, file_contents ):
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.
356 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
357 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
358 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
360 if file_contents is None:
361 log.write( "__parse_configuration_file called with no file contents\n" )
366 for line in file_contents.split("\n"):
368 line_num = line_num + 1
370 # if its a comment or a whitespace line, ignore
371 if line[:1] == "#" or string.strip(line) == "":
374 # file is setup as name="value" pairs
375 parts= string.split(line,"=")
377 log.write( "Invalid line %d in configuration file:\n" % line_num )
378 log.write( line + "\n" )
381 name= string.strip(parts[0])
382 value= string.strip(parts[1])
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" )
392 # get rid of the quotes around the value
393 value= string.strip(value[1:len(value)-1])
395 if name == "NODE_ID":
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" )
403 if name == "NODE_KEY":
404 vars['NODE_KEY']= value
405 vars['WAS_NODE_KEY_IN_CONF']= 1
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" )
413 NETWORK_SETTINGS['method']= value.strip()
415 if name == "IP_ADDRESS":
416 NETWORK_SETTINGS['ip']= value.strip()
418 if name == "IP_GATEWAY":
419 NETWORK_SETTINGS['gateway']= value.strip()
421 if name == "IP_NETMASK":
422 NETWORK_SETTINGS['netmask']= value.strip()
424 if name == "IP_NETADDR":
425 NETWORK_SETTINGS['network']= value.strip()
427 if name == "IP_BROADCASTADDR":
428 NETWORK_SETTINGS['broadcast']= value.strip()
430 if name == "IP_DNS1":
431 NETWORK_SETTINGS['dns1']= value.strip()
433 if name == "IP_DNS2":
434 NETWORK_SETTINGS['dns2']= value.strip()
436 if name == "HOST_NAME":
437 NETWORK_SETTINGS['hostname']= string.lower(value)
439 if name == "DOMAIN_NAME":
440 NETWORK_SETTINGS['domainname']= string.lower(value)
442 if name == "NET_DEVICE":
443 NETWORK_SETTINGS['mac']= string.upper(value)
445 if name == "DISCONNECTED_OPERATION":
446 vars['DISCONNECTED_OPERATION']= value.strip()
449 except IndexError, e:
450 log.write( "Unable to parse configuration file\n" )
453 # now if we are set to dhcp, clear out any fields
454 # that don't make sense
455 if NETWORK_SETTINGS["method"] == "dhcp":
456 NETWORK_SETTINGS["ip"]= ""
457 NETWORK_SETTINGS["gateway"]= ""
458 NETWORK_SETTINGS["netmask"]= ""
459 NETWORK_SETTINGS["network"]= ""
460 NETWORK_SETTINGS["broadcast"]= ""
461 NETWORK_SETTINGS["dns1"]= ""
462 NETWORK_SETTINGS["dns2"]= ""
464 log.write("Successfully read and parsed node configuration file.\n" )
466 # if the mac wasn't specified, read it in from the system.
467 if NETWORK_SETTINGS["mac"] == "":
469 mac_addr= utils.get_mac_from_interface(device)
472 log.write( "Could not get mac address for device eth0.\n" )
475 NETWORK_SETTINGS["mac"]= string.upper(mac_addr)
477 log.write( "Got mac address %s for device %s\n" %
478 (NETWORK_SETTINGS["mac"],device) )
481 # now, if the conf file didn't contain a node id, post the mac address
482 # to plc to get the node_id value
483 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
484 log.write( "Configuration file does not contain the node_id value.\n" )
485 log.write( "Querying PLC for node_id.\n" )
487 bs_request= BootServerRequest.BootServerRequest()
489 postVars= {"mac_addr" : NETWORK_SETTINGS["mac"]}
490 result= bs_request.DownloadFile( "%s/getnodeid.php" %
492 None, postVars, 1, 1,
495 log.write( "Unable to make request to get node_id.\n" )
499 node_id_file= file("/tmp/node_id","r")
500 node_id= string.strip(node_id_file.read())
503 log.write( "Unable to read node_id from /tmp/node_id\n" )
507 node_id= int(string.strip(node_id))
509 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
513 log.write( "Got node_id, but it returned -1\n\n" )
515 log.write( "------------------------------------------------------\n" )
516 log.write( "This indicates that this node could not be identified\n" )
517 log.write( "by PLC. You will need to add the node to your site,\n" )
518 log.write( "and regenerate the network configuration file.\n" )
519 log.write( "See the Technical Contact guide for node setup\n" )
520 log.write( "procedures.\n\n" )
521 log.write( "Boot process canceled until this is completed.\n" )
522 log.write( "------------------------------------------------------\n" )
524 cancel_boot_flag= "/tmp/CANCEL_BOOT"
525 # this will make the initial script stop requesting scripts from PLC
526 utils.sysexec( "touch %s" % cancel_boot_flag, log )
530 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
531 vars['NODE_ID']= node_id
535 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
536 log.write( "Configuration file does not contain a node_key value.\n" )
537 log.write( "Using boot nonce instead.\n" )
539 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
540 # can be read and used directly. 2.x cds stored in the same place
541 # but in binary form, so we need to convert it to ascii the same
542 # way the old boot scripts did so it matches whats in the db
544 if BOOT_CD_VERSION[0] == 2:
550 nonce_file= file("/tmp/nonce",read_mode)
551 nonce= nonce_file.read()
554 log.write( "Unable to read nonce from /tmp/nonce\n" )
557 if BOOT_CD_VERSION[0] == 2:
558 nonce= nonce.encode('hex')
560 # there is this nice bug in the php that currently accepts the
561 # nonce for the old scripts, in that if the nonce contains
562 # null chars (2.x cds sent as binary), then
563 # the nonce is truncated. so, do the same here, truncate the nonce
564 # at the first null ('00'). This could leave us with an empty string.
565 nonce_len= len(nonce)
566 for byte_index in range(0,nonce_len,2):
567 if nonce[byte_index:byte_index+2] == '00':
568 nonce= nonce[:byte_index]
571 nonce= string.strip(nonce)
573 log.write( "Read nonce, using as key.\n" )
574 vars['NODE_KEY']= nonce
577 # at this point, we've read the network configuration file.
578 # if we were setup using dhcp, get this system's current ip
579 # address and update the vars key ip, because it
580 # is needed for future api calls.
582 # at the same time, we can check to make sure that the hostname
583 # in the configuration file matches the ip address. if it fails
586 hostname= NETWORK_SETTINGS['hostname'] + "." + \
587 NETWORK_SETTINGS['domainname']
589 # set to 0 if any part of the hostname resolution check fails
590 hostname_resolve_ok= 1
592 # set to 0 if the above fails, and, we are using dhcp in which
593 # case we don't know the ip of this machine (without having to
594 # parse ifconfig or something). In that case, we won't be able
595 # to make api calls, so printing a message to the screen will
599 log.write( "Checking that hostname %s resolves\n" % hostname )
601 # try a regular dns lookup first
603 resolved_node_ip= socket.gethostbyname(hostname)
604 except socket.gaierror, e:
605 hostname_resolve_ok= 0
608 if NETWORK_SETTINGS['method'] == "dhcp":
609 if hostname_resolve_ok:
610 NETWORK_SETTINGS['ip']= resolved_node_ip
611 node_ip= resolved_node_ip
615 node_ip= NETWORK_SETTINGS['ip']
617 # make sure the dns lookup matches what the configuration file says
618 if hostname_resolve_ok:
619 if node_ip != resolved_node_ip:
620 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
621 (hostname,node_ip,resolved_node_ip) )
622 hostname_resolve_ok= 0
624 log.write( "Hostname %s correctly resolves to %s:\n" %
628 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
630 if not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION']:
631 log.write( "Hostname does not resolve correctly, will not continue.\n" )
633 if can_make_api_call:
634 log.write( "Notifying contacts of problem.\n" )
636 vars['BOOT_STATE']= 'dbg'
637 vars['STATE_CHANGE_NOTIFY']= 1
638 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
639 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
641 UpdateBootStateWithPLC.Run( vars, log )
644 log.write( "The hostname and/or ip in the network configuration\n" )
645 log.write( "file do not resolve and match.\n" )
646 log.write( "Please make sure the hostname set in the network\n" )
647 log.write( "configuration file resolves to the ip also specified\n" )
648 log.write( "there.\n\n" )
649 log.write( "Debug mode is being started on this cd. When the above\n" )
650 log.write( "is corrected, reboot the machine to try again.\n" )
652 raise BootManagerException, \
653 "Configured node hostname does not resolve."