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 # two possible names of the configuration files
53 NEW_CONF_FILE_NAME= "plnode.txt"
54 OLD_CONF_FILE_NAME= "planet.cnf"
59 read the machines node configuration file, which contains
60 the node key and the node_id for this machine.
62 these files can exist in several different locations with
63 several different names. Below is the search order:
65 filename floppy flash cd
66 plnode.txt 1 2 4 (/usr/boot), 5 (/usr)
69 The locations will be searched in the above order, plnode.txt
70 will be checked first, then planet.cnf. Flash devices will only
71 be searched on 3.0 cds.
73 Because some of the earlier
74 boot cds don't validate the configuration file (which results
75 in a file named /tmp/planet-clean.cnf), and some do, lets
76 bypass this, and mount and attempt to read in the conf
77 file ourselves. If it doesn't exist, we cannot continue, and a
78 BootManagerException will be raised. If the configuration file is found
81 Expect the following variables from the store:
82 BOOT_CD_VERSION A tuple of the current bootcd version
83 ALPINA_SERVER_DIR directory on the boot servers containing alpina
84 scripts and support files
86 Sets the following variables from the configuration file:
87 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
88 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
89 NONE_ID The db node_id for this machine
90 NODE_KEY The key for this node
91 NETWORK_SETTINGS A dictionary of the values from the network
92 configuration file. keys set:
106 log.write( "\n\nStep: Reading node configuration file.\n" )
109 # make sure we have the variables we need
111 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
112 if BOOT_CD_VERSION == "":
113 raise ValueError, "BOOT_CD_VERSION"
115 ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"]
116 if ALPINA_SERVER_DIR == None:
117 raise ValueError, "ALPINA_SERVER_DIR"
119 except KeyError, var:
120 raise BootManagerException, "Missing variable in vars: %s\n" % var
121 except ValueError, var:
122 raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
126 NETWORK_SETTINGS['method']= "dhcp"
127 NETWORK_SETTINGS['ip']= ""
128 NETWORK_SETTINGS['mac']= ""
129 NETWORK_SETTINGS['gateway']= ""
130 NETWORK_SETTINGS['network']= ""
131 NETWORK_SETTINGS['broadcast']= ""
132 NETWORK_SETTINGS['netmask']= ""
133 NETWORK_SETTINGS['dns1']= ""
134 NETWORK_SETTINGS['dns2']= ""
135 NETWORK_SETTINGS['hostname']= "localhost"
136 NETWORK_SETTINGS['domainname']= "localdomain"
137 vars['NETWORK_SETTINGS']= NETWORK_SETTINGS
142 vars['WAS_NODE_ID_IN_CONF']= 0
143 vars['WAS_NODE_KEY_IN_CONF']= 0
145 # for any devices that need to be mounted to get the configuration
146 # file, mount them here.
147 mount_point= "/tmp/conffilemount"
148 utils.makedirs( mount_point )
150 old_conf_file_contents= None
151 conf_file_contents= None
154 # 1. check the regular floppy device
155 log.write( "Checking standard floppy disk for plnode.txt file.\n" )
157 utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \
160 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
161 if os.access( conf_file_path, os.R_OK ):
163 conf_file= file(conf_file_path,"r")
164 conf_file_contents= conf_file.read()
169 utils.sysexec_noerr( "umount /dev/fd0", log )
170 if __parse_configuration_file( vars, log, conf_file_contents):
173 raise BootManagerException( "Found configuration file plnode.txt " \
174 "on floppy, but was unable to parse it." )
177 # try the old file name, same device. its actually number 3 on the search
178 # order, but do it now to save mounting/unmounting the disk twice.
179 # try to parse it later...
180 conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME)
181 if os.access( conf_file_path, os.R_OK ):
183 old_conf_file= file(conf_file_path,"r")
184 old_conf_file_contents= old_conf_file.read()
185 old_conf_file.close()
189 utils.sysexec_noerr( "umount /dev/fd0", log )
193 if BOOT_CD_VERSION[0] == 3:
194 # 2. check flash devices on 3.0 based cds
195 log.write( "Checking flash devices for plnode.txt file.\n" )
197 # this is done the same way the 3.0 cds do it, by attempting
198 # to mount and sd*1 devices that are removable
199 devices= os.listdir("/sys/block/")
201 for device in devices:
202 if device[:2] != "sd":
206 removable_file_path= "/sys/block/%s/removable" % device
208 removable= int(file(removable_file_path,"r").read().strip())
209 except ValueError, e:
217 log.write( "Checking removable device %s\n" % device )
219 # ok, try to mount it and see if we have a conf file.
220 full_device= "/dev/%s1" % device
223 utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \
224 % (full_device,mount_point), log )
225 except BootManagerException, e:
228 conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME)
229 if os.access( conf_file_path, os.R_OK ):
231 conf_file= file(conf_file_path,"r")
232 conf_file_contents= conf_file.read()
237 utils.sysexec_noerr( "umount %s" % full_device, log )
238 if __parse_configuration_file( vars, log, conf_file_contents):
241 raise BootManagerException("Found configuration file plnode.txt " \
242 "on floppy, but was unable to parse it.")
246 # 3. check standard floppy disk for old file name planet.cnf
247 log.write( "Checking standard floppy disk for planet.cnf file.\n" )
249 if old_conf_file_contents:
250 if __parse_configuration_file( vars, log, old_conf_file_contents):
253 raise BootManagerException( "Found configuration file planet.cnf " \
254 "on floppy, but was unable to parse it." )
257 # 4. check for plnode.txt in /usr/boot (mounted already)
258 log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" )
260 conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME
261 if os.access(conf_file_path,os.R_OK):
263 conf_file= file(conf_file_path,"r")
264 conf_file_contents= conf_file.read()
269 if __parse_configuration_file( vars, log, conf_file_contents):
272 raise BootManagerException( "Found configuration file plnode.txt " \
273 "in /usr/boot, but was unable to parse it.")
277 # 5. check for plnode.txt in /usr (mounted already)
278 log.write( "Checking /usr (cd) for plnode.txt file.\n" )
280 conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME
281 if os.access(conf_file_path,os.R_OK):
283 conf_file= file(conf_file_path,"r")
284 conf_file_contents= conf_file.read()
289 if __parse_configuration_file( vars, log, conf_file_contents):
292 raise BootManagerException( "Found configuration file plnode.txt " \
293 "in /usr, but was unable to parse it.")
296 raise BootManagerException, "Unable to find and read a node configuration file."
301 def __parse_configuration_file( vars, log, file_contents ):
303 parse a configuration file, set keys in var NETWORK_SETTINGS
304 in vars (see comment for function ReadNodeConfiguration)
307 BOOT_CD_VERSION= vars["BOOT_CD_VERSION"]
308 ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"]
309 NETWORK_SETTINGS= vars["NETWORK_SETTINGS"]
311 if file_contents is None:
316 for line in file_contents.split("\n"):
318 line_num = line_num + 1
320 # if its a comment or a whitespace line, ignore
321 if line[:1] == "#" or string.strip(line) == "":
324 # file is setup as name="value" pairs
325 parts= string.split(line,"=")
327 log.write( "Invalid line %d in configuration file:\n" % line_num )
328 log.write( line + "\n" )
331 name= string.strip(parts[0])
332 value= string.strip(parts[1])
334 # make sure value starts and ends with
335 # single or double quotes
336 quotes= value[0] + value[len(value)-1]
337 if quotes != "''" and quotes != '""':
338 log.write( "Invalid line %d in configuration file:\n" % line_num )
339 log.write( line + "\n" )
342 # get rid of the quotes around the value
343 value= string.strip(value[1:len(value)-1])
345 if name == "NODE_ID":
347 vars['NODE_ID']= int(value)
348 vars['WAS_NODE_ID_IN_CONF']= 1
349 except ValueError, e:
350 log.write( "Non-numeric node_id in configuration file.\n" )
353 if name == "NODE_KEY":
354 vars['NODE_KEY']= value
355 vars['WAS_NODE_KEY_IN_CONF']= 1
357 if name == "IP_METHOD":
358 value= string.lower(value)
359 if value != "static" and value != "dhcp":
360 log.write( "Invalid IP_METHOD in configuration file:\n" )
361 log.write( line + "\n" )
363 NETWORK_SETTINGS['method']= value.strip()
365 if name == "IP_ADDRESS":
366 NETWORK_SETTINGS['ip']= value.strip()
368 if name == "IP_GATEWAY":
369 NETWORK_SETTINGS['gateway']= value.strip()
371 if name == "IP_NETMASK":
372 NETWORK_SETTINGS['netmask']= value.strip()
374 if name == "IP_NETADDR":
375 NETWORK_SETTINGS['network']= value.strip()
377 if name == "IP_BROADCASTADDR":
378 NETWORK_SETTINGS['broadcast']= value.strip()
380 if name == "IP_DNS1":
381 NETWORK_SETTINGS['dns1']= value.strip()
383 if name == "IP_DNS2":
384 NETWORK_SETTINGS['dns2']= value.strip()
386 if name == "HOST_NAME":
387 NETWORK_SETTINGS['hostname']= string.lower(value)
389 if name == "DOMAIN_NAME":
390 NETWORK_SETTINGS['domainname']= string.lower(value)
392 except IndexError, e:
393 log.write( "Unable to parse configuration file\n" )
396 # now if we are set to dhcp, clear out any fields
397 # that don't make sense
398 if NETWORK_SETTINGS["method"] == "dhcp":
399 NETWORK_SETTINGS["ip"]= ""
400 NETWORK_SETTINGS["gateway"]= ""
401 NETWORK_SETTINGS["netmask"]= ""
402 NETWORK_SETTINGS["network"]= ""
403 NETWORK_SETTINGS["broadcast"]= ""
404 NETWORK_SETTINGS["dns1"]= ""
405 NETWORK_SETTINGS["dns2"]= ""
408 log.write("Successfully read and parsed node configuration file.\n" )
411 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
412 log.write( "Configuration file does not contain the node_id value.\n" )
413 log.write( "Querying PLC for node_id.\n" )
415 bs_request= BootServerRequest.BootServerRequest()
418 ifconfig_file= file("/tmp/ifconfig","r")
419 ifconfig= ifconfig_file.read()
420 ifconfig_file.close()
422 log.write( "Unable to read ifconfig output from /tmp/ifconfig\n" )
425 postVars= {"ifconfig" : ifconfig}
426 result= bs_request.DownloadFile( "%s/getnodeid.php" %
428 None, postVars, 1, 1,
431 log.write( "Unable to make request to get node_id.\n" )
435 node_id_file= file("/tmp/node_id","r")
436 node_id= string.strip(node_id_file.read())
439 log.write( "Unable to read node_id from /tmp/node_id\n" )
443 node_id= int(string.strip(node_id))
445 log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) )
449 log.write( "Got node_id, but it returned -1\n\n" )
451 log.write( "------------------------------------------------------\n" )
452 log.write( "This indicates that this node could not be identified\n" )
453 log.write( "by PLC. You will need to add the node to your site,\n" )
454 log.write( "and regenerate the network configuration file.\n" )
455 log.write( "See the Technical Contact guide for node setup\n" )
456 log.write( "procedures.\n\n" )
457 log.write( "Boot process canceled until this is completed.\n" )
458 log.write( "------------------------------------------------------\n" )
460 cancel_boot_flag= "/tmp/CANCEL_BOOT"
461 # this will make the initial script stop requesting scripts from PLC
462 utils.sysexec( "touch %s" % cancel_boot_flag, log )
466 log.write( "Got node_id from PLC: %s\n" % str(node_id) )
467 vars['NODE_ID']= node_id
471 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
472 log.write( "Configuration file does not contain a node_key value.\n" )
473 log.write( "Using boot nonce instead.\n" )
475 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
476 # can be read and used directly. 2.x cds stored in the same place
477 # but in binary form, so we need to convert it to ascii the same
478 # way the old boot scripts did so it matches whats in the db
480 if BOOT_CD_VERSION[0] == 2:
486 nonce_file= file("/tmp/nonce",read_mode)
487 nonce= nonce_file.read()
490 log.write( "Unable to read nonce from /tmp/nonce\n" )
493 if BOOT_CD_VERSION[0] == 2:
494 nonce= nonce.encode('hex')
496 # there is this nice bug in the php that currently accepts the
497 # nonce for the old scripts, in that if the nonce contains
498 # null chars (2.x cds sent as binary), then
499 # the nonce is truncated. so, do the same here, truncate the nonce
500 # at the first null ('00'). This could leave us with an empty string.
501 nonce_len= len(nonce)
502 for byte_index in range(0,nonce_len,2):
503 if nonce[byte_index:byte_index+2] == '00':
504 nonce= nonce[:byte_index]
507 nonce= string.strip(nonce)
509 log.write( "Read nonce, using as key.\n" )
510 vars['NODE_KEY']= nonce
513 # at this point, we've read the network configuration file.
514 # if we were setup using dhcp, get this system's current ip
515 # address and update the vars key ip, because it
516 # is needed for future api calls.
518 # at the same time, we can check to make sure that the hostname
519 # in the configuration file matches the ip address.
521 hostname= NETWORK_SETTINGS['hostname'] + "." + \
522 NETWORK_SETTINGS['domainname']
524 log.write( "Checking that hostname %s resolves\n" % hostname )
526 resolved_node_ip= socket.gethostbyname(hostname)
527 except socket.gaierror, e:
528 raise BootManagerException, \
529 "Configured node hostname does not resolve."
531 if NETWORK_SETTINGS['method'] == "dhcp":
532 NETWORK_SETTINGS['ip']= resolved_node_ip
533 node_ip= resolved_node_ip
535 node_ip= NETWORK_SETTINGS['ip']
537 if node_ip != resolved_node_ip:
538 log.write( "Hostname %s does not resolve to %s, but %s:\n" % \
539 (hostname,node_ip,resolved_node_ip) )
541 log.write( "Hostname %s resolves to %s:\n" % (hostname,node_ip) )
543 # 3.x cds, with a node_key on the floppy, can update their mac address
544 # at plc, so get it here
545 if BOOT_CD_VERSION[0] == 3 and vars['WAS_NODE_ID_IN_CONF'] == 1:
548 hw_addr_file= file("/sys/class/net/%s/address" % eth_device, "r")
549 hw_addr= hw_addr_file.read().strip().upper()
552 raise BootmanagerException, \
553 "could not get hw address for device %s" % eth_device
555 NETWORK_SETTINGS['mac']= hw_addr
558 vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS