3 # Copyright (c) 2003 Intel Corporation
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
8 # expected /proc/partitions format
10 import sys, os, traceback
16 from Exceptions import *
17 import BootServerRequest
19 import notify_messages
20 import UpdateRunLevelWithPLC
23 # two possible names of the configuration files
24 NEW_CONF_FILE_NAME = "plnode.txt"
25 OLD_CONF_FILE_NAME = "planet.cnf"
30 read the machines node configuration file, which contains
31 the node key and the node_id for this machine.
33 these files can exist in several different locations with
34 several different names. Below is the search order:
36 filename floppy flash ramdisk cd
37 plnode.txt 1 2 4 (/) 5 (/usr/boot), 6 (/usr)
40 The locations will be searched in the above order, plnode.txt
41 will be checked first, then planet.cnf. Flash devices will only
42 be searched on 3.0 cds.
44 Because some of the earlier
45 boot cds don't validate the configuration file (which results
46 in a file named /tmp/planet-clean.cnf), and some do, lets
47 bypass this, and mount and attempt to read in the conf
48 file ourselves. If it doesn't exist, we cannot continue, and a
49 BootManagerException will be raised. If the configuration file is found
52 Expect the following variables from the store:
54 Sets the following variables from the configuration file:
55 WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file
56 WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file
57 NONE_ID The db node_id for this machine
58 NODE_KEY The key for this node
59 INTERFACE_SETTINGS A dictionary of the values from the network
60 configuration file. keys set:
66 broadcast IP_BROADCASTADDR
71 domainname DOMAIN_NAME
74 iwconfig WLAN_IWCONFIG
76 the mac address is read from the machine unless it exists in the
80 log.write("\n\nStep: Reading node configuration file.\n")
83 # make sure we have the variables we need
85 INTERFACE_SETTINGS = {}
86 INTERFACE_SETTINGS['method'] = "dhcp"
87 INTERFACE_SETTINGS['ip'] = ""
88 INTERFACE_SETTINGS['mac'] = ""
89 INTERFACE_SETTINGS['gateway'] = ""
90 INTERFACE_SETTINGS['network'] = ""
91 INTERFACE_SETTINGS['broadcast'] = ""
92 INTERFACE_SETTINGS['netmask'] = ""
93 INTERFACE_SETTINGS['dns1'] = ""
94 INTERFACE_SETTINGS['dns2'] = ""
95 INTERFACE_SETTINGS['hostname'] = "localhost"
96 INTERFACE_SETTINGS['domainname'] = "localdomain"
97 vars['INTERFACE_SETTINGS'] = INTERFACE_SETTINGS
100 vars['NODE_KEY'] = ""
102 vars['WAS_NODE_ID_IN_CONF'] = 0
103 vars['WAS_NODE_KEY_IN_CONF'] = 0
105 vars['DISCONNECTED_OPERATION'] = ''
107 # for any devices that need to be mounted to get the configuration
108 # file, mount them here.
109 mount_point = "/tmp/conffilemount"
110 utils.makedirs(mount_point)
112 old_conf_file_contents = None
113 conf_file_contents = None
116 # 1. check the regular floppy device
117 log.write("Checking standard floppy disk for plnode.txt file.\n")
119 log.write("Mounting /dev/fd0 on {}\n".format(mount_point))
120 utils.sysexec_noerr("mount -o ro -t ext2,msdos /dev/fd0 {} "
121 .format(mount_point), log)
123 conf_file_path = "{}/{}".format(mount_point, NEW_CONF_FILE_NAME)
125 # log.write("Checking for existence of {}\n".format(conf_file_path))
126 if os.access(conf_file_path, os.R_OK):
128 conf_file = file(conf_file_path,"r")
129 conf_file_contents = conf_file.read()
131 log.write("Read in contents of file {}\n".format(conf_file_path))
133 log.write("Unable to read file {}\n".format(conf_file_path))
136 utils.sysexec_noerr("umount {}".format(mount_point), log)
137 if __parse_configuration_file(vars, log, conf_file_contents):
138 log.write("ReadNodeConfiguration: [1] using {} from floppy /dev/fd0\n"
139 .format(NEW_CONF_FILE_NAME))
142 raise BootManagerException("Found configuration file plnode.txt "
143 "on floppy, but was unable to parse it.")
146 # try the old file name, same device. its actually number 3 on the search
147 # order, but do it now to save mounting/unmounting the disk twice.
148 # try to parse it later...
149 conf_file_path = "{}/{}".format(mount_point, OLD_CONF_FILE_NAME)
151 # this message really does not convey any useful information
152 # log.write("Checking for existence of %s (used later)\n" % conf_file_path)
153 if os.access(conf_file_path, os.R_OK):
155 old_conf_file = file(conf_file_path, "r")
156 old_conf_file_contents = old_conf_file.read()
157 old_conf_file.close()
158 log.write("Read in contents of file {}\n".format(conf_file_path))
160 log.write("Unable to read file {}\n".format(conf_file_path))
163 utils.sysexec_noerr("umount {}".format(mount_point), log)
165 # 2. check flash devices on 3.0 based cds
166 log.write("Checking flash devices for plnode.txt file.\n")
168 # this is done the same way the 3.0 cds do it, by attempting
169 # to mount and sd*1 devices that are removable
170 devices = os.listdir("/sys/block/")
172 for device in devices:
173 if device[:2] != "sd":
174 log.write("Skipping non-scsi device {}\n".format(device))
178 removable_file_path = "/sys/block/{}/removable".format(device)
180 removable = int(file(removable_file_path,"r").read().strip())
181 except ValueError as e:
187 log.write("Skipping non-removable device {}\n".format(device))
190 log.write("Checking removable device {}\n".format(device))
192 partitions = file("/proc/partitions", "r")
193 for line in partitions:
197 if not re.search("{}[0-9]*$".format(device), line):
201 # major minor #blocks name
202 parts = string.split(line)
204 # ok, try to mount it and see if we have a conf file.
205 full_device = "/dev/{}".format(parts[3])
206 except IndexError as e:
207 log.write("Incorrect /proc/partitions line:\n{}\n".format(line))
210 log.write("Mounting {} on {}\n".format(full_device, mount_point))
212 utils.sysexec("mount -o ro -t ext2,msdos {} {}"
213 .format(full_device, mount_point), log)
214 except BootManagerException as e:
215 log.write("Unable to mount, trying next partition\n")
218 conf_file_path = "{}/{}".format(mount_point, NEW_CONF_FILE_NAME)
220 log.write("Checking for existence of {}\n".format(conf_file_path))
221 if os.access(conf_file_path, os.R_OK):
223 conf_file = file(conf_file_path,"r")
224 conf_file_contents = conf_file.read()
227 log.write("Read in contents of file {}\n"
228 .format(conf_file_path))
230 if __parse_configuration_file(vars, log, conf_file_contents):
233 log.write("Unable to read file {}\n".format(conf_file_path))
235 utils.sysexec_noerr("umount {}".format(mount_point), log)
238 log.write("ReadNodeConfiguration: [2] using {} from partition {}\n"
239 .format(NEW_CONF_FILE_NAME, full_device))
242 raise BootManagerException("Found configuration file on {}, "
243 "but was unable to parse it.".format(full_device))
247 # 3. check standard floppy disk for old file name planet.cnf
248 log.write("Checking standard floppy disk for planet.cnf file (for legacy nodes).\n")
250 if old_conf_file_contents:
251 if __parse_configuration_file(vars, log, old_conf_file_contents):
252 log.write("ReadNodeConfiguration: [3] using {} from floppy /dev/fd0\n"
253 .format(OLD_CONF_FILE_NAME))
256 raise BootManagerException("Found configuration file planet.cnf "
257 "on floppy, but was unable to parse it.")
260 # 4. check for plnode.txt in / (ramdisk)
261 log.write("Checking / (ramdisk) for plnode.txt file.\n")
263 conf_file_path = "/{}".format(NEW_CONF_FILE_NAME)
265 log.write("Checking for existence of {}\n".format(conf_file_path))
266 if os.access(conf_file_path,os.R_OK):
268 conf_file = file(conf_file_path,"r")
269 conf_file_contents = conf_file.read()
271 log.write("Read in contents of file {}\n".format(conf_file_path))
273 log.write("Unable to read file {}\n".format(conf_file_path))
276 if __parse_configuration_file(vars, log, conf_file_contents):
277 log.write("ReadNodeConfiguration: [4] using {} from ramdisk\n"
278 .format(NEW_CONF_FILE_NAME))
281 raise BootManagerException("Found configuration file plnode.txt "
282 "in /, but was unable to parse it.")
285 # 5. check for plnode.txt in /usr/boot (mounted already)
286 log.write("Checking /usr/boot (cd) for plnode.txt file.\n")
288 conf_file_path = "/usr/boot/{}".format(NEW_CONF_FILE_NAME)
290 log.write("Checking for existence of {}\n".format(conf_file_path))
291 if os.access(conf_file_path,os.R_OK):
293 conf_file = file(conf_file_path,"r")
294 conf_file_contents = conf_file.read()
296 log.write("Read in contents of file {}\n".format(conf_file_path))
298 log.write("Unable to read file {}\n".format(conf_file_path))
301 if __parse_configuration_file(vars, log, conf_file_contents):
302 log.write("ReadNodeConfiguration: [5] using {} from CD in /usr/boot\n"
303 .format(NEW_CONF_FILE_NAME))
306 raise BootManagerException("Found configuration file plnode.txt "
307 "in /usr/boot, but was unable to parse it.")
311 # 6. check for plnode.txt in /usr (mounted already)
312 log.write("Checking /usr (cd) for plnode.txt file.\n")
314 conf_file_path = "/usr/{}".format(NEW_CONF_FILE_NAME)
316 log.write("Checking for existence of {}\n".format(conf_file_path))
317 if os.access(conf_file_path,os.R_OK):
319 conf_file = file(conf_file_path,"r")
320 conf_file_contents = conf_file.read()
322 log.write("Read in contents of file {}\n".format(conf_file_path))
324 log.write("Unable to read file {}\n".format(conf_file_path))
327 if __parse_configuration_file(vars, log, conf_file_contents):
328 log.write("ReadNodeConfiguration: [6] using {} from /usr\n"
329 .format(NEW_CONF_FILE_NAME))
332 raise BootManagerException("Found configuration file plnode.txt "
333 "in /usr, but was unable to parse it.")
336 raise BootManagerException("Unable to find and read a node configuration file.")
341 def __parse_configuration_file(vars, log, file_contents):
343 parse a configuration file, set keys in var INTERFACE_SETTINGS
344 in vars (see comment for function ReadNodeConfiguration). this
345 also reads the mac address from the machine if successful parsing
346 of the configuration file is completed.
349 INTERFACE_SETTINGS = vars["INTERFACE_SETTINGS"]
351 if file_contents is None:
352 log.write("__parse_configuration_file called with no file contents\n")
357 for line in file_contents.split("\n"):
359 line_num = line_num + 1
361 # if its a comment or a whitespace line, ignore
362 if line[:1] == "#" or string.strip(line) == "":
365 # file is setup as name="value" pairs
366 parts = string.split(line, "=", 1)
368 name = string.strip(parts[0])
369 value = string.strip(parts[1])
371 # make sure value starts and ends with
372 # single or double quotes
373 quotes = value[0] + value[len(value)-1]
374 if quotes != "''" and quotes != '""':
375 log.write("Invalid line {} in configuration file:\n".format(line_num))
376 log.write(line + "\n")
379 # get rid of the quotes around the value
380 value = string.strip(value[1:len(value)-1])
382 if name == "NODE_ID":
384 vars['NODE_ID'] = int(value)
385 vars['WAS_NODE_ID_IN_CONF'] = 1
386 except ValueError as e:
387 log.write("Non-numeric node_id in configuration file.\n")
390 if name == "NODE_KEY":
391 vars['NODE_KEY'] = value
392 vars['WAS_NODE_KEY_IN_CONF'] = 1
394 if name == "IP_METHOD":
395 value = string.lower(value)
396 if value != "static" and value != "dhcp":
397 log.write("Invalid IP_METHOD in configuration file:\n")
398 log.write(line + "\n")
400 INTERFACE_SETTINGS['method'] = value.strip()
402 if name == "IP_ADDRESS":
403 INTERFACE_SETTINGS['ip'] = value.strip()
405 if name == "IP_GATEWAY":
406 INTERFACE_SETTINGS['gateway'] = value.strip()
408 if name == "IP_NETMASK":
409 INTERFACE_SETTINGS['netmask'] = value.strip()
411 if name == "IP_NETADDR":
412 INTERFACE_SETTINGS['network'] = value.strip()
414 if name == "IP_BROADCASTADDR":
415 INTERFACE_SETTINGS['broadcast'] = value.strip()
417 if name == "IP_DNS1":
418 INTERFACE_SETTINGS['dns1'] = value.strip()
420 if name == "IP_DNS2":
421 INTERFACE_SETTINGS['dns2'] = value.strip()
423 if name == "HOST_NAME":
424 INTERFACE_SETTINGS['hostname'] = string.lower(value)
426 if name == "DOMAIN_NAME":
427 INTERFACE_SETTINGS['domainname'] = string.lower(value)
429 if name == "NET_DEVICE":
430 INTERFACE_SETTINGS['mac'] = string.upper(value)
432 if name == "DISCONNECTED_OPERATION":
433 vars['DISCONNECTED_OPERATION'] = value.strip()
435 except IndexError as e:
436 log.write("Unable to parse configuration file\n")
439 # now if we are set to dhcp, clear out any fields
440 # that don't make sense
441 if INTERFACE_SETTINGS["method"] == "dhcp":
442 INTERFACE_SETTINGS["ip"] = ""
443 INTERFACE_SETTINGS["gateway"] = ""
444 INTERFACE_SETTINGS["netmask"] = ""
445 INTERFACE_SETTINGS["network"] = ""
446 INTERFACE_SETTINGS["broadcast"] = ""
447 INTERFACE_SETTINGS["dns1"] = ""
448 INTERFACE_SETTINGS["dns2"] = ""
450 log.write("Successfully read and parsed node configuration file.\n")
452 # if the mac wasn't specified, read it in from the system.
453 if INTERFACE_SETTINGS["mac"] == "":
455 mac_addr = utils.get_mac_from_interface(device)
458 log.write("Could not get mac address for device eth0.\n")
461 INTERFACE_SETTINGS["mac"] = string.upper(mac_addr)
463 log.write("Got mac address {} for device {}\n"
464 .format(INTERFACE_SETTINGS["mac"], device))
467 # now, if the conf file didn't contain a node id, post the mac address
468 # to plc to get the node_id value
469 if vars['NODE_ID'] is None or vars['NODE_ID'] == 0:
470 log.write("Configuration file does not contain the node_id value.\n")
471 log.write("Querying PLC for node_id.\n")
473 bs_request = BootServerRequest.BootServerRequest(vars)
475 postVars = {"mac_addr" : INTERFACE_SETTINGS["mac"]}
476 result = bs_request.DownloadFile("/boot/getnodeid.php",
477 None, postVars, 1, 1,
480 log.write("Unable to make request to get node_id.\n")
484 node_id_file = file("/tmp/node_id","r")
485 node_id = string.strip(node_id_file.read())
488 log.write("Unable to read node_id from /tmp/node_id\n")
492 node_id = int(string.strip(node_id))
494 log.write("Got node_id from PLC, but not numeric: {}".format(node_id))
498 log.write("Got node_id, but it returned -1\n\n")
500 log.write("------------------------------------------------------\n")
501 log.write("This indicates that this node could not be identified\n")
502 log.write("by PLC. You will need to add the node to your site,\n")
503 log.write("and regenerate the network configuration file.\n")
504 log.write("See the Technical Contact guide for node setup\n")
505 log.write("procedures.\n\n")
506 log.write("Boot process canceled until this is completed.\n")
507 log.write("------------------------------------------------------\n")
509 cancel_boot_flag = "/tmp/CANCEL_BOOT"
510 # this will make the initial script stop requesting scripts from PLC
511 utils.sysexec("touch {}".format(cancel_boot_flag), log)
515 log.write("Got node_id from PLC: {}\n".format(node_id))
516 vars['NODE_ID'] = node_id
520 if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "":
521 log.write("Configuration file does not contain a node_key value.\n")
522 log.write("Using boot nonce instead.\n")
524 # 3.x cds stored the file in /tmp/nonce in ascii form, so they
525 # can be read and used directly. 2.x cds stored in the same place
526 # but in binary form, so we need to convert it to ascii the same
527 # way the old boot scripts did so it matches whats in the db
532 nonce_file = file("/tmp/nonce",read_mode)
533 nonce = nonce_file.read()
536 log.write("Unable to read nonce from /tmp/nonce\n")
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 = INTERFACE_SETTINGS['hostname'] + "." + \
555 INTERFACE_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
565 can_make_api_call = 1
567 log.write("Checking that hostname {} resolves\n".format(hostname))
569 # try a regular dns lookup first
571 resolved_node_ip = socket.gethostbyname(hostname)
572 except socket.gaierror as e:
573 hostname_resolve_ok = 0
576 if INTERFACE_SETTINGS['method'] == "dhcp":
577 if hostname_resolve_ok:
578 INTERFACE_SETTINGS['ip'] = resolved_node_ip
579 node_ip = resolved_node_ip
581 can_make_api_call = 0
583 node_ip = INTERFACE_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 {} does not resolve to {}, but {}:\n"
589 .format(hostname, node_ip, resolved_node_ip))
590 hostname_resolve_ok = 0
592 log.write("Hostname {} correctly resolves to {}:\n"
593 .format(hostname, node_ip))
596 vars["INTERFACE_SETTINGS"] = INTERFACE_SETTINGS
598 if (not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION'] and
599 'NAT_MODE' not in vars):
600 log.write("Hostname does not resolve correctly, will not continue.\n")
602 if can_make_api_call:
603 log.write("Notifying contacts of problem.\n")
605 vars['RUN_LEVEL'] = 'failboot'
606 vars['STATE_CHANGE_NOTIFY'] = 1
607 vars['STATE_CHANGE_NOTIFY_MESSAGE'] = \
608 notify_messages.MSG_HOSTNAME_NOT_RESOLVE
610 UpdateRunLevelWithPLC.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("Configured node hostname does not resolve.")
624 log.write("Using NODE_ID {}\n".format(vars['NODE_ID']))
626 log.write("Unknown NODE_ID")