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
48 from Exceptions import *
49 import BootServerRequest
52 import notify_messages
53 import UpdateBootStateWithPLC
56 # two possible names of the configuration files
57 NEW_CONF_FILE_NAME= "plnode.txt"
58 OLD_CONF_FILE_NAME= "planet.cnf"
63 read the machines node configuration file, which contains
64 the node key and the node_id for this machine.
66 these files can exist in several different locations with
67 several different names. Below is the search order:
69 filename floppy flash cd
70 plnode.txt 1 2 4 (/usr/boot), 5 (/usr)
73 The locations will be searched in the above order, plnode.txt
74 will be checked first, then planet.cnf. Flash devices will only
75 be searched on 3.0 cds.
77 Because some of the earlier
78 boot cds don't validate the configuration file (which results
79 in a file named /tmp/planet-clean.cnf), and some do, lets
80 bypass this, and mount and attempt to read in the conf
81 file ourselves. If it doesn't exist, we cannot continue, and a
82 BootManagerException will be raised. If the configuration file is found
85 Expect the following variables from the store:
86 BOOT_CD_VERSION A tuple of the current bootcd version
87 SUPPORT_FILE_DIR directory on the boot servers containing
88 scripts and support files
90 Sets the following variables from the configuration file:
91 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
92 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
93 NONE_ID The db node_id for this machine
94 NODE_KEY The key for this node
95 NETWORK_SETTINGS A dictionary of the values from the network
96 configuration file. keys set:
110 log.write( "\n\nStep: Reading node configuration file.\n" )
113 # make sure we have the variables we need
115 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
116 if BOOT_CD_VERSION == "":
117 raise ValueError, "BOOT_CD_VERSION"
119 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
120 if SUPPORT_FILE_DIR == None:
121 raise ValueError, "SUPPORT_FILE_DIR"
123 except KeyError, var:
124 raise BootManagerException, "Missing variable in vars: %s\n" % var
125 except ValueError, var:
126 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
130 NETWORK_SETTINGS['method']= "dhcp"
131 NETWORK_SETTINGS['ip']= ""
132 NETWORK_SETTINGS['mac']= ""
133 NETWORK_SETTINGS['gateway']= ""
134 NETWORK_SETTINGS['network']= ""
135 NETWORK_SETTINGS['broadcast']= ""
136 NETWORK_SETTINGS['netmask']= ""
137 NETWORK_SETTINGS['dns1']= ""
138 NETWORK_SETTINGS['dns2']= ""
139 NETWORK_SETTINGS['hostname']= "localhost"
140 NETWORK_SETTINGS['domainname']= "localdomain"
141 vars['NETWORK_SETTINGS']= NETWORK_SETTINGS
146 vars['WAS_NODE_ID_IN_CONF']= 0
147 vars['WAS_NODE_KEY_IN_CONF']= 0
149 # for any devices that need to be mounted to get the configuration
150 # file, mount them here.
151 mount_point= "/tmp/conffilemount"
152 utils.makedirs( mount_point )
154 old_conf_file_contents= None
155 conf_file_contents= None
158 # 1. check the regular floppy device
159 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
161 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
164 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
165 if os.access( conf_file_path, os.R_OK ):
167 conf_file= file(conf_file_path,"r")
168 conf_file_contents= conf_file.read()
173 utils.sysexec_noerr( "umount /dev/fd0", log )
174 if __parse_configuration_file( vars, log, conf_file_contents):
177 raise BootManagerException( "Found configuration file plnode.txt " \
178 "on floppy, but was unable to parse it." )
181 # try the old file name, same device. its actually number 3 on the search
182 # order, but do it now to save mounting/unmounting the disk twice.
183 # try to parse it later...
184 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
185 if os.access( conf_file_path, os.R_OK ):
187 old_conf_file= file(conf_file_path,"r")
188 old_conf_file_contents= old_conf_file.read()
189 old_conf_file.close()
193 utils.sysexec_noerr( "umount /dev/fd0", log )
197 if BOOT_CD_VERSION[0] == 3:
198 # 2. check flash devices on 3.0 based cds
199 log.write( "Checking flash devices for plnode.txt file.\n" )
201 # this is done the same way the 3.0 cds do it, by attempting
202 # to mount and sd*1 devices that are removable
203 devices= os.listdir("/sys/block/")
205 for device in devices:
206 if device[:2] != "sd":
210 removable_file_path= "/sys/block/%s/removable" % device
212 removable= int(file(removable_file_path,"r").read().strip())
213 except ValueError, e:
221 log.write( "Checking removable device %s\n" % device )
223 # ok, try to mount it and see if we have a conf file.
224 full_device= "/dev/%s1" % device
227 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
228 % (full_device,mount_point), log )
229 except BootManagerException, e:
232 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
233 if os.access( conf_file_path, os.R_OK ):
235 conf_file= file(conf_file_path,"r")
236 conf_file_contents= conf_file.read()
241 utils.sysexec_noerr( "umount %s" % full_device, log )
242 if __parse_configuration_file( vars, log, conf_file_contents):
245 raise BootManagerException("Found configuration file plnode.txt " \
246 "on floppy, but was unable to parse it.")
250 # 3. check standard floppy disk for old file name planet.cnf
251 log.write( "Checking standard floppy disk for planet.cnf file.\n" )
253 if old_conf_file_contents:
254 if __parse_configuration_file( vars, log, old_conf_file_contents):
257 raise BootManagerException( "Found configuration file planet.cnf " \
258 "on floppy, but was unable to parse it." )
261 # 4. check for plnode.txt in /usr/boot (mounted already)
262 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
264 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
265 if os.access(conf_file_path,os.R_OK):
267 conf_file= file(conf_file_path,"r")
268 conf_file_contents= conf_file.read()
273 if __parse_configuration_file( vars, log, conf_file_contents):
276 raise BootManagerException( "Found configuration file plnode.txt " \
277 "in /usr/boot, but was unable to parse it.")
281 # 5. check for plnode.txt in /usr (mounted already)
282 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
284 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
285 if os.access(conf_file_path,os.R_OK):
287 conf_file= file(conf_file_path,"r")
288 conf_file_contents= conf_file.read()
293 if __parse_configuration_file( vars, log, conf_file_contents):
296 raise BootManagerException( "Found configuration file plnode.txt " \
297 "in /usr, but was unable to parse it.")
300 raise BootManagerException, "Unable to find and read a node configuration file."
305 def __parse_configuration_file( vars, log, file_contents ):
307 parse a configuration file, set keys in var NETWORK_SETTINGS
308 in vars (see comment for function ReadNodeConfiguration)
311 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
312 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
313 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
315 if file_contents is None:
320 for line in file_contents.split("\n"):
322 line_num = line_num + 1
324 # if its a comment or a whitespace line, ignore
325 if line[:1] == "#" or string.strip(line) == "":
328 # file is setup as name="value" pairs
329 parts= string.split(line,"=")
331 log.write( "Invalid line %d in configuration file:\n" % line_num )
332 log.write( line + "\n" )
335 name= string.strip(parts[0])
336 value= string.strip(parts[1])
338 # make sure value starts and ends with
339 # single or double quotes
340 quotes= value[0] + value[len(value)-1]
341 if quotes != "''" and quotes != '""':
342 log.write( "Invalid line %d in configuration file:\n" % line_num )
343 log.write( line + "\n" )
346 # get rid of the quotes around the value
347 value= string.strip(value[1:len(value)-1])
349 if name == "NODE_ID":
351 vars['NODE_ID']= int(value)
352 vars['WAS_NODE_ID_IN_CONF']= 1
353 except ValueError, e:
354 log.write( "Non-numeric node_id in configuration file.\n" )
357 if name == "NODE_KEY":
358 vars['NODE_KEY']= value
359 vars['WAS_NODE_KEY_IN_CONF']= 1
361 if name == "IP_METHOD":
362 value= string.lower(value)
363 if value != "static" and value != "dhcp":
364 log.write( "Invalid IP_METHOD in configuration file:\n" )
365 log.write( line + "\n" )
367 NETWORK_SETTINGS['method']= value.strip()
369 if name == "IP_ADDRESS":
370 NETWORK_SETTINGS['ip']= value.strip()
372 if name == "IP_GATEWAY":
373 NETWORK_SETTINGS['gateway']= value.strip()
375 if name == "IP_NETMASK":
376 NETWORK_SETTINGS['netmask']= value.strip()
378 if name == "IP_NETADDR":
379 NETWORK_SETTINGS['network']= value.strip()
381 if name == "IP_BROADCASTADDR":
382 NETWORK_SETTINGS['broadcast']= value.strip()
384 if name == "IP_DNS1":
385 NETWORK_SETTINGS['dns1']= value.strip()
387 if name == "IP_DNS2":
388 NETWORK_SETTINGS['dns2']= value.strip()
390 if name == "HOST_NAME":
391 NETWORK_SETTINGS['hostname']= string.lower(value)
393 if name == "DOMAIN_NAME":
394 NETWORK_SETTINGS['domainname']= string.lower(value)
396 except IndexError, e:
397 log.write( "Unable to parse configuration file\n" )
400 # now if we are set to dhcp, clear out any fields
401 # that don't make sense
402 if NETWORK_SETTINGS["method"] == "dhcp":
403 NETWORK_SETTINGS["ip"]= ""
404 NETWORK_SETTINGS["gateway"]= ""
405 NETWORK_SETTINGS["netmask"]= ""
406 NETWORK_SETTINGS["network"]= ""
407 NETWORK_SETTINGS["broadcast"]= ""
408 NETWORK_SETTINGS["dns1"]= ""
409 NETWORK_SETTINGS["dns2"]= ""
412 log.write("Successfully read and parsed node configuration file.\n" )
415 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
416 log.write( "Configuration file does not contain the node_id value.\n" )
417 log.write( "Querying PLC for node_id.\n" )
419 bs_request= BootServerRequest.BootServerRequest()
422 ifconfig_file= file("/tmp/ifconfig","r")
423 ifconfig= ifconfig_file.read()
424 ifconfig_file.close()
426 log.write( "Unable to read ifconfig output from /tmp/ifconfig\n" )
429 postVars= {"ifconfig" : ifconfig}
430 result= bs_request.DownloadFile( "%s/getnodeid.php" %
432 None, postVars, 1, 1,
435 log.write( "Unable to make request to get node_id.\n" )
439 node_id_file= file("/tmp/node_id","r")
440 node_id= string.strip(node_id_file.read())
443 log.write( "Unable to read node_id from /tmp/node_id\n" )
447 node_id= int(string.strip(node_id))
449 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
453 log.write( "Got node_id, but it returned -1\n\n" )
455 log.write( "------------------------------------------------------\n" )
456 log.write( "This indicates that this node could not be identified\n" )
457 log.write( "by PLC. You will need to add the node to your site,\n" )
458 log.write( "and regenerate the network configuration file.\n" )
459 log.write( "See the Technical Contact guide for node setup\n" )
460 log.write( "procedures.\n\n" )
461 log.write( "Boot process canceled until this is completed.\n" )
462 log.write( "------------------------------------------------------\n" )
464 cancel_boot_flag= "/tmp/CANCEL_BOOT"
465 # this will make the initial script stop requesting scripts from PLC
466 utils.sysexec( "touch %s" % cancel_boot_flag, log )
470 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
471 vars['NODE_ID']= node_id
475 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
476 log.write( "Configuration file does not contain a node_key value.\n" )
477 log.write( "Using boot nonce instead.\n" )
479 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
480 # can be read and used directly. 2.x cds stored in the same place
481 # but in binary form, so we need to convert it to ascii the same
482 # way the old boot scripts did so it matches whats in the db
484 if BOOT_CD_VERSION[0] == 2:
490 nonce_file= file("/tmp/nonce",read_mode)
491 nonce= nonce_file.read()
494 log.write( "Unable to read nonce from /tmp/nonce\n" )
497 if BOOT_CD_VERSION[0] == 2:
498 nonce= nonce.encode('hex')
500 # there is this nice bug in the php that currently accepts the
501 # nonce for the old scripts, in that if the nonce contains
502 # null chars (2.x cds sent as binary), then
503 # the nonce is truncated. so, do the same here, truncate the nonce
504 # at the first null ('00'). This could leave us with an empty string.
505 nonce_len= len(nonce)
506 for byte_index in range(0,nonce_len,2):
507 if nonce[byte_index:byte_index+2] == '00':
508 nonce= nonce[:byte_index]
511 nonce= string.strip(nonce)
513 log.write( "Read nonce, using as key.\n" )
514 vars['NODE_KEY']= nonce
517 # at this point, we've read the network configuration file.
518 # if we were setup using dhcp, get this system's current ip
519 # address and update the vars key ip, because it
520 # is needed for future api calls.
522 # at the same time, we can check to make sure that the hostname
523 # in the configuration file matches the ip address. if it fails
526 hostname= NETWORK_SETTINGS['hostname'] + "." + \
527 NETWORK_SETTINGS['domainname']
529 # set to 0 if any part of the hostname resolution check fails
530 hostname_resolve_ok= 1
532 # set to 0 if the above fails, and, we are using dhcp in which
533 # case we don't know the ip of this machine (without having to
534 # parse ifconfig or something). In that case, we won't be able
535 # to make api calls, so printing a message to the screen will
539 log.write( "Checking that hostname %s resolves\n" % hostname )
541 # try a regular dns lookup first
543 resolved_node_ip= socket.gethostbyname(hostname)
544 except socket.gaierror, e:
545 hostname_resolve_ok= 0
548 if NETWORK_SETTINGS['method'] == "dhcp":
549 if hostname_resolve_ok:
550 NETWORK_SETTINGS['ip']= resolved_node_ip
551 node_ip= resolved_node_ip
555 node_ip= NETWORK_SETTINGS['ip']
557 # make sure the dns lookup matches what the configuration file says
558 if hostname_resolve_ok:
559 if node_ip != resolved_node_ip:
560 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
561 (hostname,node_ip,resolved_node_ip) )
562 hostname_resolve_ok= 0
564 log.write( "Hostname %s correctly resolves to %s:\n" %
568 # 3.x cds, with a node_key on the floppy, can update their mac address
569 # at plc, so get it here
570 if BOOT_CD_VERSION[0] == 3 and vars['WAS_NODE_ID_IN_CONF'] == 1:
573 hw_addr_file= file("/sys/class/net/%s/address" % eth_device, "r")
574 hw_addr= hw_addr_file.read().strip().upper()
577 raise BootmanagerException, \
578 "could not get hw address for device %s" % eth_device
580 NETWORK_SETTINGS['mac']= hw_addr
583 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
585 if not hostname_resolve_ok:
586 log.write( "Hostname does not resolve correctly, will not continue.\n" )
588 StartDebug.Run( vars, log )
590 if can_make_api_call:
591 log.write( "Notifying contacts of problem.\n" )
593 vars['BOOT_STATE']= 'dbg'
594 vars['STATE_CHANGE_NOTIFY']= 1
595 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
596 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
598 UpdateBootStateWithPLC.Run( vars, log )
601 log.write( "The hostname and/or ip in the network configuration\n" )
602 log.write( "file do not resolve and match.\n" )
603 log.write( "Please make sure the hostname set in the network\n" )
604 log.write( "configuration file resolves to the ip also specified\n" )
605 log.write( "there.\n\n" )
606 log.write( "Debug mode is being started on this cd. When the above\n" )
607 log.write( "is corrected, reboot the machine to try again.\n" )
609 raise BootManagerException, \
610 "Configured node hostname does not resolve."