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 cd
71 plnode.txt 1 2 4 (/usr/boot), 5 (/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 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
168 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
169 if os.access( conf_file_path, os.R_OK ):
171 conf_file= file(conf_file_path,"r")
172 conf_file_contents= conf_file.read()
177 utils.sysexec_noerr( "umount %s" % mount_point, log )
178 if __parse_configuration_file( vars, log, conf_file_contents):
181 raise BootManagerException( "Found configuration file plnode.txt " \
182 "on floppy, but was unable to parse it." )
185 # try the old file name, same device. its actually number 3 on the search
186 # order, but do it now to save mounting/unmounting the disk twice.
187 # try to parse it later...
188 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
189 if os.access( conf_file_path, os.R_OK ):
191 old_conf_file= file(conf_file_path,"r")
192 old_conf_file_contents= old_conf_file.read()
193 old_conf_file.close()
197 utils.sysexec_noerr( "umount %s" % mount_point, log )
201 if BOOT_CD_VERSION[0] == 3:
202 # 2. check flash devices on 3.0 based cds
203 log.write( "Checking flash devices for plnode.txt file.\n" )
205 # this is done the same way the 3.0 cds do it, by attempting
206 # to mount and sd*1 devices that are removable
207 devices= os.listdir("/sys/block/")
209 for device in devices:
210 if device[:2] != "sd":
214 removable_file_path= "/sys/block/%s/removable" % device
216 removable= int(file(removable_file_path,"r").read().strip())
217 except ValueError, e:
225 log.write( "Checking removable device %s\n" % device )
227 partitions= file("/proc/partitions", "r")
228 for line in partitions:
229 if not re.search("%s[0-9]*$" % device, line):
233 # major minor #blocks name
234 parts= string.split(line)
236 # ok, try to mount it and see if we have a conf file.
237 full_device= "/dev/%s" % parts[3]
238 except IndexError, e:
242 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
243 % (full_device,mount_point), log )
244 except BootManagerException, e:
247 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
248 if os.access( conf_file_path, os.R_OK ):
250 conf_file= file(conf_file_path,"r")
251 conf_file_contents= conf_file.read()
256 utils.sysexec_noerr( "umount %s" % mount_point, log )
257 if __parse_configuration_file( vars, log, conf_file_contents):
260 raise BootManagerException("Found configuration file plnode.txt " \
261 "on floppy, but was unable to parse it.")
265 # 3. check standard floppy disk for old file name planet.cnf
266 log.write( "Checking standard floppy disk for planet.cnf file.\n" )
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 /usr/boot (mounted already)
277 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
279 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
280 if os.access(conf_file_path,os.R_OK):
282 conf_file= file(conf_file_path,"r")
283 conf_file_contents= conf_file.read()
288 if __parse_configuration_file( vars, log, conf_file_contents):
291 raise BootManagerException( "Found configuration file plnode.txt " \
292 "in /usr/boot, but was unable to parse it.")
296 # 5. check for plnode.txt in /usr (mounted already)
297 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
299 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
300 if os.access(conf_file_path,os.R_OK):
302 conf_file= file(conf_file_path,"r")
303 conf_file_contents= conf_file.read()
308 if __parse_configuration_file( vars, log, conf_file_contents):
311 raise BootManagerException( "Found configuration file plnode.txt " \
312 "in /usr, but was unable to parse it.")
315 raise BootManagerException, "Unable to find and read a node configuration file."
320 def __parse_configuration_file( vars, log, file_contents ):
322 parse a configuration file, set keys in var NETWORK_SETTINGS
323 in vars (see comment for function ReadNodeConfiguration). this
324 also reads the mac address from the machine if successful parsing
325 of the configuration file is completed.
328 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
329 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
330 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
332 if file_contents is None:
337 for line in file_contents.split("\n"):
339 line_num = line_num + 1
341 # if its a comment or a whitespace line, ignore
342 if line[:1] == "#" or string.strip(line) == "":
345 # file is setup as name="value" pairs
346 parts= string.split(line,"=")
348 log.write( "Invalid line %d in configuration file:\n" % line_num )
349 log.write( line + "\n" )
352 name= string.strip(parts[0])
353 value= string.strip(parts[1])
355 # make sure value starts and ends with
356 # single or double quotes
357 quotes= value[0] + value[len(value)-1]
358 if quotes != "''" and quotes != '""':
359 log.write( "Invalid line %d in configuration file:\n" % line_num )
360 log.write( line + "\n" )
363 # get rid of the quotes around the value
364 value= string.strip(value[1:len(value)-1])
366 if name == "NODE_ID":
368 vars['NODE_ID']= int(value)
369 vars['WAS_NODE_ID_IN_CONF']= 1
370 except ValueError, e:
371 log.write( "Non-numeric node_id in configuration file.\n" )
374 if name == "NODE_KEY":
375 vars['NODE_KEY']= value
376 vars['WAS_NODE_KEY_IN_CONF']= 1
378 if name == "IP_METHOD":
379 value= string.lower(value)
380 if value != "static" and value != "dhcp":
381 log.write( "Invalid IP_METHOD in configuration file:\n" )
382 log.write( line + "\n" )
384 NETWORK_SETTINGS['method']= value.strip()
386 if name == "IP_ADDRESS":
387 NETWORK_SETTINGS['ip']= value.strip()
389 if name == "IP_GATEWAY":
390 NETWORK_SETTINGS['gateway']= value.strip()
392 if name == "IP_NETMASK":
393 NETWORK_SETTINGS['netmask']= value.strip()
395 if name == "IP_NETADDR":
396 NETWORK_SETTINGS['network']= value.strip()
398 if name == "IP_BROADCASTADDR":
399 NETWORK_SETTINGS['broadcast']= value.strip()
401 if name == "IP_DNS1":
402 NETWORK_SETTINGS['dns1']= value.strip()
404 if name == "IP_DNS2":
405 NETWORK_SETTINGS['dns2']= value.strip()
407 if name == "HOST_NAME":
408 NETWORK_SETTINGS['hostname']= string.lower(value)
410 if name == "DOMAIN_NAME":
411 NETWORK_SETTINGS['domainname']= string.lower(value)
413 if name == "NET_DEVICE":
414 NETWORK_SETTINGS['mac']= string.upper(value)
417 except IndexError, e:
418 log.write( "Unable to parse configuration file\n" )
421 # now if we are set to dhcp, clear out any fields
422 # that don't make sense
423 if NETWORK_SETTINGS["method"] == "dhcp":
424 NETWORK_SETTINGS["ip"]= ""
425 NETWORK_SETTINGS["gateway"]= ""
426 NETWORK_SETTINGS["netmask"]= ""
427 NETWORK_SETTINGS["network"]= ""
428 NETWORK_SETTINGS["broadcast"]= ""
429 NETWORK_SETTINGS["dns1"]= ""
430 NETWORK_SETTINGS["dns2"]= ""
432 log.write("Successfully read and parsed node configuration file.\n" )
434 # if the mac wasn't specified, read it in from the system.
435 if NETWORK_SETTINGS["mac"] == "":
437 mac_addr= utils.get_mac_from_interface(device)
440 log.write( "Could not get mac address for device eth0.\n" )
443 NETWORK_SETTINGS["mac"]= string.upper(mac_addr)
445 log.write( "Got mac address %s for device %s\n" %
446 (NETWORK_SETTINGS["mac"],device) )
449 # now, if the conf file didn't contain a node id, post the mac address
450 # to plc to get the node_id value
451 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
452 log.write( "Configuration file does not contain the node_id value.\n" )
453 log.write( "Querying PLC for node_id.\n" )
455 bs_request= BootServerRequest.BootServerRequest()
457 postVars= {"mac_addr" : NETWORK_SETTINGS["mac"]}
458 result= bs_request.DownloadFile( "%s/getnodeid.php" %
460 None, postVars, 1, 1,
463 log.write( "Unable to make request to get node_id.\n" )
467 node_id_file= file("/tmp/node_id","r")
468 node_id= string.strip(node_id_file.read())
471 log.write( "Unable to read node_id from /tmp/node_id\n" )
475 node_id= int(string.strip(node_id))
477 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
481 log.write( "Got node_id, but it returned -1\n\n" )
483 log.write( "------------------------------------------------------\n" )
484 log.write( "This indicates that this node could not be identified\n" )
485 log.write( "by PLC. You will need to add the node to your site,\n" )
486 log.write( "and regenerate the network configuration file.\n" )
487 log.write( "See the Technical Contact guide for node setup\n" )
488 log.write( "procedures.\n\n" )
489 log.write( "Boot process canceled until this is completed.\n" )
490 log.write( "------------------------------------------------------\n" )
492 cancel_boot_flag= "/tmp/CANCEL_BOOT"
493 # this will make the initial script stop requesting scripts from PLC
494 utils.sysexec( "touch %s" % cancel_boot_flag, log )
498 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
499 vars['NODE_ID']= node_id
503 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
504 log.write( "Configuration file does not contain a node_key value.\n" )
505 log.write( "Using boot nonce instead.\n" )
507 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
508 # can be read and used directly. 2.x cds stored in the same place
509 # but in binary form, so we need to convert it to ascii the same
510 # way the old boot scripts did so it matches whats in the db
512 if BOOT_CD_VERSION[0] == 2:
518 nonce_file= file("/tmp/nonce",read_mode)
519 nonce= nonce_file.read()
522 log.write( "Unable to read nonce from /tmp/nonce\n" )
525 if BOOT_CD_VERSION[0] == 2:
526 nonce= nonce.encode('hex')
528 # there is this nice bug in the php that currently accepts the
529 # nonce for the old scripts, in that if the nonce contains
530 # null chars (2.x cds sent as binary), then
531 # the nonce is truncated. so, do the same here, truncate the nonce
532 # at the first null ('00'). This could leave us with an empty string.
533 nonce_len= len(nonce)
534 for byte_index in range(0,nonce_len,2):
535 if nonce[byte_index:byte_index+2] == '00':
536 nonce= nonce[:byte_index]
539 nonce= string.strip(nonce)
541 log.write( "Read nonce, using as key.\n" )
542 vars['NODE_KEY']= nonce
545 # at this point, we've read the network configuration file.
546 # if we were setup using dhcp, get this system's current ip
547 # address and update the vars key ip, because it
548 # is needed for future api calls.
550 # at the same time, we can check to make sure that the hostname
551 # in the configuration file matches the ip address. if it fails
554 hostname= NETWORK_SETTINGS['hostname'] + "." + \
555 NETWORK_SETTINGS['domainname']
557 # set to 0 if any part of the hostname resolution check fails
558 hostname_resolve_ok= 1
560 # set to 0 if the above fails, and, we are using dhcp in which
561 # case we don't know the ip of this machine (without having to
562 # parse ifconfig or something). In that case, we won't be able
563 # to make api calls, so printing a message to the screen will
567 log.write( "Checking that hostname %s resolves\n" % hostname )
569 # try a regular dns lookup first
571 resolved_node_ip= socket.gethostbyname(hostname)
572 except socket.gaierror, e:
573 hostname_resolve_ok= 0
576 if NETWORK_SETTINGS['method'] == "dhcp":
577 if hostname_resolve_ok:
578 NETWORK_SETTINGS['ip']= resolved_node_ip
579 node_ip= resolved_node_ip
583 node_ip= NETWORK_SETTINGS['ip']
585 # make sure the dns lookup matches what the configuration file says
586 if hostname_resolve_ok:
587 if node_ip != resolved_node_ip:
588 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
589 (hostname,node_ip,resolved_node_ip) )
590 hostname_resolve_ok= 0
592 log.write( "Hostname %s correctly resolves to %s:\n" %
596 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
598 if not hostname_resolve_ok:
599 log.write( "Hostname does not resolve correctly, will not continue.\n" )
601 StartDebug.Run( vars, log )
603 if can_make_api_call:
604 log.write( "Notifying contacts of problem.\n" )
606 vars['BOOT_STATE']= 'dbg'
607 vars['STATE_CHANGE_NOTIFY']= 1
608 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
609 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
611 UpdateBootStateWithPLC.Run( vars, log )
614 log.write( "The hostname and/or ip in the network configuration\n" )
615 log.write( "file do not resolve and match.\n" )
616 log.write( "Please make sure the hostname set in the network\n" )
617 log.write( "configuration file resolves to the ip also specified\n" )
618 log.write( "there.\n\n" )
619 log.write( "Debug mode is being started on this cd. When the above\n" )
620 log.write( "is corrected, reboot the machine to try again.\n" )
622 raise BootManagerException, \
623 "Configured node hostname does not resolve."