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:
53 SUPPORT_FILE_DIR directory on the boot servers containing
54 scripts and support files
56 Sets the following variables from the configuration file:
57 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
58 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
59 NONE_ID The db node_id for this machine
60 NODE_KEY The key for this node
61 INTERFACE_SETTINGS A dictionary of the values from the network
62 configuration file. keys set:
68 broadcast IP_BROADCASTADDR
73 domainname DOMAIN_NAME
76 iwconfig WLAN_IWCONFIG
78 the mac address is read from the machine unless it exists in the
82 log.write( "\n\nStep: Reading node configuration file.\n" )
85 # make sure we have the variables we need
87 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
88 if SUPPORT_FILE_DIR == None:
89 raise ValueError, "SUPPORT_FILE_DIR"
92 raise BootManagerException, "Missing variable in vars: %s\n" % var
93 except ValueError, var:
94 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
97 INTERFACE_SETTINGS= {}
98 INTERFACE_SETTINGS['method']= "dhcp"
99 INTERFACE_SETTINGS['ip']= ""
100 INTERFACE_SETTINGS['mac']= ""
101 INTERFACE_SETTINGS['gateway']= ""
102 INTERFACE_SETTINGS['network']= ""
103 INTERFACE_SETTINGS['broadcast']= ""
104 INTERFACE_SETTINGS['netmask']= ""
105 INTERFACE_SETTINGS['dns1']= ""
106 INTERFACE_SETTINGS['dns2']= ""
107 INTERFACE_SETTINGS['hostname']= "localhost"
108 INTERFACE_SETTINGS['domainname']= "localdomain"
109 vars['INTERFACE_SETTINGS']= INTERFACE_SETTINGS
114 vars['WAS_NODE_ID_IN_CONF']= 0
115 vars['WAS_NODE_KEY_IN_CONF']= 0
117 vars['DISCONNECTED_OPERATION']= ''
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 )
174 # 2. check flash devices on 3.0 based cds
175 log.write( "Checking flash devices for plnode.txt file.\n" )
177 # this is done the same way the 3.0 cds do it, by attempting
178 # to mount and sd*1 devices that are removable
179 devices= os.listdir("/sys/block/")
181 for device in devices:
182 if device[:2] != "sd":
183 log.write( "Skipping non-scsi device %s\n" % device )
187 removable_file_path= "/sys/block/%s/removable" % device
189 removable= int(file(removable_file_path,"r").read().strip())
190 except ValueError, e:
196 log.write( "Skipping non-removable device %s\n" % device )
199 log.write( "Checking removable device %s\n" % device )
201 partitions= file("/proc/partitions", "r")
202 for line in partitions:
206 if not re.search("%s[0-9]*$" % device, line):
210 # major minor #blocks name
211 parts= string.split(line)
213 # ok, try to mount it and see if we have a conf file.
214 full_device= "/dev/%s" % parts[3]
215 except IndexError, e:
216 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
219 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
221 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
222 % (full_device,mount_point), log )
223 except BootManagerException, e:
224 log.write( "Unable to mount, trying next partition\n" )
227 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
229 log.write( "Checking for existence of %s\n" % conf_file_path )
230 if os.access( conf_file_path, os.R_OK ):
232 conf_file= file(conf_file_path,"r")
233 conf_file_contents= conf_file.read()
236 log.write( "Read in contents of file %s\n" % \
239 if __parse_configuration_file( vars, log, \
243 log.write( "Unable to read file %s\n" % conf_file_path )
245 utils.sysexec_noerr( "umount %s" % mount_point, log )
250 raise BootManagerException( \
251 "Found configuration file plnode.txt " \
252 "on floppy, but was unable to parse it.")
256 # 3. check standard floppy disk for old file name planet.cnf
257 log.write( "Checking standard floppy disk for planet.cnf file " \
258 "(for legacy nodes).\n" )
260 if old_conf_file_contents:
261 if __parse_configuration_file( vars, log, old_conf_file_contents):
264 raise BootManagerException( "Found configuration file planet.cnf " \
265 "on floppy, but was unable to parse it." )
268 # 4. check for plnode.txt in / (ramdisk)
269 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
271 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
273 log.write( "Checking for existence of %s\n" % conf_file_path )
274 if os.access(conf_file_path,os.R_OK):
276 conf_file= file(conf_file_path,"r")
277 conf_file_contents= conf_file.read()
279 log.write( "Read in contents of file %s\n" % conf_file_path )
281 log.write( "Unable to read file %s\n" % conf_file_path )
284 if __parse_configuration_file( vars, log, conf_file_contents):
287 raise BootManagerException( "Found configuration file plnode.txt " \
288 "in /, but was unable to parse it.")
291 # 5. check for plnode.txt in /usr/boot (mounted already)
292 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
294 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
296 log.write( "Checking for existence of %s\n" % conf_file_path )
297 if os.access(conf_file_path,os.R_OK):
299 conf_file= file(conf_file_path,"r")
300 conf_file_contents= conf_file.read()
302 log.write( "Read in contents of file %s\n" % conf_file_path )
304 log.write( "Unable to read file %s\n" % conf_file_path )
307 if __parse_configuration_file( vars, log, conf_file_contents):
310 raise BootManagerException( "Found configuration file plnode.txt " \
311 "in /usr/boot, but was unable to parse it.")
315 # 6. check for plnode.txt in /usr (mounted already)
316 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
318 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
320 log.write( "Checking for existence of %s\n" % conf_file_path )
321 if os.access(conf_file_path,os.R_OK):
323 conf_file= file(conf_file_path,"r")
324 conf_file_contents= conf_file.read()
326 log.write( "Read in contents of file %s\n" % conf_file_path )
328 log.write( "Unable to read file %s\n" % conf_file_path )
331 if __parse_configuration_file( vars, log, conf_file_contents):
334 raise BootManagerException( "Found configuration file plnode.txt " \
335 "in /usr, but was unable to parse it.")
338 raise BootManagerException, "Unable to find and read a node configuration file."
343 def __parse_configuration_file( vars, log, file_contents ):
345 parse a configuration file, set keys in var INTERFACE_SETTINGS
346 in vars (see comment for function ReadNodeConfiguration). this
347 also reads the mac address from the machine if successful parsing
348 of the configuration file is completed.
351 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
352 INTERFACE_SETTINGS= vars["INTERFACE_SETTINGS"]
354 if file_contents is None:
355 log.write( "__parse_configuration_file called with no file contents\n" )
360 for line in file_contents.split("\n"):
362 line_num = line_num + 1
364 # if its a comment or a whitespace line, ignore
365 if line[:1] == "#" or string.strip(line) == "":
368 # file is setup as name="value" pairs
369 parts= string.split(line, "=", 1)
371 name= string.strip(parts[0])
372 value= string.strip(parts[1])
374 # make sure value starts and ends with
375 # single or double quotes
376 quotes= value[0] + value[len(value)-1]
377 if quotes != "''" and quotes != '""':
378 log.write( "Invalid line %d in configuration file:\n" % line_num )
379 log.write( line + "\n" )
382 # get rid of the quotes around the value
383 value= string.strip(value[1:len(value)-1])
385 if name == "NODE_ID":
387 vars['NODE_ID']= int(value)
388 vars['WAS_NODE_ID_IN_CONF']= 1
389 except ValueError, e:
390 log.write( "Non-numeric node_id in configuration file.\n" )
393 if name == "NODE_KEY":
394 vars['NODE_KEY']= value
395 vars['WAS_NODE_KEY_IN_CONF']= 1
397 if name == "IP_METHOD":
398 value= string.lower(value)
399 if value != "static" and value != "dhcp":
400 log.write( "Invalid IP_METHOD in configuration file:\n" )
401 log.write( line + "\n" )
403 INTERFACE_SETTINGS['method']= value.strip()
405 if name == "IP_ADDRESS":
406 INTERFACE_SETTINGS['ip']= value.strip()
408 if name == "IP_GATEWAY":
409 INTERFACE_SETTINGS['gateway']= value.strip()
411 if name == "IP_NETMASK":
412 INTERFACE_SETTINGS['netmask']= value.strip()
414 if name == "IP_NETADDR":
415 INTERFACE_SETTINGS['network']= value.strip()
417 if name == "IP_BROADCASTADDR":
418 INTERFACE_SETTINGS['broadcast']= value.strip()
420 if name == "IP_DNS1":
421 INTERFACE_SETTINGS['dns1']= value.strip()
423 if name == "IP_DNS2":
424 INTERFACE_SETTINGS['dns2']= value.strip()
426 if name == "HOST_NAME":
427 INTERFACE_SETTINGS['hostname']= string.lower(value)
429 if name == "DOMAIN_NAME":
430 INTERFACE_SETTINGS['domainname']= string.lower(value)
432 if name == "NET_DEVICE":
433 INTERFACE_SETTINGS['mac']= string.upper(value)
435 if name == "DISCONNECTED_OPERATION":
436 vars['DISCONNECTED_OPERATION']= value.strip()
438 except IndexError, e:
439 log.write( "Unable to parse configuration file\n" )
442 # now if we are set to dhcp, clear out any fields
443 # that don't make sense
444 if INTERFACE_SETTINGS["method"] == "dhcp":
445 INTERFACE_SETTINGS["ip"]= ""
446 INTERFACE_SETTINGS["gateway"]= ""
447 INTERFACE_SETTINGS["netmask"]= ""
448 INTERFACE_SETTINGS["network"]= ""
449 INTERFACE_SETTINGS["broadcast"]= ""
450 INTERFACE_SETTINGS["dns1"]= ""
451 INTERFACE_SETTINGS["dns2"]= ""
453 log.write("Successfully read and parsed node configuration file.\n" )
455 # if the mac wasn't specified, read it in from the system.
456 if INTERFACE_SETTINGS["mac"] == "":
458 mac_addr= utils.get_mac_from_interface(device)
461 log.write( "Could not get mac address for device eth0.\n" )
464 INTERFACE_SETTINGS["mac"]= string.upper(mac_addr)
466 log.write( "Got mac address %s for device %s\n" %
467 (INTERFACE_SETTINGS["mac"],device) )
470 # now, if the conf file didn't contain a node id, post the mac address
471 # to plc to get the node_id value
472 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
473 log.write( "Configuration file does not contain the node_id value.\n" )
474 log.write( "Querying PLC for node_id.\n" )
476 bs_request= BootServerRequest.BootServerRequest(vars)
478 postVars= {"mac_addr" : INTERFACE_SETTINGS["mac"]}
479 result= bs_request.DownloadFile( "%s/getnodeid.php" %
481 None, postVars, 1, 1,
484 log.write( "Unable to make request to get node_id.\n" )
488 node_id_file= file("/tmp/node_id","r")
489 node_id= string.strip(node_id_file.read())
492 log.write( "Unable to read node_id from /tmp/node_id\n" )
496 node_id= int(string.strip(node_id))
498 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
502 log.write( "Got node_id, but it returned -1\n\n" )
504 log.write( "------------------------------------------------------\n" )
505 log.write( "This indicates that this node could not be identified\n" )
506 log.write( "by PLC. You will need to add the node to your site,\n" )
507 log.write( "and regenerate the network configuration file.\n" )
508 log.write( "See the Technical Contact guide for node setup\n" )
509 log.write( "procedures.\n\n" )
510 log.write( "Boot process canceled until this is completed.\n" )
511 log.write( "------------------------------------------------------\n" )
513 cancel_boot_flag= "/tmp/CANCEL_BOOT"
514 # this will make the initial script stop requesting scripts from PLC
515 utils.sysexec( "touch %s" % cancel_boot_flag, log )
519 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
520 vars['NODE_ID']= node_id
524 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
525 log.write( "Configuration file does not contain a node_key value.\n" )
526 log.write( "Using boot nonce instead.\n" )
528 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
529 # can be read and used directly. 2.x cds stored in the same place
530 # but in binary form, so we need to convert it to ascii the same
531 # way the old boot scripts did so it matches whats in the db
536 nonce_file= file("/tmp/nonce",read_mode)
537 nonce= nonce_file.read()
540 log.write( "Unable to read nonce from /tmp/nonce\n" )
543 nonce= string.strip(nonce)
545 log.write( "Read nonce, using as key.\n" )
546 vars['NODE_KEY']= nonce
549 # at this point, we've read the network configuration file.
550 # if we were setup using dhcp, get this system's current ip
551 # address and update the vars key ip, because it
552 # is needed for future api calls.
554 # at the same time, we can check to make sure that the hostname
555 # in the configuration file matches the ip address. if it fails
558 hostname= INTERFACE_SETTINGS['hostname'] + "." + \
559 INTERFACE_SETTINGS['domainname']
561 # set to 0 if any part of the hostname resolution check fails
562 hostname_resolve_ok= 1
564 # set to 0 if the above fails, and, we are using dhcp in which
565 # case we don't know the ip of this machine (without having to
566 # parse ifconfig or something). In that case, we won't be able
567 # to make api calls, so printing a message to the screen will
571 log.write( "Checking that hostname %s resolves\n" % hostname )
573 # try a regular dns lookup first
575 resolved_node_ip= socket.gethostbyname(hostname)
576 except socket.gaierror, e:
577 hostname_resolve_ok= 0
580 if INTERFACE_SETTINGS['method'] == "dhcp":
581 if hostname_resolve_ok:
582 INTERFACE_SETTINGS['ip']= resolved_node_ip
583 node_ip= resolved_node_ip
587 node_ip= INTERFACE_SETTINGS['ip']
589 # make sure the dns lookup matches what the configuration file says
590 if hostname_resolve_ok:
591 if node_ip != resolved_node_ip:
592 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
593 (hostname,node_ip,resolved_node_ip) )
594 hostname_resolve_ok= 0
596 log.write( "Hostname %s correctly resolves to %s:\n" %
600 vars["INTERFACE_SETTINGS"]= INTERFACE_SETTINGS
602 if not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION']:
603 log.write( "Hostname does not resolve correctly, will not continue.\n" )
605 if can_make_api_call:
606 log.write( "Notifying contacts of problem.\n" )
608 vars['RUN_LEVEL']= 'failboot'
609 vars['STATE_CHANGE_NOTIFY']= 1
610 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
611 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
613 UpdateRunLevelWithPLC.Run( vars, log )
616 log.write( "The hostname and/or ip in the network configuration\n" )
617 log.write( "file do not resolve and match.\n" )
618 log.write( "Please make sure the hostname set in the network\n" )
619 log.write( "configuration file resolves to the ip also specified\n" )
620 log.write( "there.\n\n" )
621 log.write( "Debug mode is being started on this cd. When the above\n" )
622 log.write( "is corrected, reboot the machine to try again.\n" )
624 raise BootManagerException, \
625 "Configured node hostname does not resolve."