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):
140 raise BootManagerException( "Found configuration file plnode.txt " \
141 "on floppy, but was unable to parse it." )
144 # try the old file name, same device. its actually number 3 on the search
145 # order, but do it now to save mounting/unmounting the disk twice.
146 # try to parse it later...
147 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
149 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
150 if os.access( conf_file_path, os.R_OK ):
152 old_conf_file= file(conf_file_path,"r")
153 old_conf_file_contents= old_conf_file.read()
154 old_conf_file.close()
155 log.write( "Read in contents of file %s\n" % conf_file_path )
157 log.write( "Unable to read file %s\n" % conf_file_path )
160 utils.sysexec_noerr( "umount %s" % mount_point, log )
162 # 2. check flash devices on 3.0 based cds
163 log.write( "Checking flash devices for plnode.txt file.\n" )
165 # this is done the same way the 3.0 cds do it, by attempting
166 # to mount and sd*1 devices that are removable
167 devices= os.listdir("/sys/block/")
169 for device in devices:
170 if device[:2] != "sd":
171 log.write( "Skipping non-scsi device %s\n" % device )
175 removable_file_path= "/sys/block/%s/removable" % device
177 removable= int(file(removable_file_path,"r").read().strip())
178 except ValueError, e:
184 log.write( "Skipping non-removable device %s\n" % device )
187 log.write( "Checking removable device %s\n" % device )
189 partitions= file("/proc/partitions", "r")
190 for line in partitions:
194 if not re.search("%s[0-9]*$" % device, line):
198 # major minor #blocks name
199 parts= string.split(line)
201 # ok, try to mount it and see if we have a conf file.
202 full_device= "/dev/%s" % parts[3]
203 except IndexError, e:
204 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
207 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
209 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
210 % (full_device,mount_point), log )
211 except BootManagerException, e:
212 log.write( "Unable to mount, trying next partition\n" )
215 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
217 log.write( "Checking for existence of %s\n" % conf_file_path )
218 if os.access( conf_file_path, os.R_OK ):
220 conf_file= file(conf_file_path,"r")
221 conf_file_contents= conf_file.read()
224 log.write( "Read in contents of file %s\n" % \
227 if __parse_configuration_file( vars, log, \
231 log.write( "Unable to read file %s\n" % conf_file_path )
233 utils.sysexec_noerr( "umount %s" % mount_point, log )
238 raise BootManagerException( \
239 "Found configuration file plnode.txt " \
240 "on floppy, but was unable to parse it.")
244 # 3. check standard floppy disk for old file name planet.cnf
245 log.write( "Checking standard floppy disk for planet.cnf file " \
246 "(for legacy nodes).\n" )
248 if old_conf_file_contents:
249 if __parse_configuration_file( vars, log, old_conf_file_contents):
252 raise BootManagerException( "Found configuration file planet.cnf " \
253 "on floppy, but was unable to parse it." )
256 # 4. check for plnode.txt in / (ramdisk)
257 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
259 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
261 log.write( "Checking for existence of %s\n" % conf_file_path )
262 if os.access(conf_file_path,os.R_OK):
264 conf_file= file(conf_file_path,"r")
265 conf_file_contents= conf_file.read()
267 log.write( "Read in contents of file %s\n" % conf_file_path )
269 log.write( "Unable to read file %s\n" % conf_file_path )
272 if __parse_configuration_file( vars, log, conf_file_contents):
275 raise BootManagerException( "Found configuration file plnode.txt " \
276 "in /, but was unable to parse it.")
279 # 5. check for plnode.txt in /usr/boot (mounted already)
280 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
282 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
284 log.write( "Checking for existence of %s\n" % conf_file_path )
285 if os.access(conf_file_path,os.R_OK):
287 conf_file= file(conf_file_path,"r")
288 conf_file_contents= conf_file.read()
290 log.write( "Read in contents of file %s\n" % conf_file_path )
292 log.write( "Unable to read file %s\n" % conf_file_path )
295 if __parse_configuration_file( vars, log, conf_file_contents):
298 raise BootManagerException( "Found configuration file plnode.txt " \
299 "in /usr/boot, but was unable to parse it.")
303 # 6. check for plnode.txt in /usr (mounted already)
304 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
306 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
308 log.write( "Checking for existence of %s\n" % conf_file_path )
309 if os.access(conf_file_path,os.R_OK):
311 conf_file= file(conf_file_path,"r")
312 conf_file_contents= conf_file.read()
314 log.write( "Read in contents of file %s\n" % conf_file_path )
316 log.write( "Unable to read file %s\n" % conf_file_path )
319 if __parse_configuration_file( vars, log, conf_file_contents):
322 raise BootManagerException( "Found configuration file plnode.txt " \
323 "in /usr, but was unable to parse it.")
326 raise BootManagerException, "Unable to find and read a node configuration file."
331 def __parse_configuration_file( vars, log, file_contents ):
333 parse a configuration file, set keys in var INTERFACE_SETTINGS
334 in vars (see comment for function ReadNodeConfiguration). this
335 also reads the mac address from the machine if successful parsing
336 of the configuration file is completed.
339 INTERFACE_SETTINGS= vars["INTERFACE_SETTINGS"]
341 if file_contents is None:
342 log.write( "__parse_configuration_file called with no file contents\n" )
347 for line in file_contents.split("\n"):
349 line_num = line_num + 1
351 # if its a comment or a whitespace line, ignore
352 if line[:1] == "#" or string.strip(line) == "":
355 # file is setup as name="value" pairs
356 parts= string.split(line, "=", 1)
358 name= string.strip(parts[0])
359 value= string.strip(parts[1])
361 # make sure value starts and ends with
362 # single or double quotes
363 quotes= value[0] + value[len(value)-1]
364 if quotes != "''" and quotes != '""':
365 log.write( "Invalid line %d in configuration file:\n" % line_num )
366 log.write( line + "\n" )
369 # get rid of the quotes around the value
370 value= string.strip(value[1:len(value)-1])
372 if name == "NODE_ID":
374 vars['NODE_ID']= int(value)
375 vars['WAS_NODE_ID_IN_CONF']= 1
376 except ValueError, e:
377 log.write( "Non-numeric node_id in configuration file.\n" )
380 if name == "NODE_KEY":
381 vars['NODE_KEY']= value
382 vars['WAS_NODE_KEY_IN_CONF']= 1
384 if name == "IP_METHOD":
385 value= string.lower(value)
386 if value != "static" and value != "dhcp":
387 log.write( "Invalid IP_METHOD in configuration file:\n" )
388 log.write( line + "\n" )
390 INTERFACE_SETTINGS['method']= value.strip()
392 if name == "IP_ADDRESS":
393 INTERFACE_SETTINGS['ip']= value.strip()
395 if name == "IP_GATEWAY":
396 INTERFACE_SETTINGS['gateway']= value.strip()
398 if name == "IP_NETMASK":
399 INTERFACE_SETTINGS['netmask']= value.strip()
401 if name == "IP_NETADDR":
402 INTERFACE_SETTINGS['network']= value.strip()
404 if name == "IP_BROADCASTADDR":
405 INTERFACE_SETTINGS['broadcast']= value.strip()
407 if name == "IP_DNS1":
408 INTERFACE_SETTINGS['dns1']= value.strip()
410 if name == "IP_DNS2":
411 INTERFACE_SETTINGS['dns2']= value.strip()
413 if name == "HOST_NAME":
414 INTERFACE_SETTINGS['hostname']= string.lower(value)
416 if name == "DOMAIN_NAME":
417 INTERFACE_SETTINGS['domainname']= string.lower(value)
419 if name == "NET_DEVICE":
420 INTERFACE_SETTINGS['mac']= string.upper(value)
422 if name == "DISCONNECTED_OPERATION":
423 vars['DISCONNECTED_OPERATION']= value.strip()
425 except IndexError, e:
426 log.write( "Unable to parse configuration file\n" )
429 # now if we are set to dhcp, clear out any fields
430 # that don't make sense
431 if INTERFACE_SETTINGS["method"] == "dhcp":
432 INTERFACE_SETTINGS["ip"]= ""
433 INTERFACE_SETTINGS["gateway"]= ""
434 INTERFACE_SETTINGS["netmask"]= ""
435 INTERFACE_SETTINGS["network"]= ""
436 INTERFACE_SETTINGS["broadcast"]= ""
437 INTERFACE_SETTINGS["dns1"]= ""
438 INTERFACE_SETTINGS["dns2"]= ""
440 log.write("Successfully read and parsed node configuration file.\n" )
442 # if the mac wasn't specified, read it in from the system.
443 if INTERFACE_SETTINGS["mac"] == "":
445 mac_addr= utils.get_mac_from_interface(device)
448 log.write( "Could not get mac address for device eth0.\n" )
451 INTERFACE_SETTINGS["mac"]= string.upper(mac_addr)
453 log.write( "Got mac address %s for device %s\n" %
454 (INTERFACE_SETTINGS["mac"],device) )
457 # now, if the conf file didn't contain a node id, post the mac address
458 # to plc to get the node_id value
459 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
460 log.write( "Configuration file does not contain the node_id value.\n" )
461 log.write( "Querying PLC for node_id.\n" )
463 bs_request= BootServerRequest.BootServerRequest(vars)
465 postVars= {"mac_addr" : INTERFACE_SETTINGS["mac"]}
466 result= bs_request.DownloadFile( "/boot/getnodeid.php",
467 None, postVars, 1, 1,
470 log.write( "Unable to make request to get node_id.\n" )
474 node_id_file= file("/tmp/node_id","r")
475 node_id= string.strip(node_id_file.read())
478 log.write( "Unable to read node_id from /tmp/node_id\n" )
482 node_id= int(string.strip(node_id))
484 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
488 log.write( "Got node_id, but it returned -1\n\n" )
490 log.write( "------------------------------------------------------\n" )
491 log.write( "This indicates that this node could not be identified\n" )
492 log.write( "by PLC. You will need to add the node to your site,\n" )
493 log.write( "and regenerate the network configuration file.\n" )
494 log.write( "See the Technical Contact guide for node setup\n" )
495 log.write( "procedures.\n\n" )
496 log.write( "Boot process canceled until this is completed.\n" )
497 log.write( "------------------------------------------------------\n" )
499 cancel_boot_flag= "/tmp/CANCEL_BOOT"
500 # this will make the initial script stop requesting scripts from PLC
501 utils.sysexec( "touch %s" % cancel_boot_flag, log )
505 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
506 vars['NODE_ID']= node_id
510 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
511 log.write( "Configuration file does not contain a node_key value.\n" )
512 log.write( "Using boot nonce instead.\n" )
514 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
515 # can be read and used directly. 2.x cds stored in the same place
516 # but in binary form, so we need to convert it to ascii the same
517 # way the old boot scripts did so it matches whats in the db
522 nonce_file= file("/tmp/nonce",read_mode)
523 nonce= nonce_file.read()
526 log.write( "Unable to read nonce from /tmp/nonce\n" )
529 nonce= string.strip(nonce)
531 log.write( "Read nonce, using as key.\n" )
532 vars['NODE_KEY']= nonce
535 # at this point, we've read the network configuration file.
536 # if we were setup using dhcp, get this system's current ip
537 # address and update the vars key ip, because it
538 # is needed for future api calls.
540 # at the same time, we can check to make sure that the hostname
541 # in the configuration file matches the ip address. if it fails
544 hostname= INTERFACE_SETTINGS['hostname'] + "." + \
545 INTERFACE_SETTINGS['domainname']
547 # set to 0 if any part of the hostname resolution check fails
548 hostname_resolve_ok= 1
550 # set to 0 if the above fails, and, we are using dhcp in which
551 # case we don't know the ip of this machine (without having to
552 # parse ifconfig or something). In that case, we won't be able
553 # to make api calls, so printing a message to the screen will
557 log.write( "Checking that hostname %s resolves\n" % hostname )
559 # try a regular dns lookup first
561 resolved_node_ip= socket.gethostbyname(hostname)
562 except socket.gaierror, e:
563 hostname_resolve_ok= 0
566 if INTERFACE_SETTINGS['method'] == "dhcp":
567 if hostname_resolve_ok:
568 INTERFACE_SETTINGS['ip']= resolved_node_ip
569 node_ip= resolved_node_ip
573 node_ip= INTERFACE_SETTINGS['ip']
575 # make sure the dns lookup matches what the configuration file says
576 if hostname_resolve_ok:
577 if node_ip != resolved_node_ip:
578 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
579 (hostname,node_ip,resolved_node_ip) )
580 hostname_resolve_ok= 0
582 log.write( "Hostname %s correctly resolves to %s:\n" %
586 vars["INTERFACE_SETTINGS"]= INTERFACE_SETTINGS
588 if (not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION'] and
589 'NAT_MODE' not in vars):
590 log.write( "Hostname does not resolve correctly, will not continue.\n" )
592 if can_make_api_call:
593 log.write( "Notifying contacts of problem.\n" )
595 vars['RUN_LEVEL']= 'failboot'
596 vars['STATE_CHANGE_NOTIFY']= 1
597 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
598 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
600 UpdateRunLevelWithPLC.Run( vars, log )
603 log.write( "The hostname and/or ip in the network configuration\n" )
604 log.write( "file do not resolve and match.\n" )
605 log.write( "Please make sure the hostname set in the network\n" )
606 log.write( "configuration file resolves to the ip also specified\n" )
607 log.write( "there.\n\n" )
608 log.write( "Debug mode is being started on this cd. When the above\n" )
609 log.write( "is corrected, reboot the machine to try again.\n" )
611 raise BootManagerException, \
612 "Configured node hostname does not resolve."