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
24 # two possible names of the configuration files
25 NEW_CONF_FILE_NAME= "plnode.txt"
26 OLD_CONF_FILE_NAME= "planet.cnf"
31 read the machines node configuration file, which contains
32 the node key and the node_id for this machine.
34 these files can exist in several different locations with
35 several different names. Below is the search order:
37 filename floppy flash ramdisk cd
38 plnode.txt 1 2 4 (/) 5 (/usr/boot), 6 (/usr)
41 The locations will be searched in the above order, plnode.txt
42 will be checked first, then planet.cnf. Flash devices will only
43 be searched on 3.0 cds.
45 Because some of the earlier
46 boot cds don't validate the configuration file (which results
47 in a file named /tmp/planet-clean.cnf), and some do, lets
48 bypass this, and mount and attempt to read in the conf
49 file ourselves. If it doesn't exist, we cannot continue, and a
50 BootManagerException will be raised. If the configuration file is found
53 Expect the following variables from the store:
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 INTERFACE_SETTINGS A dictionary of the values from the network
63 configuration file. keys set:
69 broadcast IP_BROADCASTADDR
74 domainname DOMAIN_NAME
77 iwconfig WLAN_IWCONFIG
79 the mac address is read from the machine unless it exists in the
83 log.write( "\n\nStep: Reading node configuration file.\n" )
86 # make sure we have the variables we need
88 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
89 if SUPPORT_FILE_DIR == None:
90 raise ValueError, "SUPPORT_FILE_DIR"
93 raise BootManagerException, "Missing variable in vars: %s\n" % var
94 except ValueError, var:
95 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
98 INTERFACE_SETTINGS= {}
99 INTERFACE_SETTINGS['method']= "dhcp"
100 INTERFACE_SETTINGS['ip']= ""
101 INTERFACE_SETTINGS['mac']= ""
102 INTERFACE_SETTINGS['gateway']= ""
103 INTERFACE_SETTINGS['network']= ""
104 INTERFACE_SETTINGS['broadcast']= ""
105 INTERFACE_SETTINGS['netmask']= ""
106 INTERFACE_SETTINGS['dns1']= ""
107 INTERFACE_SETTINGS['dns2']= ""
108 INTERFACE_SETTINGS['hostname']= "localhost"
109 INTERFACE_SETTINGS['domainname']= "localdomain"
110 vars['INTERFACE_SETTINGS']= INTERFACE_SETTINGS
115 vars['WAS_NODE_ID_IN_CONF']= 0
116 vars['WAS_NODE_KEY_IN_CONF']= 0
118 vars['DISCONNECTED_OPERATION']= ''
120 # for any devices that need to be mounted to get the configuration
121 # file, mount them here.
122 mount_point= "/tmp/conffilemount"
123 utils.makedirs( mount_point )
125 old_conf_file_contents= None
126 conf_file_contents= None
129 # 1. check the regular floppy device
130 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
132 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
133 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
136 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
138 log.write( "Checking for existence of %s\n" % conf_file_path )
139 if os.access( conf_file_path, os.R_OK ):
141 conf_file= file(conf_file_path,"r")
142 conf_file_contents= conf_file.read()
144 log.write( "Read in contents of file %s\n" % conf_file_path )
146 log.write( "Unable to read file %s\n" % conf_file_path )
149 utils.sysexec_noerr( "umount %s" % mount_point, log )
150 if __parse_configuration_file( vars, log, conf_file_contents):
153 raise BootManagerException( "Found configuration file plnode.txt " \
154 "on floppy, but was unable to parse it." )
157 # try the old file name, same device. its actually number 3 on the search
158 # order, but do it now to save mounting/unmounting the disk twice.
159 # try to parse it later...
160 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
162 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
163 if os.access( conf_file_path, os.R_OK ):
165 old_conf_file= file(conf_file_path,"r")
166 old_conf_file_contents= old_conf_file.read()
167 old_conf_file.close()
168 log.write( "Read in contents of file %s\n" % conf_file_path )
170 log.write( "Unable to read file %s\n" % conf_file_path )
173 utils.sysexec_noerr( "umount %s" % mount_point, log )
175 # 2. check flash devices on 3.0 based cds
176 log.write( "Checking flash devices for plnode.txt file.\n" )
178 # this is done the same way the 3.0 cds do it, by attempting
179 # to mount and sd*1 devices that are removable
180 devices= os.listdir("/sys/block/")
182 for device in devices:
183 if device[:2] != "sd":
184 log.write( "Skipping non-scsi device %s\n" % device )
188 removable_file_path= "/sys/block/%s/removable" % device
190 removable= int(file(removable_file_path,"r").read().strip())
191 except ValueError, e:
197 log.write( "Skipping non-removable device %s\n" % device )
200 log.write( "Checking removable device %s\n" % device )
202 partitions= file("/proc/partitions", "r")
203 for line in partitions:
207 if not re.search("%s[0-9]*$" % device, line):
211 # major minor #blocks name
212 parts= string.split(line)
214 # ok, try to mount it and see if we have a conf file.
215 full_device= "/dev/%s" % parts[3]
216 except IndexError, e:
217 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
220 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
222 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
223 % (full_device,mount_point), log )
224 except BootManagerException, e:
225 log.write( "Unable to mount, trying next partition\n" )
228 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
230 log.write( "Checking for existence of %s\n" % conf_file_path )
231 if os.access( conf_file_path, os.R_OK ):
233 conf_file= file(conf_file_path,"r")
234 conf_file_contents= conf_file.read()
237 log.write( "Read in contents of file %s\n" % \
240 if __parse_configuration_file( vars, log, \
244 log.write( "Unable to read file %s\n" % conf_file_path )
246 utils.sysexec_noerr( "umount %s" % mount_point, log )
251 raise BootManagerException( \
252 "Found configuration file plnode.txt " \
253 "on floppy, but was unable to parse it.")
257 # 3. check standard floppy disk for old file name planet.cnf
258 log.write( "Checking standard floppy disk for planet.cnf file " \
259 "(for legacy nodes).\n" )
261 if old_conf_file_contents:
262 if __parse_configuration_file( vars, log, old_conf_file_contents):
265 raise BootManagerException( "Found configuration file planet.cnf " \
266 "on floppy, but was unable to parse it." )
269 # 4. check for plnode.txt in / (ramdisk)
270 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
272 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
274 log.write( "Checking for existence of %s\n" % conf_file_path )
275 if os.access(conf_file_path,os.R_OK):
277 conf_file= file(conf_file_path,"r")
278 conf_file_contents= conf_file.read()
280 log.write( "Read in contents of file %s\n" % conf_file_path )
282 log.write( "Unable to read file %s\n" % conf_file_path )
285 if __parse_configuration_file( vars, log, conf_file_contents):
288 raise BootManagerException( "Found configuration file plnode.txt " \
289 "in /, but was unable to parse it.")
292 # 5. check for plnode.txt in /usr/boot (mounted already)
293 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
295 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
297 log.write( "Checking for existence of %s\n" % conf_file_path )
298 if os.access(conf_file_path,os.R_OK):
300 conf_file= file(conf_file_path,"r")
301 conf_file_contents= conf_file.read()
303 log.write( "Read in contents of file %s\n" % conf_file_path )
305 log.write( "Unable to read file %s\n" % conf_file_path )
308 if __parse_configuration_file( vars, log, conf_file_contents):
311 raise BootManagerException( "Found configuration file plnode.txt " \
312 "in /usr/boot, but was unable to parse it.")
316 # 6. check for plnode.txt in /usr (mounted already)
317 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
319 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
321 log.write( "Checking for existence of %s\n" % conf_file_path )
322 if os.access(conf_file_path,os.R_OK):
324 conf_file= file(conf_file_path,"r")
325 conf_file_contents= conf_file.read()
327 log.write( "Read in contents of file %s\n" % conf_file_path )
329 log.write( "Unable to read file %s\n" % conf_file_path )
332 if __parse_configuration_file( vars, log, conf_file_contents):
335 raise BootManagerException( "Found configuration file plnode.txt " \
336 "in /usr, but was unable to parse it.")
339 raise BootManagerException, "Unable to find and read a node configuration file."
344 def __parse_configuration_file( vars, log, file_contents ):
346 parse a configuration file, set keys in var INTERFACE_SETTINGS
347 in vars (see comment for function ReadNodeConfiguration). this
348 also reads the mac address from the machine if successful parsing
349 of the configuration file is completed.
352 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
353 INTERFACE_SETTINGS= vars["INTERFACE_SETTINGS"]
355 if file_contents is None:
356 log.write( "__parse_configuration_file called with no file contents\n" )
361 for line in file_contents.split("\n"):
363 line_num = line_num + 1
365 # if its a comment or a whitespace line, ignore
366 if line[:1] == "#" or string.strip(line) == "":
369 # file is setup as name="value" pairs
370 parts= string.split(line, "=", 1)
372 name= string.strip(parts[0])
373 value= string.strip(parts[1])
375 # make sure value starts and ends with
376 # single or double quotes
377 quotes= value[0] + value[len(value)-1]
378 if quotes != "''" and quotes != '""':
379 log.write( "Invalid line %d in configuration file:\n" % line_num )
380 log.write( line + "\n" )
383 # get rid of the quotes around the value
384 value= string.strip(value[1:len(value)-1])
386 if name == "NODE_ID":
388 vars['NODE_ID']= int(value)
389 vars['WAS_NODE_ID_IN_CONF']= 1
390 except ValueError, e:
391 log.write( "Non-numeric node_id in configuration file.\n" )
394 if name == "NODE_KEY":
395 vars['NODE_KEY']= value
396 vars['WAS_NODE_KEY_IN_CONF']= 1
398 if name == "IP_METHOD":
399 value= string.lower(value)
400 if value != "static" and value != "dhcp":
401 log.write( "Invalid IP_METHOD in configuration file:\n" )
402 log.write( line + "\n" )
404 INTERFACE_SETTINGS['method']= value.strip()
406 if name == "IP_ADDRESS":
407 INTERFACE_SETTINGS['ip']= value.strip()
409 if name == "IP_GATEWAY":
410 INTERFACE_SETTINGS['gateway']= value.strip()
412 if name == "IP_NETMASK":
413 INTERFACE_SETTINGS['netmask']= value.strip()
415 if name == "IP_NETADDR":
416 INTERFACE_SETTINGS['network']= value.strip()
418 if name == "IP_BROADCASTADDR":
419 INTERFACE_SETTINGS['broadcast']= value.strip()
421 if name == "IP_DNS1":
422 INTERFACE_SETTINGS['dns1']= value.strip()
424 if name == "IP_DNS2":
425 INTERFACE_SETTINGS['dns2']= value.strip()
427 if name == "HOST_NAME":
428 INTERFACE_SETTINGS['hostname']= string.lower(value)
430 if name == "DOMAIN_NAME":
431 INTERFACE_SETTINGS['domainname']= string.lower(value)
433 if name == "NET_DEVICE":
434 INTERFACE_SETTINGS['mac']= string.upper(value)
436 if name == "DISCONNECTED_OPERATION":
437 vars['DISCONNECTED_OPERATION']= value.strip()
439 except IndexError, e:
440 log.write( "Unable to parse configuration file\n" )
443 # now if we are set to dhcp, clear out any fields
444 # that don't make sense
445 if INTERFACE_SETTINGS["method"] == "dhcp":
446 INTERFACE_SETTINGS["ip"]= ""
447 INTERFACE_SETTINGS["gateway"]= ""
448 INTERFACE_SETTINGS["netmask"]= ""
449 INTERFACE_SETTINGS["network"]= ""
450 INTERFACE_SETTINGS["broadcast"]= ""
451 INTERFACE_SETTINGS["dns1"]= ""
452 INTERFACE_SETTINGS["dns2"]= ""
454 log.write("Successfully read and parsed node configuration file.\n" )
456 # if the mac wasn't specified, read it in from the system.
457 if INTERFACE_SETTINGS["mac"] == "":
459 mac_addr= utils.get_mac_from_interface(device)
462 log.write( "Could not get mac address for device eth0.\n" )
465 INTERFACE_SETTINGS["mac"]= string.upper(mac_addr)
467 log.write( "Got mac address %s for device %s\n" %
468 (INTERFACE_SETTINGS["mac"],device) )
471 # now, if the conf file didn't contain a node id, post the mac address
472 # to plc to get the node_id value
473 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
474 log.write( "Configuration file does not contain the node_id value.\n" )
475 log.write( "Querying PLC for node_id.\n" )
477 bs_request= BootServerRequest.BootServerRequest(vars)
479 postVars= {"mac_addr" : INTERFACE_SETTINGS["mac"]}
480 result= bs_request.DownloadFile( "%s/getnodeid.php" %
482 None, postVars, 1, 1,
485 log.write( "Unable to make request to get node_id.\n" )
489 node_id_file= file("/tmp/node_id","r")
490 node_id= string.strip(node_id_file.read())
493 log.write( "Unable to read node_id from /tmp/node_id\n" )
497 node_id= int(string.strip(node_id))
499 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
503 log.write( "Got node_id, but it returned -1\n\n" )
505 log.write( "------------------------------------------------------\n" )
506 log.write( "This indicates that this node could not be identified\n" )
507 log.write( "by PLC. You will need to add the node to your site,\n" )
508 log.write( "and regenerate the network configuration file.\n" )
509 log.write( "See the Technical Contact guide for node setup\n" )
510 log.write( "procedures.\n\n" )
511 log.write( "Boot process canceled until this is completed.\n" )
512 log.write( "------------------------------------------------------\n" )
514 cancel_boot_flag= "/tmp/CANCEL_BOOT"
515 # this will make the initial script stop requesting scripts from PLC
516 utils.sysexec( "touch %s" % cancel_boot_flag, log )
520 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
521 vars['NODE_ID']= node_id
525 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
526 log.write( "Configuration file does not contain a node_key value.\n" )
527 log.write( "Using boot nonce instead.\n" )
529 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
530 # can be read and used directly. 2.x cds stored in the same place
531 # but in binary form, so we need to convert it to ascii the same
532 # way the old boot scripts did so it matches whats in the db
537 nonce_file= file("/tmp/nonce",read_mode)
538 nonce= nonce_file.read()
541 log.write( "Unable to read nonce from /tmp/nonce\n" )
544 nonce= string.strip(nonce)
546 log.write( "Read nonce, using as key.\n" )
547 vars['NODE_KEY']= nonce
550 # at this point, we've read the network configuration file.
551 # if we were setup using dhcp, get this system's current ip
552 # address and update the vars key ip, because it
553 # is needed for future api calls.
555 # at the same time, we can check to make sure that the hostname
556 # in the configuration file matches the ip address. if it fails
559 hostname= INTERFACE_SETTINGS['hostname'] + "." + \
560 INTERFACE_SETTINGS['domainname']
562 # set to 0 if any part of the hostname resolution check fails
563 hostname_resolve_ok= 1
565 # set to 0 if the above fails, and, we are using dhcp in which
566 # case we don't know the ip of this machine (without having to
567 # parse ifconfig or something). In that case, we won't be able
568 # to make api calls, so printing a message to the screen will
572 log.write( "Checking that hostname %s resolves\n" % hostname )
574 # try a regular dns lookup first
576 resolved_node_ip= socket.gethostbyname(hostname)
577 except socket.gaierror, e:
578 hostname_resolve_ok= 0
581 if INTERFACE_SETTINGS['method'] == "dhcp":
582 if hostname_resolve_ok:
583 INTERFACE_SETTINGS['ip']= resolved_node_ip
584 node_ip= resolved_node_ip
588 node_ip= INTERFACE_SETTINGS['ip']
590 # make sure the dns lookup matches what the configuration file says
591 if hostname_resolve_ok:
592 if node_ip != resolved_node_ip:
593 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
594 (hostname,node_ip,resolved_node_ip) )
595 hostname_resolve_ok= 0
597 log.write( "Hostname %s correctly resolves to %s:\n" %
601 vars["INTERFACE_SETTINGS"]= INTERFACE_SETTINGS
603 if (not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION'] and
604 (vars['NODE_MODEL_OPTIONS'] & ModelOptions.NAT) == 0):
605 log.write( "Hostname does not resolve correctly, will not continue.\n" )
607 if can_make_api_call:
608 log.write( "Notifying contacts of problem.\n" )
610 vars['RUN_LEVEL']= 'failboot'
611 vars['STATE_CHANGE_NOTIFY']= 1
612 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
613 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
615 UpdateRunLevelWithPLC.Run( vars, log )
618 log.write( "The hostname and/or ip in the network configuration\n" )
619 log.write( "file do not resolve and match.\n" )
620 log.write( "Please make sure the hostname set in the network\n" )
621 log.write( "configuration file resolves to the ip also specified\n" )
622 log.write( "there.\n\n" )
623 log.write( "Debug mode is being started on this cd. When the above\n" )
624 log.write( "is corrected, reboot the machine to try again.\n" )
626 raise BootManagerException, \
627 "Configured node hostname does not resolve."