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:
111 log.write( "\n\nStep: Reading node configuration file.\n" )
114 # make sure we have the variables we need
116 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
117 if BOOT_CD_VERSION == "":
118 raise ValueError, "BOOT_CD_VERSION"
120 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
121 if SUPPORT_FILE_DIR == None:
122 raise ValueError, "SUPPORT_FILE_DIR"
124 except KeyError, var:
125 raise BootManagerException, "Missing variable in vars: %s\n" % var
126 except ValueError, var:
127 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
131 NETWORK_SETTINGS['method']= "dhcp"
132 NETWORK_SETTINGS['ip']= ""
133 NETWORK_SETTINGS['mac']= ""
134 NETWORK_SETTINGS['gateway']= ""
135 NETWORK_SETTINGS['network']= ""
136 NETWORK_SETTINGS['broadcast']= ""
137 NETWORK_SETTINGS['netmask']= ""
138 NETWORK_SETTINGS['dns1']= ""
139 NETWORK_SETTINGS['dns2']= ""
140 NETWORK_SETTINGS['hostname']= "localhost"
141 NETWORK_SETTINGS['domainname']= "localdomain"
142 vars['NETWORK_SETTINGS']= NETWORK_SETTINGS
147 vars['WAS_NODE_ID_IN_CONF']= 0
148 vars['WAS_NODE_KEY_IN_CONF']= 0
150 # for any devices that need to be mounted to get the configuration
151 # file, mount them here.
152 mount_point= "/tmp/conffilemount"
153 utils.makedirs( mount_point )
155 old_conf_file_contents= None
156 conf_file_contents= None
159 # 1. check the regular floppy device
160 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
162 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
165 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
166 if os.access( conf_file_path, os.R_OK ):
168 conf_file= file(conf_file_path,"r")
169 conf_file_contents= conf_file.read()
174 utils.sysexec_noerr( "umount %s" % mount_point, log )
175 if __parse_configuration_file( vars, log, conf_file_contents):
178 raise BootManagerException( "Found configuration file plnode.txt " \
179 "on floppy, but was unable to parse it." )
182 # try the old file name, same device. its actually number 3 on the search
183 # order, but do it now to save mounting/unmounting the disk twice.
184 # try to parse it later...
185 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
186 if os.access( conf_file_path, os.R_OK ):
188 old_conf_file= file(conf_file_path,"r")
189 old_conf_file_contents= old_conf_file.read()
190 old_conf_file.close()
194 utils.sysexec_noerr( "umount %s" % mount_point, log )
198 if BOOT_CD_VERSION[0] == 3:
199 # 2. check flash devices on 3.0 based cds
200 log.write( "Checking flash devices for plnode.txt file.\n" )
202 # this is done the same way the 3.0 cds do it, by attempting
203 # to mount and sd*1 devices that are removable
204 devices= os.listdir("/sys/block/")
206 for device in devices:
207 if device[:2] != "sd":
211 removable_file_path= "/sys/block/%s/removable" % device
213 removable= int(file(removable_file_path,"r").read().strip())
214 except ValueError, e:
222 log.write( "Checking removable device %s\n" % device )
224 partitions= file("/proc/partitions", "r")
225 for line in partitions:
226 if not re.search("%s[0-9]*$" % device, line):
230 # major minor #blocks name
231 parts= string.split(line)
233 # ok, try to mount it and see if we have a conf file.
234 full_device= "/dev/%s" % parts[3]
235 except IndexError, e:
239 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
240 % (full_device,mount_point), log )
241 except BootManagerException, e:
244 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
245 if os.access( conf_file_path, os.R_OK ):
247 conf_file= file(conf_file_path,"r")
248 conf_file_contents= conf_file.read()
253 utils.sysexec_noerr( "umount %s" % mount_point, log )
254 if __parse_configuration_file( vars, log, conf_file_contents):
257 raise BootManagerException("Found configuration file plnode.txt " \
258 "on floppy, but was unable to parse it.")
262 # 3. check standard floppy disk for old file name planet.cnf
263 log.write( "Checking standard floppy disk for planet.cnf file.\n" )
265 if old_conf_file_contents:
266 if __parse_configuration_file( vars, log, old_conf_file_contents):
269 raise BootManagerException( "Found configuration file planet.cnf " \
270 "on floppy, but was unable to parse it." )
273 # 4. check for plnode.txt in /usr/boot (mounted already)
274 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
276 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
277 if os.access(conf_file_path,os.R_OK):
279 conf_file= file(conf_file_path,"r")
280 conf_file_contents= conf_file.read()
285 if __parse_configuration_file( vars, log, conf_file_contents):
288 raise BootManagerException( "Found configuration file plnode.txt " \
289 "in /usr/boot, but was unable to parse it.")
293 # 5. check for plnode.txt in /usr (mounted already)
294 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
296 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
297 if os.access(conf_file_path,os.R_OK):
299 conf_file= file(conf_file_path,"r")
300 conf_file_contents= conf_file.read()
305 if __parse_configuration_file( vars, log, conf_file_contents):
308 raise BootManagerException( "Found configuration file plnode.txt " \
309 "in /usr, but was unable to parse it.")
312 raise BootManagerException, "Unable to find and read a node configuration file."
317 def __parse_configuration_file( vars, log, file_contents ):
319 parse a configuration file, set keys in var NETWORK_SETTINGS
320 in vars (see comment for function ReadNodeConfiguration)
323 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
324 SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"]
325 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
327 if file_contents is None:
332 for line in file_contents.split("\n"):
334 line_num = line_num + 1
336 # if its a comment or a whitespace line, ignore
337 if line[:1] == "#" or string.strip(line) == "":
340 # file is setup as name="value" pairs
341 parts= string.split(line,"=")
343 log.write( "Invalid line %d in configuration file:\n" % line_num )
344 log.write( line + "\n" )
347 name= string.strip(parts[0])
348 value= string.strip(parts[1])
350 # make sure value starts and ends with
351 # single or double quotes
352 quotes= value[0] + value[len(value)-1]
353 if quotes != "''" and quotes != '""':
354 log.write( "Invalid line %d in configuration file:\n" % line_num )
355 log.write( line + "\n" )
358 # get rid of the quotes around the value
359 value= string.strip(value[1:len(value)-1])
361 if name == "NODE_ID":
363 vars['NODE_ID']= int(value)
364 vars['WAS_NODE_ID_IN_CONF']= 1
365 except ValueError, e:
366 log.write( "Non-numeric node_id in configuration file.\n" )
369 if name == "NODE_KEY":
370 vars['NODE_KEY']= value
371 vars['WAS_NODE_KEY_IN_CONF']= 1
373 if name == "IP_METHOD":
374 value= string.lower(value)
375 if value != "static" and value != "dhcp":
376 log.write( "Invalid IP_METHOD in configuration file:\n" )
377 log.write( line + "\n" )
379 NETWORK_SETTINGS['method']= value.strip()
381 if name == "IP_ADDRESS":
382 NETWORK_SETTINGS['ip']= value.strip()
384 if name == "IP_GATEWAY":
385 NETWORK_SETTINGS['gateway']= value.strip()
387 if name == "IP_NETMASK":
388 NETWORK_SETTINGS['netmask']= value.strip()
390 if name == "IP_NETADDR":
391 NETWORK_SETTINGS['network']= value.strip()
393 if name == "IP_BROADCASTADDR":
394 NETWORK_SETTINGS['broadcast']= value.strip()
396 if name == "IP_DNS1":
397 NETWORK_SETTINGS['dns1']= value.strip()
399 if name == "IP_DNS2":
400 NETWORK_SETTINGS['dns2']= value.strip()
402 if name == "HOST_NAME":
403 NETWORK_SETTINGS['hostname']= string.lower(value)
405 if name == "DOMAIN_NAME":
406 NETWORK_SETTINGS['domainname']= string.lower(value)
408 except IndexError, e:
409 log.write( "Unable to parse configuration file\n" )
412 # now if we are set to dhcp, clear out any fields
413 # that don't make sense
414 if NETWORK_SETTINGS["method"] == "dhcp":
415 NETWORK_SETTINGS["ip"]= ""
416 NETWORK_SETTINGS["gateway"]= ""
417 NETWORK_SETTINGS["netmask"]= ""
418 NETWORK_SETTINGS["network"]= ""
419 NETWORK_SETTINGS["broadcast"]= ""
420 NETWORK_SETTINGS["dns1"]= ""
421 NETWORK_SETTINGS["dns2"]= ""
424 log.write("Successfully read and parsed node configuration file.\n" )
427 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
428 log.write( "Configuration file does not contain the node_id value.\n" )
429 log.write( "Querying PLC for node_id.\n" )
431 bs_request= BootServerRequest.BootServerRequest()
434 ifconfig_file= file("/tmp/ifconfig","r")
435 ifconfig= ifconfig_file.read()
436 ifconfig_file.close()
438 log.write( "Unable to read ifconfig output from /tmp/ifconfig\n" )
441 postVars= {"ifconfig" : ifconfig}
442 result= bs_request.DownloadFile( "%s/getnodeid.php" %
444 None, postVars, 1, 1,
447 log.write( "Unable to make request to get node_id.\n" )
451 node_id_file= file("/tmp/node_id","r")
452 node_id= string.strip(node_id_file.read())
455 log.write( "Unable to read node_id from /tmp/node_id\n" )
459 node_id= int(string.strip(node_id))
461 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
465 log.write( "Got node_id, but it returned -1\n\n" )
467 log.write( "------------------------------------------------------\n" )
468 log.write( "This indicates that this node could not be identified\n" )
469 log.write( "by PLC. You will need to add the node to your site,\n" )
470 log.write( "and regenerate the network configuration file.\n" )
471 log.write( "See the Technical Contact guide for node setup\n" )
472 log.write( "procedures.\n\n" )
473 log.write( "Boot process canceled until this is completed.\n" )
474 log.write( "------------------------------------------------------\n" )
476 cancel_boot_flag= "/tmp/CANCEL_BOOT"
477 # this will make the initial script stop requesting scripts from PLC
478 utils.sysexec( "touch %s" % cancel_boot_flag, log )
482 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
483 vars['NODE_ID']= node_id
487 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
488 log.write( "Configuration file does not contain a node_key value.\n" )
489 log.write( "Using boot nonce instead.\n" )
491 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
492 # can be read and used directly. 2.x cds stored in the same place
493 # but in binary form, so we need to convert it to ascii the same
494 # way the old boot scripts did so it matches whats in the db
496 if BOOT_CD_VERSION[0] == 2:
502 nonce_file= file("/tmp/nonce",read_mode)
503 nonce= nonce_file.read()
506 log.write( "Unable to read nonce from /tmp/nonce\n" )
509 if BOOT_CD_VERSION[0] == 2:
510 nonce= nonce.encode('hex')
512 # there is this nice bug in the php that currently accepts the
513 # nonce for the old scripts, in that if the nonce contains
514 # null chars (2.x cds sent as binary), then
515 # the nonce is truncated. so, do the same here, truncate the nonce
516 # at the first null ('00'). This could leave us with an empty string.
517 nonce_len= len(nonce)
518 for byte_index in range(0,nonce_len,2):
519 if nonce[byte_index:byte_index+2] == '00':
520 nonce= nonce[:byte_index]
523 nonce= string.strip(nonce)
525 log.write( "Read nonce, using as key.\n" )
526 vars['NODE_KEY']= nonce
529 # at this point, we've read the network configuration file.
530 # if we were setup using dhcp, get this system's current ip
531 # address and update the vars key ip, because it
532 # is needed for future api calls.
534 # at the same time, we can check to make sure that the hostname
535 # in the configuration file matches the ip address. if it fails
538 hostname= NETWORK_SETTINGS['hostname'] + "." + \
539 NETWORK_SETTINGS['domainname']
541 # set to 0 if any part of the hostname resolution check fails
542 hostname_resolve_ok= 1
544 # set to 0 if the above fails, and, we are using dhcp in which
545 # case we don't know the ip of this machine (without having to
546 # parse ifconfig or something). In that case, we won't be able
547 # to make api calls, so printing a message to the screen will
551 log.write( "Checking that hostname %s resolves\n" % hostname )
553 # try a regular dns lookup first
555 resolved_node_ip= socket.gethostbyname(hostname)
556 except socket.gaierror, e:
557 hostname_resolve_ok= 0
560 if NETWORK_SETTINGS['method'] == "dhcp":
561 if hostname_resolve_ok:
562 NETWORK_SETTINGS['ip']= resolved_node_ip
563 node_ip= resolved_node_ip
567 node_ip= NETWORK_SETTINGS['ip']
569 # make sure the dns lookup matches what the configuration file says
570 if hostname_resolve_ok:
571 if node_ip != resolved_node_ip:
572 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
573 (hostname,node_ip,resolved_node_ip) )
574 hostname_resolve_ok= 0
576 log.write( "Hostname %s correctly resolves to %s:\n" %
580 # 3.x cds, with a node_key on the floppy, can update their mac address
581 # at plc, so get it here
582 if BOOT_CD_VERSION[0] == 3 and vars['WAS_NODE_ID_IN_CONF'] == 1:
585 hw_addr_file= file("/sys/class/net/%s/address" % eth_device, "r")
586 hw_addr= hw_addr_file.read().strip().upper()
589 raise BootmanagerException, \
590 "could not get hw address for device %s" % eth_device
592 NETWORK_SETTINGS['mac']= hw_addr
595 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS
597 if not hostname_resolve_ok:
598 log.write( "Hostname does not resolve correctly, will not continue.\n" )
600 StartDebug.Run( vars, log )
602 if can_make_api_call:
603 log.write( "Notifying contacts of problem.\n" )
605 vars['BOOT_STATE']= 'dbg'
606 vars['STATE_CHANGE_NOTIFY']= 1
607 vars['STATE_CHANGE_NOTIFY_MESSAGE']= \
608 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
610 UpdateBootStateWithPLC.Run( vars, log )
613 log.write( "The hostname and/or ip in the network configuration\n" )
614 log.write( "file do not resolve and match.\n" )
615 log.write( "Please make sure the hostname set in the network\n" )
616 log.write( "configuration file resolves to the ip also specified\n" )
617 log.write( "there.\n\n" )
618 log.write( "Debug mode is being started on this cd. When the above\n" )
619 log.write( "is corrected, reboot the machine to try again.\n" )
621 raise BootManagerException, \
622 "Configured node hostname does not resolve."