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:
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 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
89 if BOOT_CD_VERSION == "":
90 raise ValueError, "BOOT_CD_VERSION"
92 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
93 if SUPPORT_FILE_DIR == None:
94 raise ValueError, "SUPPORT_FILE_DIR"
97 raise BootManagerException, "Missing variable in vars: %s\n" % var
98 except ValueError, var:
99 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
103 NETWORK_SETTINGS['method']= "dhcp"
104 NETWORK_SETTINGS['ip']= ""
105 NETWORK_SETTINGS['mac']= ""
106 NETWORK_SETTINGS['gateway']= ""
107 NETWORK_SETTINGS['network']= ""
108 NETWORK_SETTINGS['broadcast']= ""
109 NETWORK_SETTINGS['netmask']= ""
110 NETWORK_SETTINGS['dns1']= ""
111 NETWORK_SETTINGS['dns2']= ""
112 NETWORK_SETTINGS['hostname']= "localhost"
113 NETWORK_SETTINGS['domainname']= "localdomain"
114 vars['NETWORK_SETTINGS']= NETWORK_SETTINGS
119 vars['WAS_NODE_ID_IN_CONF']= 0
120 vars['WAS_NODE_KEY_IN_CONF']= 0
122 vars['DISCONNECTED_OPERATION']= ''
124 # for any devices that need to be mounted to get the configuration
125 # file, mount them here.
126 mount_point= "/tmp/conffilemount"
127 utils.makedirs( mount_point )
129 old_conf_file_contents= None
130 conf_file_contents= None
133 # 1. check the regular floppy device
134 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
136 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
137 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
140 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
142 log.write( "Checking for existence of %s\n" % conf_file_path )
143 if os.access( conf_file_path, os.R_OK ):
145 conf_file= file(conf_file_path,"r")
146 conf_file_contents= conf_file.read()
148 log.write( "Read in contents of file %s\n" % conf_file_path )
150 log.write( "Unable to read file %s\n" % conf_file_path )
153 utils.sysexec_noerr( "umount %s" % mount_point, log )
154 if __parse_configuration_file( vars, log, conf_file_contents):
157 raise BootManagerException( "Found configuration file plnode.txt " \
158 "on floppy, but was unable to parse it." )
161 # try the old file name, same device. its actually number 3 on the search
162 # order, but do it now to save mounting/unmounting the disk twice.
163 # try to parse it later...
164 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
166 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
167 if os.access( conf_file_path, os.R_OK ):
169 old_conf_file= file(conf_file_path,"r")
170 old_conf_file_contents= old_conf_file.read()
171 old_conf_file.close()
172 log.write( "Read in contents of file %s\n" % conf_file_path )
174 log.write( "Unable to read file %s\n" % conf_file_path )
177 utils.sysexec_noerr( "umount %s" % mount_point, log )
181 if BOOT_CD_VERSION[0] >= 3:
182 # 2. check flash devices on 3.0 based cds
183 log.write( "Checking flash devices for plnode.txt file.\n" )
185 # this is done the same way the 3.0 cds do it, by attempting
186 # to mount and sd*1 devices that are removable
187 devices= os.listdir("/sys/block/")
189 for device in devices:
190 if device[:2] != "sd":
191 log.write( "Skipping non-scsi device %s\n" % device )
195 removable_file_path= "/sys/block/%s/removable" % device
197 removable= int(file(removable_file_path,"r").read().strip())
198 except ValueError, e:
204 log.write( "Skipping non-removable device %s\n" % device )
207 log.write( "Checking removable device %s\n" % device )
209 partitions= file("/proc/partitions", "r")
210 for line in partitions:
214 if not re.search("%s[0-9]*$" % device, line):
218 # major minor #blocks name
219 parts= string.split(line)
221 # ok, try to mount it and see if we have a conf file.
222 full_device= "/dev/%s" % parts[3]
223 except IndexError, e:
224 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
227 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
229 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
230 % (full_device,mount_point), log )
231 except BootManagerException, e:
232 log.write( "Unable to mount, trying next partition\n" )
235 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
237 log.write( "Checking for existence of %s\n" % conf_file_path )
238 if os.access( conf_file_path, os.R_OK ):
240 conf_file= file(conf_file_path,"r")
241 conf_file_contents= conf_file.read()
244 log.write( "Read in contents of file %s\n" % \
247 if __parse_configuration_file( vars, log, \
251 log.write( "Unable to read file %s\n" % conf_file_path )
253 utils.sysexec_noerr( "umount %s" % mount_point, log )
258 raise BootManagerException( \
259 "Found configuration file plnode.txt " \
260 "on floppy, but was unable to parse it.")
264 # 3. check standard floppy disk for old file name planet.cnf
265 log.write( "Checking standard floppy disk for planet.cnf file " \
268 if old_conf_file_contents:
269 if __parse_configuration_file( vars, log, old_conf_file_contents):
272 raise BootManagerException( "Found configuration file planet.cnf " \
273 "on floppy, but was unable to parse it." )
276 # 4. check for plnode.txt in / (ramdisk)
277 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
279 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
281 log.write( "Checking for existence of %s\n" % conf_file_path )
282 if os.access(conf_file_path,os.R_OK):
284 conf_file= file(conf_file_path,"r")
285 conf_file_contents= conf_file.read()
287 log.write( "Read in contents of file %s\n" % conf_file_path )
289 log.write( "Unable to read file %s\n" % conf_file_path )
292 if __parse_configuration_file( vars, log, conf_file_contents):
295 raise BootManagerException( "Found configuration file plnode.txt " \
296 "in /, but was unable to parse it.")
299 # 5. check for plnode.txt in /usr/boot (mounted already)
300 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
302 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
304 log.write( "Checking for existence of %s\n" % conf_file_path )
305 if os.access(conf_file_path,os.R_OK):
307 conf_file= file(conf_file_path,"r")
308 conf_file_contents= conf_file.read()
310 log.write( "Read in contents of file %s\n" % conf_file_path )
312 log.write( "Unable to read file %s\n" % conf_file_path )
315 if __parse_configuration_file( vars, log, conf_file_contents):
318 raise BootManagerException( "Found configuration file plnode.txt " \
319 "in /usr/boot, but was unable to parse it.")
323 # 6. check for plnode.txt in /usr (mounted already)
324 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
326 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
328 log.write( "Checking for existence of %s\n" % conf_file_path )
329 if os.access(conf_file_path,os.R_OK):
331 conf_file= file(conf_file_path,"r")
332 conf_file_contents= conf_file.read()
334 log.write( "Read in contents of file %s\n" % conf_file_path )
336 log.write( "Unable to read file %s\n" % conf_file_path )
339 if __parse_configuration_file( vars, log, conf_file_contents):
342 raise BootManagerException( "Found configuration file plnode.txt " \
343 "in /usr, but was unable to parse it.")
346 raise BootManagerException, "Unable to find and read a node configuration file."
351 def __parse_configuration_file( vars, log, file_contents ):
353 parse a configuration file, set keys in var NETWORK_SETTINGS
354 in vars (see comment for function ReadNodeConfiguration). this
355 also reads the mac address from the machine if successful parsing
356 of the configuration file is completed.
359 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
360 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
361 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
363 if file_contents is None:
364 log.write( "__parse_configuration_file called with no file contents\n" )
369 for line in file_contents.split("\n"):
371 line_num = line_num + 1
373 # if its a comment or a whitespace line, ignore
374 if line[:1] == "#" or string.strip(line) == "":
377 # file is setup as name="value" pairs
378 parts= string.split(line, "=", 1)
380 name= string.strip(parts[0])
381 value= string.strip(parts[1])
383 # make sure value starts and ends with
384 # single or double quotes
385 quotes= value[0] + value[len(value)-1]
386 if quotes != "''" and quotes != '""':
387 log.write( "Invalid line %d in configuration file:\n" % line_num )
388 log.write( line + "\n" )
391 # get rid of the quotes around the value
392 value= string.strip(value[1:len(value)-1])
394 if name == "NODE_ID":
396 vars['NODE_ID']= int(value)
397 vars['WAS_NODE_ID_IN_CONF']= 1
398 except ValueError, e:
399 log.write( "Non-numeric node_id in configuration file.\n" )
402 if name == "NODE_KEY":
403 vars['NODE_KEY']= value
404 vars['WAS_NODE_KEY_IN_CONF']= 1
406 if name == "IP_METHOD":
407 value= string.lower(value)
408 if value != "static" and value != "dhcp":
409 log.write( "Invalid IP_METHOD in configuration file:\n" )
410 log.write( line + "\n" )
412 NETWORK_SETTINGS['method']= value.strip()
414 if name == "IP_ADDRESS":
415 NETWORK_SETTINGS['ip']= value.strip()
417 if name == "IP_GATEWAY":
418 NETWORK_SETTINGS['gateway']= value.strip()
420 if name == "IP_NETMASK":
421 NETWORK_SETTINGS['netmask']= value.strip()
423 if name == "IP_NETADDR":
424 NETWORK_SETTINGS['network']= value.strip()
426 if name == "IP_BROADCASTADDR":
427 NETWORK_SETTINGS['broadcast']= value.strip()
429 if name == "IP_DNS1":
430 NETWORK_SETTINGS['dns1']= value.strip()
432 if name == "IP_DNS2":
433 NETWORK_SETTINGS['dns2']= value.strip()
435 if name == "HOST_NAME":
436 NETWORK_SETTINGS['hostname']= string.lower(value)
438 if name == "DOMAIN_NAME":
439 NETWORK_SETTINGS['domainname']= string.lower(value)
441 if name == "NET_DEVICE":
442 NETWORK_SETTINGS['mac']= string.upper(value)
444 if name == "DISCONNECTED_OPERATION":
445 vars['DISCONNECTED_OPERATION']= value.strip()
447 except IndexError, e:
448 log.write( "Unable to parse configuration file\n" )
451 # now if we are set to dhcp, clear out any fields
452 # that don't make sense
453 if NETWORK_SETTINGS["method"] == "dhcp":
454 NETWORK_SETTINGS["ip"]= ""
455 NETWORK_SETTINGS["gateway"]= ""
456 NETWORK_SETTINGS["netmask"]= ""
457 NETWORK_SETTINGS["network"]= ""
458 NETWORK_SETTINGS["broadcast"]= ""
459 NETWORK_SETTINGS["dns1"]= ""
460 NETWORK_SETTINGS["dns2"]= ""
462 log.write("Successfully read and parsed node configuration file.\n" )
464 # if the mac wasn't specified, read it in from the system.
465 if NETWORK_SETTINGS["mac"] == "":
467 mac_addr= utils.get_mac_from_interface(device)
470 log.write( "Could not get mac address for device eth0.\n" )
473 NETWORK_SETTINGS["mac"]= string.upper(mac_addr)
475 log.write( "Got mac address %s for device %s\n" %
476 (NETWORK_SETTINGS["mac"],device) )
479 # now, if the conf file didn't contain a node id, post the mac address
480 # to plc to get the node_id value
481 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
482 log.write( "Configuration file does not contain the node_id value.\n" )
483 log.write( "Querying PLC for node_id.\n" )
485 bs_request= BootServerRequest.BootServerRequest()
487 postVars= {"mac_addr" : NETWORK_SETTINGS["mac"]}
488 result= bs_request.DownloadFile( "%s/getnodeid.php" %
490 None, postVars, 1, 1,
493 log.write( "Unable to make request to get node_id.\n" )
497 node_id_file= file("/tmp/node_id","r")
498 node_id= string.strip(node_id_file.read())
501 log.write( "Unable to read node_id from /tmp/node_id\n" )
505 node_id= int(string.strip(node_id))
507 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
511 log.write( "Got node_id, but it returned -1\n\n" )
513 log.write( "------------------------------------------------------\n" )
514 log.write( "This indicates that this node could not be identified\n" )
515 log.write( "by PLC. You will need to add the node to your site,\n" )
516 log.write( "and regenerate the network configuration file.\n" )
517 log.write( "See the Technical Contact guide for node setup\n" )
518 log.write( "procedures.\n\n" )
519 log.write( "Boot process canceled until this is completed.\n" )
520 log.write( "------------------------------------------------------\n" )
522 cancel_boot_flag= "/tmp/CANCEL_BOOT"
523 # this will make the initial script stop requesting scripts from PLC
524 utils.sysexec( "touch %s" % cancel_boot_flag, log )
528 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
529 vars['NODE_ID']= node_id
533 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
534 log.write( "Configuration file does not contain a node_key value.\n" )
535 log.write( "Using boot nonce instead.\n" )
537 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
538 # can be read and used directly. 2.x cds stored in the same place
539 # but in binary form, so we need to convert it to ascii the same
540 # way the old boot scripts did so it matches whats in the db
542 if BOOT_CD_VERSION[0] == 2:
548 nonce_file= file("/tmp/nonce",read_mode)
549 nonce= nonce_file.read()
552 log.write( "Unable to read nonce from /tmp/nonce\n" )
555 if BOOT_CD_VERSION[0] == 2:
556 nonce= nonce.encode('hex')
558 # there is this nice bug in the php that currently accepts the
559 # nonce for the old scripts, in that if the nonce contains
560 # null chars (2.x cds sent as binary), then
561 # the nonce is truncated. so, do the same here, truncate the nonce
562 # at the first null ('00'). This could leave us with an empty string.
563 nonce_len= len(nonce)
564 for byte_index in range(0,nonce_len,2):
565 if nonce[byte_index:byte_index+2] == '00':
566 nonce= nonce[:byte_index]
569 nonce= string.strip(nonce)
571 log.write( "Read nonce, using as key.\n" )
572 vars['NODE_KEY']= nonce
575 # at this point, we've read the network configuration file.
576 # if we were setup using dhcp, get this system's current ip
577 # address and update the vars key ip, because it
578 # is needed for future api calls.
580 # at the same time, we can check to make sure that the hostname
581 # in the configuration file matches the ip address. if it fails
584 hostname= NETWORK_SETTINGS['hostname'] + "." + \
585 NETWORK_SETTINGS['domainname']
587 # set to 0 if any part of the hostname resolution check fails
588 hostname_resolve_ok= 1
590 # set to 0 if the above fails, and, we are using dhcp in which
591 # case we don't know the ip of this machine (without having to
592 # parse ifconfig or something). In that case, we won't be able
593 # to make api calls, so printing a message to the screen will
597 log.write( "Checking that hostname %s resolves\n" % hostname )
599 # try a regular dns lookup first
601 resolved_node_ip= socket.gethostbyname(hostname)
602 except socket.gaierror, e:
603 hostname_resolve_ok= 0
606 if NETWORK_SETTINGS['method'] == "dhcp":
607 if hostname_resolve_ok:
608 NETWORK_SETTINGS['ip']= resolved_node_ip
609 node_ip= resolved_node_ip
613 node_ip= NETWORK_SETTINGS['ip']
615 # make sure the dns lookup matches what the configuration file says
616 if hostname_resolve_ok:
617 if node_ip != resolved_node_ip:
618 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
619 (hostname,node_ip,resolved_node_ip) )
620 hostname_resolve_ok= 0
622 log.write( "Hostname %s correctly resolves to %s:\n" %
626 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
628 if not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION']:
629 log.write( "Hostname does not resolve correctly, will not continue.\n" )
631 if can_make_api_call:
632 log.write( "Notifying contacts of problem.\n" )
634 vars['BOOT_STATE']= 'dbg'
635 vars['STATE_CHANGE_NOTIFY']= 1
636 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
637 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
639 UpdateBootStateWithPLC.Run( vars, log )
642 log.write( "The hostname and/or ip in the network configuration\n" )
643 log.write( "file do not resolve and match.\n" )
644 log.write( "Please make sure the hostname set in the network\n" )
645 log.write( "configuration file resolves to the ip also specified\n" )
646 log.write( "there.\n\n" )
647 log.write( "Debug mode is being started on this cd. When the above\n" )
648 log.write( "is corrected, reboot the machine to try again.\n" )
650 raise BootManagerException, \
651 "Configured node hostname does not resolve."