1 # Copyright (c) 2003 Intel Corporation
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials provided
14 # with the distribution.
16 # * Neither the name of the Intel Corporation nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
24 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 # EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
33 # YOUR JURISDICTION. It is licensee's responsibility to comply with any
34 # export regulations applicable in licensee's jurisdiction. Under
35 # CURRENT (May 2000) U.S. export regulations this software is eligible
36 # for export from the U.S. and can be downloaded by or otherwise
37 # exported or reexported worldwide EXCEPT to U.S. embargoed destinations
38 # which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
39 # Afghanistan and any other country to which the U.S. has embargoed
43 import sys, os, traceback
49 from Exceptions import *
50 import BootServerRequest
53 import notify_messages
54 import UpdateBootStateWithPLC
57 # two possible names of the configuration files
58 NEW_CONF_FILE_NAME= "plnode.txt"
59 OLD_CONF_FILE_NAME= "planet.cnf"
64 read the machines node configuration file, which contains
65 the node key and the node_id for this machine.
67 these files can exist in several different locations with
68 several different names. Below is the search order:
70 filename floppy flash ramdisk cd
71 plnode.txt 1 2 4 (/) 5 (/usr/boot), 6 (/usr)
74 The locations will be searched in the above order, plnode.txt
75 will be checked first, then planet.cnf. Flash devices will only
76 be searched on 3.0 cds.
78 Because some of the earlier
79 boot cds don't validate the configuration file (which results
80 in a file named /tmp/planet-clean.cnf), and some do, lets
81 bypass this, and mount and attempt to read in the conf
82 file ourselves. If it doesn't exist, we cannot continue, and a
83 BootManagerException will be raised. If the configuration file is found
86 Expect the following variables from the store:
87 BOOT_CD_VERSION A tuple of the current bootcd version
88 SUPPORT_FILE_DIR directory on the boot servers containing
89 scripts and support files
91 Sets the following variables from the configuration file:
92 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
93 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
94 NONE_ID The db node_id for this machine
95 NODE_KEY The key for this node
96 NETWORK_SETTINGS A dictionary of the values from the network
97 configuration file. keys set:
110 the mac address is read from the machine unless it exists in the
114 log.write( "\n\nStep: Reading node configuration file.\n" )
117 # make sure we have the variables we need
119 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
120 if BOOT_CD_VERSION == "":
121 raise ValueError, "BOOT_CD_VERSION"
123 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
124 if SUPPORT_FILE_DIR == None:
125 raise ValueError, "SUPPORT_FILE_DIR"
127 except KeyError, var:
128 raise BootManagerException, "Missing variable in vars: %s\n" % var
129 except ValueError, var:
130 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
134 NETWORK_SETTINGS['method']= "dhcp"
135 NETWORK_SETTINGS['ip']= ""
136 NETWORK_SETTINGS['mac']= ""
137 NETWORK_SETTINGS['gateway']= ""
138 NETWORK_SETTINGS['network']= ""
139 NETWORK_SETTINGS['broadcast']= ""
140 NETWORK_SETTINGS['netmask']= ""
141 NETWORK_SETTINGS['dns1']= ""
142 NETWORK_SETTINGS['dns2']= ""
143 NETWORK_SETTINGS['hostname']= "localhost"
144 NETWORK_SETTINGS['domainname']= "localdomain"
145 vars['NETWORK_SETTINGS']= NETWORK_SETTINGS
150 vars['WAS_NODE_ID_IN_CONF']= 0
151 vars['WAS_NODE_KEY_IN_CONF']= 0
153 # for any devices that need to be mounted to get the configuration
154 # file, mount them here.
155 mount_point= "/tmp/conffilemount"
156 utils.makedirs( mount_point )
158 old_conf_file_contents= None
159 conf_file_contents= None
162 # 1. check the regular floppy device
163 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
165 log.write( "Mounting /dev/fd0 on %s\n" % mount_point )
166 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
169 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
171 log.write( "Checking for existence of %s\n" % conf_file_path )
172 if os.access( conf_file_path, os.R_OK ):
174 conf_file= file(conf_file_path,"r")
175 conf_file_contents= conf_file.read()
177 log.write( "Read in contents of file %s\n" % conf_file_path )
179 log.write( "Unable to read file %s\n" % conf_file_path )
182 utils.sysexec_noerr( "umount %s" % mount_point, log )
183 if __parse_configuration_file( vars, log, conf_file_contents):
186 raise BootManagerException( "Found configuration file plnode.txt " \
187 "on floppy, but was unable to parse it." )
190 # try the old file name, same device. its actually number 3 on the search
191 # order, but do it now to save mounting/unmounting the disk twice.
192 # try to parse it later...
193 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
195 log.write( "Checking for existence of %s (used later)\n" % conf_file_path )
196 if os.access( conf_file_path, os.R_OK ):
198 old_conf_file= file(conf_file_path,"r")
199 old_conf_file_contents= old_conf_file.read()
200 old_conf_file.close()
201 log.write( "Read in contents of file %s\n" % conf_file_path )
203 log.write( "Unable to read file %s\n" % conf_file_path )
206 utils.sysexec_noerr( "umount %s" % mount_point, log )
210 if BOOT_CD_VERSION[0] == 3:
211 # 2. check flash devices on 3.0 based cds
212 log.write( "Checking flash devices for plnode.txt file.\n" )
214 # this is done the same way the 3.0 cds do it, by attempting
215 # to mount and sd*1 devices that are removable
216 devices= os.listdir("/sys/block/")
218 for device in devices:
219 if device[:2] != "sd":
220 log.write( "Skipping non-scsi device %s\n" % device )
224 removable_file_path= "/sys/block/%s/removable" % device
226 removable= int(file(removable_file_path,"r").read().strip())
227 except ValueError, e:
233 log.write( "Skipping non-removable device %s\n" % device )
236 log.write( "Checking removable device %s\n" % device )
238 partitions= file("/proc/partitions", "r")
239 for line in partitions:
243 if not re.search("%s[0-9]*$" % device, line):
247 # major minor #blocks name
248 parts= string.split(line)
250 # ok, try to mount it and see if we have a conf file.
251 full_device= "/dev/%s" % parts[3]
252 except IndexError, e:
253 log.write( "Incorrect /proc/partitions line:\n%s\n" % line )
256 log.write( "Mounting %s on %s\n" % (full_device,mount_point) )
258 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
259 % (full_device,mount_point), log )
260 except BootManagerException, e:
261 log.write( "Unable to mount, trying next partition\n" )
264 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
266 log.write( "Checking for existence of %s\n" % conf_file_path )
267 if os.access( conf_file_path, os.R_OK ):
269 conf_file= file(conf_file_path,"r")
270 conf_file_contents= conf_file.read()
273 log.write( "Read in contents of file %s\n" % \
276 if __parse_configuration_file( vars, log, \
280 log.write( "Unable to read file %s\n" % conf_file_path )
282 utils.sysexec_noerr( "umount %s" % mount_point, log )
287 raise BootManagerException( \
288 "Found configuration file plnode.txt " \
289 "on floppy, but was unable to parse it.")
293 # 3. check standard floppy disk for old file name planet.cnf
294 log.write( "Checking standard floppy disk for planet.cnf file " \
297 if old_conf_file_contents:
298 if __parse_configuration_file( vars, log, old_conf_file_contents):
301 raise BootManagerException( "Found configuration file planet.cnf " \
302 "on floppy, but was unable to parse it." )
305 # 4. check for plnode.txt in / (ramdisk)
306 log.write( "Checking / (ramdisk) for plnode.txt file.\n" )
308 conf_file_path= "/%s" % NEW_CONF_FILE_NAME
310 log.write( "Checking for existence of %s\n" % conf_file_path )
311 if os.access(conf_file_path,os.R_OK):
313 conf_file= file(conf_file_path,"r")
314 conf_file_contents= conf_file.read()
316 log.write( "Read in contents of file %s\n" % conf_file_path )
318 log.write( "Unable to read file %s\n" % conf_file_path )
321 if __parse_configuration_file( vars, log, conf_file_contents):
324 raise BootManagerException( "Found configuration file plnode.txt " \
325 "in /, but was unable to parse it.")
328 # 5. check for plnode.txt in /usr/boot (mounted already)
329 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
331 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
333 log.write( "Checking for existence of %s\n" % conf_file_path )
334 if os.access(conf_file_path,os.R_OK):
336 conf_file= file(conf_file_path,"r")
337 conf_file_contents= conf_file.read()
339 log.write( "Read in contents of file %s\n" % conf_file_path )
341 log.write( "Unable to read file %s\n" % conf_file_path )
344 if __parse_configuration_file( vars, log, conf_file_contents):
347 raise BootManagerException( "Found configuration file plnode.txt " \
348 "in /usr/boot, but was unable to parse it.")
352 # 6. check for plnode.txt in /usr (mounted already)
353 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
355 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
357 log.write( "Checking for existence of %s\n" % conf_file_path )
358 if os.access(conf_file_path,os.R_OK):
360 conf_file= file(conf_file_path,"r")
361 conf_file_contents= conf_file.read()
363 log.write( "Read in contents of file %s\n" % conf_file_path )
365 log.write( "Unable to read file %s\n" % conf_file_path )
368 if __parse_configuration_file( vars, log, conf_file_contents):
371 raise BootManagerException( "Found configuration file plnode.txt " \
372 "in /usr, but was unable to parse it.")
375 raise BootManagerException, "Unable to find and read a node configuration file."
380 def __parse_configuration_file( vars, log, file_contents ):
382 parse a configuration file, set keys in var NETWORK_SETTINGS
383 in vars (see comment for function ReadNodeConfiguration). this
384 also reads the mac address from the machine if successful parsing
385 of the configuration file is completed.
388 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
389 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
390 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
392 if file_contents is None:
393 log.write( "__parse_configuration_file called with no file contents\n" )
398 for line in file_contents.split("\n"):
400 line_num = line_num + 1
402 # if its a comment or a whitespace line, ignore
403 if line[:1] == "#" or string.strip(line) == "":
406 # file is setup as name="value" pairs
407 parts= string.split(line,"=")
409 log.write( "Invalid line %d in configuration file:\n" % line_num )
410 log.write( line + "\n" )
413 name= string.strip(parts[0])
414 value= string.strip(parts[1])
416 # make sure value starts and ends with
417 # single or double quotes
418 quotes= value[0] + value[len(value)-1]
419 if quotes != "''" and quotes != '""':
420 log.write( "Invalid line %d in configuration file:\n" % line_num )
421 log.write( line + "\n" )
424 # get rid of the quotes around the value
425 value= string.strip(value[1:len(value)-1])
427 if name == "NODE_ID":
429 vars['NODE_ID']= int(value)
430 vars['WAS_NODE_ID_IN_CONF']= 1
431 except ValueError, e:
432 log.write( "Non-numeric node_id in configuration file.\n" )
435 if name == "NODE_KEY":
436 vars['NODE_KEY']= value
437 vars['WAS_NODE_KEY_IN_CONF']= 1
439 if name == "IP_METHOD":
440 value= string.lower(value)
441 if value != "static" and value != "dhcp":
442 log.write( "Invalid IP_METHOD in configuration file:\n" )
443 log.write( line + "\n" )
445 NETWORK_SETTINGS['method']= value.strip()
447 if name == "IP_ADDRESS":
448 NETWORK_SETTINGS['ip']= value.strip()
450 if name == "IP_GATEWAY":
451 NETWORK_SETTINGS['gateway']= value.strip()
453 if name == "IP_NETMASK":
454 NETWORK_SETTINGS['netmask']= value.strip()
456 if name == "IP_NETADDR":
457 NETWORK_SETTINGS['network']= value.strip()
459 if name == "IP_BROADCASTADDR":
460 NETWORK_SETTINGS['broadcast']= value.strip()
462 if name == "IP_DNS1":
463 NETWORK_SETTINGS['dns1']= value.strip()
465 if name == "IP_DNS2":
466 NETWORK_SETTINGS['dns2']= value.strip()
468 if name == "HOST_NAME":
469 NETWORK_SETTINGS['hostname']= string.lower(value)
471 if name == "DOMAIN_NAME":
472 NETWORK_SETTINGS['domainname']= string.lower(value)
474 if name == "NET_DEVICE":
475 NETWORK_SETTINGS['mac']= string.upper(value)
478 except IndexError, e:
479 log.write( "Unable to parse configuration file\n" )
482 # now if we are set to dhcp, clear out any fields
483 # that don't make sense
484 if NETWORK_SETTINGS["method"] == "dhcp":
485 NETWORK_SETTINGS["ip"]= ""
486 NETWORK_SETTINGS["gateway"]= ""
487 NETWORK_SETTINGS["netmask"]= ""
488 NETWORK_SETTINGS["network"]= ""
489 NETWORK_SETTINGS["broadcast"]= ""
490 NETWORK_SETTINGS["dns1"]= ""
491 NETWORK_SETTINGS["dns2"]= ""
493 log.write("Successfully read and parsed node configuration file.\n" )
495 # if the mac wasn't specified, read it in from the system.
496 if NETWORK_SETTINGS["mac"] == "":
498 mac_addr= utils.get_mac_from_interface(device)
501 log.write( "Could not get mac address for device eth0.\n" )
504 NETWORK_SETTINGS["mac"]= string.upper(mac_addr)
506 log.write( "Got mac address %s for device %s\n" %
507 (NETWORK_SETTINGS["mac"],device) )
510 # now, if the conf file didn't contain a node id, post the mac address
511 # to plc to get the node_id value
512 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
513 log.write( "Configuration file does not contain the node_id value.\n" )
514 log.write( "Querying PLC for node_id.\n" )
516 bs_request= BootServerRequest.BootServerRequest()
518 postVars= {"mac_addr" : NETWORK_SETTINGS["mac"]}
519 result= bs_request.DownloadFile( "%s/getnodeid.php" %
521 None, postVars, 1, 1,
524 log.write( "Unable to make request to get node_id.\n" )
528 node_id_file= file("/tmp/node_id","r")
529 node_id= string.strip(node_id_file.read())
532 log.write( "Unable to read node_id from /tmp/node_id\n" )
536 node_id= int(string.strip(node_id))
538 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
542 log.write( "Got node_id, but it returned -1\n\n" )
544 log.write( "------------------------------------------------------\n" )
545 log.write( "This indicates that this node could not be identified\n" )
546 log.write( "by PLC. You will need to add the node to your site,\n" )
547 log.write( "and regenerate the network configuration file.\n" )
548 log.write( "See the Technical Contact guide for node setup\n" )
549 log.write( "procedures.\n\n" )
550 log.write( "Boot process canceled until this is completed.\n" )
551 log.write( "------------------------------------------------------\n" )
553 cancel_boot_flag= "/tmp/CANCEL_BOOT"
554 # this will make the initial script stop requesting scripts from PLC
555 utils.sysexec( "touch %s" % cancel_boot_flag, log )
559 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
560 vars['NODE_ID']= node_id
564 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
565 log.write( "Configuration file does not contain a node_key value.\n" )
566 log.write( "Using boot nonce instead.\n" )
568 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
569 # can be read and used directly. 2.x cds stored in the same place
570 # but in binary form, so we need to convert it to ascii the same
571 # way the old boot scripts did so it matches whats in the db
573 if BOOT_CD_VERSION[0] == 2:
579 nonce_file= file("/tmp/nonce",read_mode)
580 nonce= nonce_file.read()
583 log.write( "Unable to read nonce from /tmp/nonce\n" )
586 if BOOT_CD_VERSION[0] == 2:
587 nonce= nonce.encode('hex')
589 # there is this nice bug in the php that currently accepts the
590 # nonce for the old scripts, in that if the nonce contains
591 # null chars (2.x cds sent as binary), then
592 # the nonce is truncated. so, do the same here, truncate the nonce
593 # at the first null ('00'). This could leave us with an empty string.
594 nonce_len= len(nonce)
595 for byte_index in range(0,nonce_len,2):
596 if nonce[byte_index:byte_index+2] == '00':
597 nonce= nonce[:byte_index]
600 nonce= string.strip(nonce)
602 log.write( "Read nonce, using as key.\n" )
603 vars['NODE_KEY']= nonce
606 # at this point, we've read the network configuration file.
607 # if we were setup using dhcp, get this system's current ip
608 # address and update the vars key ip, because it
609 # is needed for future api calls.
611 # at the same time, we can check to make sure that the hostname
612 # in the configuration file matches the ip address. if it fails
615 hostname= NETWORK_SETTINGS['hostname'] + "." + \
616 NETWORK_SETTINGS['domainname']
618 # set to 0 if any part of the hostname resolution check fails
619 hostname_resolve_ok= 1
621 # set to 0 if the above fails, and, we are using dhcp in which
622 # case we don't know the ip of this machine (without having to
623 # parse ifconfig or something). In that case, we won't be able
624 # to make api calls, so printing a message to the screen will
628 log.write( "Checking that hostname %s resolves\n" % hostname )
630 # try a regular dns lookup first
632 resolved_node_ip= socket.gethostbyname(hostname)
633 except socket.gaierror, e:
634 hostname_resolve_ok= 0
637 if NETWORK_SETTINGS['method'] == "dhcp":
638 if hostname_resolve_ok:
639 NETWORK_SETTINGS['ip']= resolved_node_ip
640 node_ip= resolved_node_ip
644 node_ip= NETWORK_SETTINGS['ip']
646 # make sure the dns lookup matches what the configuration file says
647 if hostname_resolve_ok:
648 if node_ip != resolved_node_ip:
649 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
650 (hostname,node_ip,resolved_node_ip) )
651 hostname_resolve_ok= 0
653 log.write( "Hostname %s correctly resolves to %s:\n" %
657 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
659 if not hostname_resolve_ok:
660 log.write( "Hostname does not resolve correctly, will not continue.\n" )
662 StartDebug.Run( vars, log )
664 if can_make_api_call:
665 log.write( "Notifying contacts of problem.\n" )
667 vars['BOOT_STATE']= 'dbg'
668 vars['STATE_CHANGE_NOTIFY']= 1
669 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
670 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
672 UpdateBootStateWithPLC.Run( vars, log )
675 log.write( "The hostname and/or ip in the network configuration\n" )
676 log.write( "file do not resolve and match.\n" )
677 log.write( "Please make sure the hostname set in the network\n" )
678 log.write( "configuration file resolves to the ip also specified\n" )
679 log.write( "there.\n\n" )
680 log.write( "Debug mode is being started on this cd. When the above\n" )
681 log.write( "is corrected, reboot the machine to try again.\n" )
683 raise BootManagerException, \
684 "Configured node hostname does not resolve."