X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=source%2Fsteps%2FReadNodeConfiguration.py;h=d19439e3f718f1ab88114af412a723a786192442;hb=refs%2Fheads%2Fdisconnectedops;hp=3fcc414a329a3b69a6c90f579ac629eafa72f73c;hpb=7ab7e9dd797333a9fdc8604554e16e192a32144d;p=bootmanager.git diff --git a/source/steps/ReadNodeConfiguration.py b/source/steps/ReadNodeConfiguration.py index 3fcc414..d19439e 100644 --- a/source/steps/ReadNodeConfiguration.py +++ b/source/steps/ReadNodeConfiguration.py @@ -1,52 +1,23 @@ +#!/usr/bin/python2 + # Copyright (c) 2003 Intel Corporation # All rights reserved. - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: - -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. - -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. - -# * Neither the name of the Intel Corporation nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. - -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF -# YOUR JURISDICTION. It is licensee's responsibility to comply with any -# export regulations applicable in licensee's jurisdiction. Under -# CURRENT (May 2000) U.S. export regulations this software is eligible -# for export from the U.S. and can be downloaded by or otherwise -# exported or reexported worldwide EXCEPT to U.S. embargoed destinations -# which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan, -# Afghanistan and any other country to which the U.S. has embargoed -# goods and services. - +# +# Copyright (c) 2004-2006 The Trustees of Princeton University +# All rights reserved. +# expected /proc/partitions format import sys, os, traceback import string import socket +import re import utils from Exceptions import * import BootServerRequest +import BootAPI +import notify_messages +import UpdateBootStateWithPLC # two possible names of the configuration files @@ -62,8 +33,8 @@ def Run( vars, log ): these files can exist in several different locations with several different names. Below is the search order: - filename floppy flash cd - plnode.txt 1 2 4 (/usr/boot), 5 (/usr) + filename floppy flash ramdisk cd + plnode.txt 1 2 4 (/) 5 (/usr/boot), 6 (/usr) planet.cnf 3 The locations will be searched in the above order, plnode.txt @@ -80,7 +51,7 @@ def Run( vars, log ): Expect the following variables from the store: BOOT_CD_VERSION A tuple of the current bootcd version - ALPINA_SERVER_DIR directory on the boot servers containing alpina + SUPPORT_FILE_DIR directory on the boot servers containing scripts and support files Sets the following variables from the configuration file: @@ -101,6 +72,9 @@ def Run( vars, log ): dns2 hostname domainname + + the mac address is read from the machine unless it exists in the + configuration file. """ log.write( "\n\nStep: Reading node configuration file.\n" ) @@ -112,9 +86,9 @@ def Run( vars, log ): if BOOT_CD_VERSION == "": raise ValueError, "BOOT_CD_VERSION" - ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"] - if ALPINA_SERVER_DIR == None: - raise ValueError, "ALPINA_SERVER_DIR" + SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"] + if SUPPORT_FILE_DIR == None: + raise ValueError, "SUPPORT_FILE_DIR" except KeyError, var: raise BootManagerException, "Missing variable in vars: %s\n" % var @@ -142,6 +116,8 @@ def Run( vars, log ): vars['WAS_NODE_ID_IN_CONF']= 0 vars['WAS_NODE_KEY_IN_CONF']= 0 + vars['DISCONNECTED_OPERATION']= '' + # for any devices that need to be mounted to get the configuration # file, mount them here. mount_point= "/tmp/conffilemount" @@ -153,20 +129,25 @@ def Run( vars, log ): # 1. check the regular floppy device log.write( "Checking standard floppy disk for plnode.txt file.\n" ) - + + log.write( "Mounting /dev/fd0 on %s\n" % mount_point ) utils.sysexec_noerr( "mount -o ro -t ext2,msdos /dev/fd0 %s " \ % mount_point, log ) conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME) + + log.write( "Checking for existence of %s\n" % conf_file_path ) if os.access( conf_file_path, os.R_OK ): try: conf_file= file(conf_file_path,"r") conf_file_contents= conf_file.read() conf_file.close() + log.write( "Read in contents of file %s\n" % conf_file_path ) except IOError, e: + log.write( "Unable to read file %s\n" % conf_file_path ) pass - utils.sysexec_noerr( "umount /dev/fd0", log ) + utils.sysexec_noerr( "umount %s" % mount_point, log ) if __parse_configuration_file( vars, log, conf_file_contents): return 1 else: @@ -178,15 +159,19 @@ def Run( vars, log ): # order, but do it now to save mounting/unmounting the disk twice. # try to parse it later... conf_file_path= "%s/%s" % (mount_point,OLD_CONF_FILE_NAME) + + log.write( "Checking for existence of %s (used later)\n" % conf_file_path ) if os.access( conf_file_path, os.R_OK ): try: old_conf_file= file(conf_file_path,"r") old_conf_file_contents= old_conf_file.read() old_conf_file.close() + log.write( "Read in contents of file %s\n" % conf_file_path ) except IOError, e: + log.write( "Unable to read file %s\n" % conf_file_path ) pass - utils.sysexec_noerr( "umount /dev/fd0", log ) + utils.sysexec_noerr( "umount %s" % mount_point, log ) @@ -200,6 +185,7 @@ def Run( vars, log ): for device in devices: if device[:2] != "sd": + log.write( "Skipping non-scsi device %s\n" % device ) continue # test removable @@ -212,39 +198,69 @@ def Run( vars, log ): continue if not removable: + log.write( "Skipping non-removable device %s\n" % device ) continue log.write( "Checking removable device %s\n" % device ) - - # ok, try to mount it and see if we have a conf file. - full_device= "/dev/%s1" % device - try: - utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \ - % (full_device,mount_point), log ) - except BootManagerException, e: - continue - - conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME) - if os.access( conf_file_path, os.R_OK ): + partitions= file("/proc/partitions", "r") + for line in partitions: + found_file= 0 + parsed_file= 0 + + if not re.search("%s[0-9]*$" % device, line): + continue + try: - conf_file= file(conf_file_path,"r") - conf_file_contents= conf_file.read() - conf_file.close() - except IOError, e: - pass - - utils.sysexec_noerr( "umount %s" % full_device, log ) - if __parse_configuration_file( vars, log, conf_file_contents): - return 1 - else: - raise BootManagerException("Found configuration file plnode.txt " \ - "on floppy, but was unable to parse it.") - + # major minor #blocks name + parts= string.split(line) + + # ok, try to mount it and see if we have a conf file. + full_device= "/dev/%s" % parts[3] + except IndexError, e: + log.write( "Incorrect /proc/partitions line:\n%s\n" % line ) + continue + + log.write( "Mounting %s on %s\n" % (full_device,mount_point) ) + try: + utils.sysexec( "mount -o ro -t ext2,msdos %s %s" \ + % (full_device,mount_point), log ) + except BootManagerException, e: + log.write( "Unable to mount, trying next partition\n" ) + continue + + conf_file_path= "%s/%s" % (mount_point,NEW_CONF_FILE_NAME) + + log.write( "Checking for existence of %s\n" % conf_file_path ) + if os.access( conf_file_path, os.R_OK ): + try: + conf_file= file(conf_file_path,"r") + conf_file_contents= conf_file.read() + conf_file.close() + found_file= 1 + log.write( "Read in contents of file %s\n" % \ + conf_file_path ) + + if __parse_configuration_file( vars, log, \ + conf_file_contents): + parsed_file= 1 + except IOError, e: + log.write( "Unable to read file %s\n" % conf_file_path ) + + utils.sysexec_noerr( "umount %s" % mount_point, log ) + if found_file: + if parsed_file: + return 1 + else: + raise BootManagerException( \ + "Found configuration file plnode.txt " \ + "on floppy, but was unable to parse it.") + # 3. check standard floppy disk for old file name planet.cnf - log.write( "Checking standard floppy disk for planet.cnf file.\n" ) + log.write( "Checking standard floppy disk for planet.cnf file " \ + "(from earlier.\n" ) if old_conf_file_contents: if __parse_configuration_file( vars, log, old_conf_file_contents): @@ -254,17 +270,44 @@ def Run( vars, log ): "on floppy, but was unable to parse it." ) - # 4. check for plnode.txt in /usr/boot (mounted already) + # 4. check for plnode.txt in / (ramdisk) + log.write( "Checking / (ramdisk) for plnode.txt file.\n" ) + + conf_file_path= "/%s" % NEW_CONF_FILE_NAME + + log.write( "Checking for existence of %s\n" % conf_file_path ) + if os.access(conf_file_path,os.R_OK): + try: + conf_file= file(conf_file_path,"r") + conf_file_contents= conf_file.read() + conf_file.close() + log.write( "Read in contents of file %s\n" % conf_file_path ) + except IOError, e: + log.write( "Unable to read file %s\n" % conf_file_path ) + pass + + if __parse_configuration_file( vars, log, conf_file_contents): + return 1 + else: + raise BootManagerException( "Found configuration file plnode.txt " \ + "in /, but was unable to parse it.") + + + # 5. check for plnode.txt in /usr/boot (mounted already) log.write( "Checking /usr/boot (cd) for plnode.txt file.\n" ) conf_file_path= "/usr/boot/%s" % NEW_CONF_FILE_NAME + + log.write( "Checking for existence of %s\n" % conf_file_path ) if os.access(conf_file_path,os.R_OK): try: conf_file= file(conf_file_path,"r") conf_file_contents= conf_file.read() conf_file.close() + log.write( "Read in contents of file %s\n" % conf_file_path ) except IOError, e: - pass + log.write( "Unable to read file %s\n" % conf_file_path ) + pass if __parse_configuration_file( vars, log, conf_file_contents): return 1 @@ -274,16 +317,20 @@ def Run( vars, log ): - # 5. check for plnode.txt in /usr (mounted already) + # 6. check for plnode.txt in /usr (mounted already) log.write( "Checking /usr (cd) for plnode.txt file.\n" ) conf_file_path= "/usr/%s" % NEW_CONF_FILE_NAME + + log.write( "Checking for existence of %s\n" % conf_file_path ) if os.access(conf_file_path,os.R_OK): try: conf_file= file(conf_file_path,"r") conf_file_contents= conf_file.read() conf_file.close() + log.write( "Read in contents of file %s\n" % conf_file_path ) except IOError, e: + log.write( "Unable to read file %s\n" % conf_file_path ) pass if __parse_configuration_file( vars, log, conf_file_contents): @@ -301,14 +348,17 @@ def Run( vars, log ): def __parse_configuration_file( vars, log, file_contents ): """ parse a configuration file, set keys in var NETWORK_SETTINGS - in vars (see comment for function ReadNodeConfiguration) + in vars (see comment for function ReadNodeConfiguration). this + also reads the mac address from the machine if successful parsing + of the configuration file is completed. """ BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] - ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"] + SUPPORT_FILE_DIR= vars["SUPPORT_FILE_DIR"] NETWORK_SETTINGS= vars["NETWORK_SETTINGS"] if file_contents is None: + log.write( "__parse_configuration_file called with no file contents\n" ) return 0 try: @@ -389,6 +439,13 @@ def __parse_configuration_file( vars, log, file_contents ): if name == "DOMAIN_NAME": NETWORK_SETTINGS['domainname']= string.lower(value) + if name == "NET_DEVICE": + NETWORK_SETTINGS['mac']= string.upper(value) + + if name == "DISCONNECTED_OPERATION": + vars['DISCONNECTED_OPERATION']= value.strip() + + except IndexError, e: log.write( "Unable to parse configuration file\n" ) return 0 @@ -404,27 +461,34 @@ def __parse_configuration_file( vars, log, file_contents ): NETWORK_SETTINGS["dns1"]= "" NETWORK_SETTINGS["dns2"]= "" - log.write("Successfully read and parsed node configuration file.\n" ) - + # if the mac wasn't specified, read it in from the system. + if NETWORK_SETTINGS["mac"] == "": + device= "eth0" + mac_addr= utils.get_mac_from_interface(device) + + if mac_addr is None: + log.write( "Could not get mac address for device eth0.\n" ) + return 0 + + NETWORK_SETTINGS["mac"]= string.upper(mac_addr) + + log.write( "Got mac address %s for device %s\n" % + (NETWORK_SETTINGS["mac"],device) ) + + + # now, if the conf file didn't contain a node id, post the mac address + # to plc to get the node_id value if vars['NODE_ID'] is None or vars['NODE_ID'] == 0: log.write( "Configuration file does not contain the node_id value.\n" ) log.write( "Querying PLC for node_id.\n" ) bs_request= BootServerRequest.BootServerRequest() - - try: - ifconfig_file= file("/tmp/ifconfig","r") - ifconfig= ifconfig_file.read() - ifconfig_file.close() - except IOError: - log.write( "Unable to read ifconfig output from /tmp/ifconfig\n" ) - return 0 - postVars= {"ifconfig" : ifconfig} + postVars= {"mac_addr" : NETWORK_SETTINGS["mac"]} result= bs_request.DownloadFile( "%s/getnodeid.php" % - ALPINA_SERVER_DIR, + SUPPORT_FILE_DIR, None, postVars, 1, 1, "/tmp/node_id") if result == 0: @@ -446,7 +510,21 @@ def __parse_configuration_file( vars, log, file_contents ): return 0 if node_id == -1: - log.write( "Got node_id, but it returned -1\n" ) + log.write( "Got node_id, but it returned -1\n\n" ) + + log.write( "------------------------------------------------------\n" ) + log.write( "This indicates that this node could not be identified\n" ) + log.write( "by PLC. You will need to add the node to your site,\n" ) + log.write( "and regenerate the network configuration file.\n" ) + log.write( "See the Technical Contact guide for node setup\n" ) + log.write( "procedures.\n\n" ) + log.write( "Boot process canceled until this is completed.\n" ) + log.write( "------------------------------------------------------\n" ) + + cancel_boot_flag= "/tmp/CANCEL_BOOT" + # this will make the initial script stop requesting scripts from PLC + utils.sysexec( "touch %s" % cancel_boot_flag, log ) + return 0 log.write( "Got node_id from PLC: %s\n" % str(node_id) ) @@ -502,45 +580,76 @@ def __parse_configuration_file( vars, log, file_contents ): # is needed for future api calls. # at the same time, we can check to make sure that the hostname - # in the configuration file matches the ip address. + # in the configuration file matches the ip address. if it fails + # notify the owners hostname= NETWORK_SETTINGS['hostname'] + "." + \ NETWORK_SETTINGS['domainname'] + # set to 0 if any part of the hostname resolution check fails + hostname_resolve_ok= 1 + + # set to 0 if the above fails, and, we are using dhcp in which + # case we don't know the ip of this machine (without having to + # parse ifconfig or something). In that case, we won't be able + # to make api calls, so printing a message to the screen will + # have to suffice. + can_make_api_call= 1 + log.write( "Checking that hostname %s resolves\n" % hostname ) + + # try a regular dns lookup first try: resolved_node_ip= socket.gethostbyname(hostname) except socket.gaierror, e: - raise BootManagerException, \ - "Configured node hostname does not resolve." + hostname_resolve_ok= 0 + if NETWORK_SETTINGS['method'] == "dhcp": - NETWORK_SETTINGS['ip']= resolved_node_ip - node_ip= resolved_node_ip + if hostname_resolve_ok: + NETWORK_SETTINGS['ip']= resolved_node_ip + node_ip= resolved_node_ip + else: + can_make_api_call= 0 else: node_ip= NETWORK_SETTINGS['ip'] - if node_ip != resolved_node_ip: - log.write( "Hostname %s does not resolve to %s, but %s:\n" % \ - (hostname,node_ip,resolved_node_ip) ) - else: - log.write( "Hostname %s resolves to %s:\n" % (hostname,node_ip) ) + # make sure the dns lookup matches what the configuration file says + if hostname_resolve_ok: + if node_ip != resolved_node_ip: + log.write( "Hostname %s does not resolve to %s, but %s:\n" % \ + (hostname,node_ip,resolved_node_ip) ) + hostname_resolve_ok= 0 + else: + log.write( "Hostname %s correctly resolves to %s:\n" % + (hostname,node_ip) ) - # 3.x cds, with a node_key on the floppy, can update their mac address - # at plc, so get it here - if BOOT_CD_VERSION[0] == 3 and vars['WAS_NODE_ID_IN_CONF'] == 1: - eth_device= "eth0" - try: - hw_addr_file= file("/sys/class/net/%s/address" % eth_device, "r") - hw_addr= hw_addr_file.read().strip().upper() - hw_addr_file.close() - except IOError, e: - raise BootmanagerException, \ - "could not get hw address for device %s" % eth_device + + vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS + + if not hostname_resolve_ok and not vars['DISCONNECTED_OPERATION']: + log.write( "Hostname does not resolve correctly, will not continue.\n" ) - NETWORK_SETTINGS['mac']= hw_addr + if can_make_api_call: + log.write( "Notifying contacts of problem.\n" ) + vars['BOOT_STATE']= 'dbg' + vars['STATE_CHANGE_NOTIFY']= 1 + vars['STATE_CHANGE_NOTIFY_MESSAGE']= \ + notify_messages.MSG_HOSTNAME_NOT_RESOLVE + + UpdateBootStateWithPLC.Run( vars, log ) + + log.write( "\n\n" ) + log.write( "The hostname and/or ip in the network configuration\n" ) + log.write( "file do not resolve and match.\n" ) + log.write( "Please make sure the hostname set in the network\n" ) + log.write( "configuration file resolves to the ip also specified\n" ) + log.write( "there.\n\n" ) + log.write( "Debug mode is being started on this cd. When the above\n" ) + log.write( "is corrected, reboot the machine to try again.\n" ) - vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS + raise BootManagerException, \ + "Configured node hostname does not resolve." return 1