From: Aaron Klingaman Date: Thu, 26 May 2005 17:11:48 +0000 (+0000) Subject: check in all bootmanager sources X-Git-Tag: BOOTMANAGER_3_1_RELEASE~4 X-Git-Url: http://git.onelab.eu/?p=bootmanager.git;a=commitdiff_plain;h=7ab7e9dd797333a9fdc8604554e16e192a32144d check in all bootmanager sources --- diff --git a/source/BootAPI.py b/source/BootAPI.py new file mode 100644 index 0000000..f1d418b --- /dev/null +++ b/source/BootAPI.py @@ -0,0 +1,122 @@ +import xmlrpclib +import xml.parsers.expat +import hmac +import string +import sha + +from Exceptions import * + + +def create_auth_structure( vars, call_params ): + """ + create and return an authentication structure for a Boot API + call. Vars contains the boot manager runtime variables, and + call_params is a tuple of the parameters that will be passed to the + API call. Return None if unable to (typically due to missing + keys in vars, such as node_id or node_key) + """ + + auth= {} + auth['AuthMethod']= 'hmac' + + try: + network= vars['NETWORK_SETTINGS'] + + auth['node_id']= vars['NODE_ID'] + auth['node_ip']= network['ip'] + node_key= vars['NODE_KEY'] + except KeyError, e: + return None + + msg= serialize_params(call_params) + node_hmac= hmac.new(node_key,msg,sha).hexdigest() + auth['value']= node_hmac + + return auth + + + +def serialize_params( call_params ): + """ + convert a list of parameters into a format that will be used in the + hmac generation. both the boot manager and plc must have a common + format. full documentation is in the boot manager technical document, + but essentially we are going to take all the values (and keys for + dictionary objects), and put them into a list. sort them, and combine + them into one long string encased in a set of braces. + """ + + # if there are no parameters, just return empty paren set + if len(call_params) == 0: + return "[]" + + values= [] + + for param in call_params: + if isinstance(param,list) or isinstance(param,tuple): + values= values + map(str,param) + elif isinstance(param,dict): + values= values + collapse_dict(param) + else: + values.append( str(param) ) + + values.sort() + values= "[" + string.join(values,"") + "]" + return values + + +def collapse_dict( value ): + """ + given a dictionary, return a list of all the keys and values as strings, + in no particular order + """ + + item_list= [] + + if not isinstance(value,dict): + return item_list + + for key in value.keys(): + key_value= value[key] + if isinstance(key_value,list) or isinstance(key_value,tuple): + item_list= item_list + map(str,key_value) + elif isinstance(key_value,dict): + item_list= item_list + collapse_dict(key_value) + else: + item_list.append( str(key_value) ) + + return item_list + + + +def call_api_function( vars, function, user_params ): + """ + call the named api function with params, and return the + value to the caller. the authentication structure is handled + automatically, and doesn't need to be passed in with params. + + If the call fails, a BootManagerException is raised. + """ + + try: + api_server= vars['API_SERVER_INST'] + except KeyError, e: + raise BootManagerException, "No connection to the API server exists." + + auth= create_auth_structure(vars,user_params) + if auth is None: + raise BootManagerException, \ + "Could not create auth structure, missing values." + + params= (auth,) + params= params + user_params + + try: + exec( "rc= api_server.%s(*params)" % function ) + return rc + except xmlrpclib.Fault, fault: + raise BootManagerException, "API Fault: %s" % fault + except xmlrpclib.ProtocolError, err: + raise BootManagerException,"XML RPC protocol error: %s" % err + except xml.parsers.expat.ExpatError, err: + raise BootManagerException,"XML parsing error: %s" % err diff --git a/source/BootManager.py b/source/BootManager.py new file mode 100755 index 0000000..478c714 --- /dev/null +++ b/source/BootManager.py @@ -0,0 +1,360 @@ +#!/usr/bin/python2 -u + +# ------------------------------------------------------------------------ +# THIS file used to be named alpina.py, from the node installer. Since then +# the installer has been expanded to include all the functions of the boot +# manager as well, hence the new name for this file. +# ------------------------------------------------------------------------ + +# 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. + + +import string +import sys, os, traceback +from time import gmtime, strftime +from gzip import GzipFile + +from steps import * +from Exceptions import * +import notify_messages + + + +# all output is written to this file +LOG_FILE= "/tmp/bm.log" +CURL_PATH= "curl" +UPLOAD_LOG_URL = "http://boot.planet-lab.org/alpina-logs/upload.php" + +# the new contents of PATH when the boot manager is running +BIN_PATH= ('/usr/local/bin', + '/usr/local/sbin', + '/bin', + '/sbin', + '/usr/bin', + '/usr/sbin', + '/usr/local/planetlab/bin') + + + +class log: + + def __init__( self, OutputFilePath= None ): + if OutputFilePath: + try: + self.OutputFilePath= OutputFilePath + self.OutputFile= GzipFile( OutputFilePath, "w", 9 ) + except: + print( "Unable to open output file for log, continuing" ) + self.OutputFile= None + + # for upload + os.system( "ifconfig eth0 > /tmp/ifconfig" ) + + + def LogEntry( self, str, inc_newline= 1, display_screen= 1 ): + if self.OutputFile: + self.OutputFile.write( str ) + if display_screen: + sys.stdout.write( str ) + + if inc_newline: + if display_screen: + sys.stdout.write( "\n" ) + if self.OutputFile: + self.OutputFile.write( "\n" ) + + if self.OutputFile: + self.OutputFile.flush() + + + + def write( self, str ): + """ + make log behave like a writable file object (for traceback + prints) + """ + self.LogEntry( str, 0, 1 ) + + + + def Upload( self ): + """ + upload the contents of the log to the server + """ + + self.LogEntry( "Uploading logs to %s" % UPLOAD_LOG_URL ) + + self.OutputFile.close() + self.OutputFile= None + + curl_cmd= "%s -s --connect-timeout 60 --max-time 600 " \ + "--form log=@%s --form ifconfig=\ /dev/null 2>&1" % path ) + + version_file= path + self.BOOTCD_VERSION_FILE + self.Message( "Looking for version file %s" % version_file ) + + if os.access(version_file, os.R_OK) == 0: + self.Message( "No boot cd found." ); + else: + self.Message( "Found boot cd." ) + self.HAS_BOOTCD=1 + break + + + if self.HAS_BOOTCD: + + # check the version of the boot cd, and locate the certs + self.Message( "Getting boot cd version." ) + + versionRegExp= re.compile(r"PlanetLab BootCD v(\S+)") + + bootcd_version_f= file(version_file,"r") + line= string.strip(bootcd_version_f.readline()) + bootcd_version_f.close() + + match= versionRegExp.findall(line) + if match: + (self.BOOTCD_VERSION)= match[0] + + # right now, all the versions of the bootcd are supported, + # so no need to check it + + # create a list of the servers we should + # attempt to contact, and the certs for each + server_list= path + self.BOOTCD_SERVER_FILE + self.Message( "Getting list of servers off of cd from %s." % + server_list ) + + bootservers_f= file(server_list,"r") + bootservers= bootservers_f.readlines() + bootservers_f.close() + + for bootserver in bootservers: + bootserver = string.strip(bootserver) + cacert_path= "%s/%s/%s/%s" % \ + (path,self.BOOTCD_SERVER_CERT_DIR, + bootserver,self.CACERT_NAME) + if os.access(cacert_path, os.R_OK): + self.BOOTSERVER_CERTS[bootserver]= cacert_path + + self.Message( "Set of servers to contact: %s" % + str(self.BOOTSERVER_CERTS) ) + else: + self.Message( "Using default boot server address." ) + self.BOOTSERVER_CERTS[self.DEFAULT_BOOT_SERVER]= "" + + + def CheckProxy( self ): + # see if we have any proxy info from the machine + self.USE_PROXY= 0 + self.Message( "Checking existance of proxy config file..." ) + + if os.access(self.PROXY_FILE, os.R_OK) and \ + os.path.isfile(self.PROXY_FILE): + self.PROXY= string.strip(file(self.PROXY_FILE,'r').readline()) + self.USE_PROXY= 1 + self.Message( "Using proxy %s." % self.PROXY ) + else: + self.Message( "Not using any proxy." ) + + + + def Message( self, Msg ): + if( self.VERBOSE ): + print( Msg ) + + + + def Error( self, Msg ): + sys.stderr.write( Msg + "\n" ) + + + + def Warning( self, Msg ): + self.Error(Msg) + + + + def MakeRequest( self, PartialPath, GetVars, + PostVars, DoSSL, DoCertCheck, + ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT, + MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME): + + if PYCURL_LOADED == 0: + self.Error( "MakeRequest method requires pycurl." ) + return None + + self.CheckProxy() + + self.Message( "Attempting to retrieve %s" % PartialPath ) + + # we can't do ssl and check the cert if we don't have a bootcd + if DoSSL and DoCertCheck and not self.HAS_BOOTCD: + self.Error( "No boot cd exists (needed to use -c and -s.\n" ) + return None + + # didn't pass a path in? just get the root doc + if PartialPath == "": + PartialPath= "/" + + # ConnectTimeout has to be greater than 0 + if ConnectTimeout <= 0: + self.Error( "Connect timeout must be greater than zero.\n" ) + return None + + # setup the post and get vars for the request + if PostVars: + dopostdata= 1 + postdata = urllib.urlencode(PostVars) + self.Message( "Posting data:\n%s\n" % postdata ) + else: + dopostdata= 0 + + getstr= "" + if GetVars: + getstr= "?" + urllib.urlencode(GetVars) + self.Message( "Get data:\n%s\n" % getstr ) + + # now, attempt to make the request, starting at the first + # server in the list + for server in self.BOOTSERVER_CERTS: + self.Message( "Contacting server %s." % server ) + + certpath = self.BOOTSERVER_CERTS[server] + + curl= pycurl.Curl() + + # don't want curl sending any signals + curl.setopt(pycurl.NOSIGNAL, 1) + + curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout) + self.Message( "Connect timeout is %s seconds" % \ + ConnectTimeout ) + + curl.setopt(pycurl.TIMEOUT, MaxTransferTime) + self.Message( "Max transfer time is %s seconds" % \ + MaxTransferTime ) + + curl.setopt(pycurl.FOLLOWLOCATION, 1) + curl.setopt(pycurl.MAXREDIRS, 2) + + if self.USE_PROXY: + curl.setopt(pycurl.PROXY, self.PROXY ) + + if DoSSL: + curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION) + + url = "https://%s/%s%s" % (server,PartialPath,getstr) + if DoCertCheck: + curl.setopt(pycurl.CAINFO, certpath) + curl.setopt(pycurl.SSL_VERIFYPEER, 2) + self.Message( "Using SSL version %d and verifying peer." % \ + self.CURL_SSL_VERSION ) + else: + curl.setopt(pycurl.SSL_VERIFYPEER, 0) + self.Message( "Using SSL version %d" % \ + self.CURL_SSL_VERSION ) + else: + url = "http://%s/%s%s" % (server,PartialPath,getstr) + + if dopostdata: + curl.setopt(pycurl.POSTFIELDS, postdata) + + curl.setopt(pycurl.URL, url) + self.Message( "URL: %s" % url ) + + # setup the output buffer + buffer = StringIO() + curl.setopt(pycurl.WRITEFUNCTION, buffer.write) + + try: + self.Message( "Fetching..." ) + curl.perform() + self.Message( "Done." ) + + http_result= curl.getinfo(pycurl.HTTP_CODE) + curl.close() + + # check the code, return the string only if it was successfull + if http_result == self.HTTP_SUCCESS: + self.Message( "Successfull!" ) + return buffer.getvalue() + else: + self.Message( "Failure, resultant http code: %d" % \ + http_result ) + return None + + except pycurl.error, err: + errno, errstr= err + self.Error( "connect to %s failed; curl error %d: '%s'\n" % + (server,errno,errstr) ) + + self.Error( "Unable to successfully contact any boot servers.\n" ) + return None + + + + def DownloadFile(self, PartialPath, GetVars, PostVars, + DoSSL, DoCertCheck, DestFilePath, + ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT, + MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME): + + self.Message( "Attempting to retrieve %s" % PartialPath ) + + # we can't do ssl and check the cert if we don't have a bootcd + if DoSSL and DoCertCheck and not self.HAS_BOOTCD: + self.Error( "No boot cd exists (needed to use -c and -s.\n" ) + return 0 + + if DoSSL and not PYCURL_LOADED: + self.Warning( "Using SSL without pycurl will by default " \ + "check at least standard certs." ) + + # ConnectTimeout has to be greater than 0 + if ConnectTimeout <= 0: + self.Error( "Connect timeout must be greater than zero.\n" ) + return 0 + + + self.CheckProxy() + + dopostdata= 0 + + # setup the post and get vars for the request + if PostVars: + dopostdata= 1 + postdata = urllib.urlencode(PostVars) + self.Message( "Posting data:\n%s\n" % postdata ) + + getstr= "" + if GetVars: + getstr= "?" + urllib.urlencode(GetVars) + self.Message( "Get data:\n%s\n" % getstr ) + + # now, attempt to make the request, starting at the first + # server in the list + + for server in self.BOOTSERVER_CERTS: + self.Message( "Contacting server %s." % server ) + + certpath = self.BOOTSERVER_CERTS[server] + + + # output what we are going to be doing + self.Message( "Connect timeout is %s seconds" % \ + ConnectTimeout ) + + self.Message( "Max transfer time is %s seconds" % \ + MaxTransferTime ) + + if DoSSL: + url = "https://%s/%s%s" % (server,PartialPath,getstr) + + if DoCertCheck and PYCURL_LOADED: + self.Message( "Using SSL version %d and verifying peer." % + self.CURL_SSL_VERSION ) + else: + self.Message( "Using SSL version %d." % + self.CURL_SSL_VERSION ) + else: + url = "http://%s/%s%s" % (server,PartialPath,getstr) + + self.Message( "URL: %s" % url ) + + # setup a new pycurl instance, or a curl command line string + # if we don't have pycurl + + if PYCURL_LOADED: + curl= pycurl.Curl() + + # don't want curl sending any signals + curl.setopt(pycurl.NOSIGNAL, 1) + + curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout) + curl.setopt(pycurl.TIMEOUT, MaxTransferTime) + + # do not follow location when attempting to download a file + curl.setopt(pycurl.FOLLOWLOCATION, 0) + + if self.USE_PROXY: + curl.setopt(pycurl.PROXY, self.PROXY ) + + if DoSSL: + curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION) + + if DoCertCheck: + curl.setopt(pycurl.CAINFO, certpath) + curl.setopt(pycurl.SSL_VERIFYPEER, 2) + + else: + curl.setopt(pycurl.SSL_VERIFYPEER, 0) + + if dopostdata: + curl.setopt(pycurl.POSTFIELDS, postdata) + + curl.setopt(pycurl.URL, url) + else: + + cmdline = "%s " \ + "--connect-timeout %d " \ + "--max-time %d " \ + "--header Pragma: " \ + "--output %s " \ + "--fail " % \ + (self.CURL_CMD, ConnectTimeout, + MaxTransferTime, DestFilePath) + + if dopostdata: + cmdline = cmdline + "--data '" + postdata + "' " + + if not self.VERBOSE: + cmdline = cmdline + "--silent " + + if self.USE_PROXY: + cmdline = cmdline + "--proxy %s " % self.PROXY + + if DoSSL: + cmdline = cmdline + "--sslv%d " % self.CURL_SSL_VERSION + + if DoCertCheck: + cmdline = cmdline + "--cacert %s " % certpath + + cmdline = cmdline + url + + self.Message( "curl command: %s" % cmdline ) + + + if PYCURL_LOADED: + try: + # setup the output file + outfile = open(DestFilePath,"wb") + + self.Message( "Opened output file %s" % DestFilePath ) + + curl.setopt(pycurl.WRITEDATA, outfile) + + self.Message( "Fetching..." ) + curl.perform() + self.Message( "Done." ) + + http_result= curl.getinfo(pycurl.HTTP_CODE) + curl.close() + + outfile.close() + self.Message( "Results saved in %s" % DestFilePath ) + + # check the code, return 1 if successfull + if http_result == self.HTTP_SUCCESS: + self.Message( "Successfull!" ) + return 1 + else: + self.Message( "Failure, resultant http code: %d" % \ + http_result ) + + except pycurl.error, err: + errno, errstr= err + self.Error( "connect to %s failed; curl error %d: '%s'\n" % + (server,errno,errstr) ) + + if not outfile.closed: + try: + os.unlink(DestFilePath) + outfile.close + except OSError: + pass + + else: + self.Message( "Fetching..." ) + rc = os.system(cmdline) + self.Message( "Done." ) + + if rc != 0: + try: + os.unlink( DestFilePath ) + except OSError: + pass + self.Message( "Failure, resultant curl code: %d" % rc ) + self.Message( "Removed %s" % DestFilePath ) + else: + self.Message( "Successfull!" ) + return 1 + + self.Error( "Unable to successfully contact any boot servers.\n" ) + return 0 + + + + +def usage(): + print( + """ +Usage: BootServerRequest.py [options] +Options: + -c/--checkcert Check SSL certs. Ignored if -s/--ssl missing. + -h/--help This help text + -o/--output Write result to file + -s/--ssl Make the request over HTTPS + -v Makes the operation more talkative +"""); + + + +if __name__ == "__main__": + import getopt + + # check out our command line options + try: + opt_list, arg_list = getopt.getopt(sys.argv[1:], + "o:vhsc", + [ "output=", "verbose", \ + "help","ssl","checkcert"]) + + ssl= 0 + checkcert= 0 + output_file= None + verbose= 0 + + for opt, arg in opt_list: + if opt in ("-h","--help"): + usage(0) + sys.exit() + + if opt in ("-c","--checkcert"): + checkcert= 1 + + if opt in ("-s","--ssl"): + ssl= 1 + + if opt in ("-o","--output"): + output_file= arg + + if opt == "-v": + verbose= 1 + + if len(arg_list) != 1: + raise Exception + + partialpath= arg_list[0] + if string.lower(partialpath[:4]) == "http": + raise Exception + + except: + usage() + sys.exit(2) + + # got the command line args straightened out + requestor= BootServerRequest(verbose) + + if output_file: + requestor.DownloadFile( partialpath, None, None, ssl, + checkcert, output_file) + else: + result= requestor.MakeRequest( partialpath, None, None, ssl, checkcert) + if result: + print result + else: + sys.exit(1) diff --git a/source/Exceptions.py b/source/Exceptions.py new file mode 100644 index 0000000..fc81fd3 --- /dev/null +++ b/source/Exceptions.py @@ -0,0 +1,8 @@ + +class BootManagerException(Exception): + def __init__( self, err ): + self.__fault= err + + def __str__( self ): + return self.__fault + diff --git a/source/compatibility.py b/source/compatibility.py new file mode 100644 index 0000000..b2e7f02 --- /dev/null +++ b/source/compatibility.py @@ -0,0 +1,216 @@ +""" +Various functions that are used to allow the boot manager to run on various +different cds are included here +""" + +import string +import os, sys + +import utils +import BootServerRequest + + +def setup_lvm_2x_cd( vars, log ): + """ + make available a set of lvm utilities for 2.x cds that don't have them + on the cd. + + Expect the following variables to be set: + TEMP_PATH somewhere to store what we need to run + BOOT_CD_VERSION A tuple of the current bootcd version + ALPINA_SERVER_DIR directory on the boot servers containing alpina + scripts and support files + LVM_SETUP_2X_CD indicates if lvm is downloaded and setup for 2.x cds + + Set the following variables upon successfully running: + LVM_SETUP_2X_CD indicates if lvm is downloaded and setup for 2.x cds + """ + + # make sure we have the variables we need + try: + TEMP_PATH= vars["TEMP_PATH"] + if TEMP_PATH == "": + raise ValueError, "TEMP_PATH" + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + 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" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + if BOOT_CD_VERSION[0] != 2: + log.write( "Only 2.x boot cds need lvm setup manually.\n" ) + return 1 + + LVM_SETUP_2X_CD= 0 + if 'LVM_SETUP_2X_CD' in vars.keys(): + LVM_SETUP_2X_CD= vars['LVM_SETUP_2X_CD'] + + if LVM_SETUP_2X_CD: + log.write( "LVM already downloaded and setup\n" ) + return 1 + + log.write( "Downloading additional libraries for lvm\n" ) + + bs_request= BootServerRequest.BootServerRequest() + + utils.makedirs(TEMP_PATH) + + # download and extract support tarball for this step, + # which has everything we need to successfully run + step_support_file= "alpina-BootLVM.tar.gz" + source_file= "%s/%s" % (ALPINA_SERVER_DIR,step_support_file) + dest_file= "%s/%s" % (TEMP_PATH, step_support_file) + + log.write( "Downloading support file for this step\n" ) + result= bs_request.DownloadFile( source_file, None, None, + 1, 1, dest_file ) + if not result: + raise BootManagerException, "Download failed." + + log.write( "Extracting support files\n" ) + old_wd= os.getcwd() + utils.chdir( TEMP_PATH ) + utils.sysexec( "tar -C / -xzf %s" % step_support_file, log ) + utils.removefile( dest_file ) + utils.chdir( old_wd ) + + utils.sysexec( "ldconfig", log ) + + # load lvm-mod + log.write( "Loading lvm module\n" ) + utils.sysexec( "modprobe lvm-mod", log ) + + # take note that we have lvm setup + LVM_SETUP_2X_CD= 1 + vars['LVM_SETUP_2X_CD']= LVM_SETUP_2X_CD + + return 1 + + + +def setup_partdisks_2x_cd( vars, log ): + """ + download necessary files to handle partitioning disks on 2.x cds + + Expect the following variables to be set: + TEMP_PATH somewhere to store what we need to run + BOOT_CD_VERSION A tuple of the current bootcd version + ALPINA_SERVER_DIR directory on the boot servers containing alpina + scripts and support files + PARTDISKS_SETUP_2X_CD indicates if lvm is downloaded and setup for 2.x cds + + Set the following variables upon successfully running: + PARTDISKS_SETUP_2X_CD indicates if lvm is downloaded and setup for 2.x cds + """ + + # make sure we have the variables we need + try: + TEMP_PATH= vars["TEMP_PATH"] + if TEMP_PATH == "": + raise ValueError, "TEMP_PATH" + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + 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" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + if BOOT_CD_VERSION[0] != 2: + log.write( "Only 2.x boot cds need partition disk tools setup manually.\n" ) + return 1 + + PARTDISKS_SETUP_2X_CD= 0 + if 'PARTDISKS_SETUP_2X_CD' in vars.keys(): + PARTDISKS_SETUP_2X_CD= vars['PARTDISKS_SETUP_2X_CD'] + + if PARTDISKS_SETUP_2X_CD: + log.write( "Partition disk tools already downloaded and setup\n" ) + return 1 + + log.write( "Downloading additional libraries for partitioning disks\n" ) + + bs_request= BootServerRequest.BootServerRequest() + + # download and extract support tarball for this step, + # which has everything we need to successfully run + step_support_file= "alpina-PartDisks.tar.gz" + source_file= "%s/%s" % (ALPINA_SERVER_DIR,step_support_file) + dest_file= "%s/%s" % (TEMP_PATH, step_support_file) + + log.write( "Downloading support file for this step\n" ) + result= bs_request.DownloadFile( source_file, None, None, + 1, 1, dest_file ) + if not result: + raise BootManagerException, "Download failed." + + log.write( "Extracting support files\n" ) + old_wd= os.getcwd() + utils.chdir( TEMP_PATH ) + utils.sysexec( "tar -xzf %s" % step_support_file, log ) + utils.removefile( dest_file ) + utils.chdir( old_wd ) + + # also included in the support package was a list of extra + # paths (lib-paths) for /etc/ld.so.conf. + # so add those, and rerun ldconfig + # so we can make our newly downloaded libraries available + + ldconf_file= file("/etc/ld.so.conf","a+") + lib_paths_file= file( TEMP_PATH + "/lib-paths","r") + + for line in lib_paths_file: + path= string.strip(line) + if path != "": + ldconf_file.write( "%s/%s\n" % (TEMP_PATH,path) ) + ldconf_file.close() + lib_paths_file.close() + + utils.sysexec( "ldconfig", log ) + + # update the PYTHONPATH to include the python modules in + # the support package + sys.path.append( TEMP_PATH + "/usr/lib/python2.2" ) + sys.path.append( TEMP_PATH + "/usr/lib/python2.2/site-packages" ) + + # update the environment variable PATH to include + # TEMP_PATH/sbin and others there + new_paths= ('%s/sbin'% TEMP_PATH, + '%s/bin'% TEMP_PATH, + '%s/user/sbin'% TEMP_PATH, + '%s/user/bin'% TEMP_PATH) + + old_path= os.environ['PATH'] + os.environ['PATH']= old_path + ":" + string.join(new_paths,":") + + # everything should be setup to import parted. this + # import is just to make sure it'll work when this step + # is being run + log.write( "Imported parted\n" ) + try: + import parted + except ImportError: + raise BootManagerException, "Unable to import parted." + + # take note that we have part disks setup + PARTDISKS_SETUP_2X_CD= 1 + vars['PARTDISKS_SETUP_2X_CD']= PARTDISKS_SETUP_2X_CD + + + diff --git a/source/configuration b/source/configuration new file mode 100644 index 0000000..fc8f872 --- /dev/null +++ b/source/configuration @@ -0,0 +1,77 @@ +# this file contains a list of variables +# to import to the INSTALL_STORE before +# any of the steps run. + + +# the current version of the bootmanager +VERSION=3.1 + + +# full url to which api server to contact +BOOT_API_SERVER=https://www.planet-lab.org:443/PLCAPI/ + + +# path to store temporary files during the install, +# do not include trailing slashes +TEMP_PATH=/mnt/tmp + + +# path to the system mount point +SYSIMG_PATH=/mnt/tmp/sysimg + + +# where the cacerts for the boot cd can be found +# currently, this must start with /mnt/cdrom +# which is hardcoded in the installer +CACERT_PATH=/mnt/cdrom/bootme/cacert + + +# the nonce the boot cd created, need to authenticate +# requests that are made to the boot server +NONCE_FILE=/tmp/nonce + + +# directory containing planetlab specific configuration +# files, like the http_proxy file +PLCONF_DIR=/etc/planetlab + + +# directory on the boot server containing +# alpina support files and scripts +ALPINA_SERVER_DIR=/alpina-v3 + + +# this sets the size of the root logical volume, +# after the root and swap has been created, remaining +# goes to the vserver partition +ROOT_SIZE=3G + + +# override the swap size +SWAP_SIZE=1G + + +# whether or not to skip hardware requirement check +SKIP_HARDWARE_REQUIREMENT_CHECK=0 + + +# minimum amount of memory needed for installer, in kb +MINIMUM_MEMORY=511000 + + +# minimum block disk size in GB to be added to lvm. +# if any block devices are smaller than this, they are ignored. +MINIMUM_DISK_SIZE=5 + + +# total minimum disk size in GB if all usable disks are below this +# size, the node cannot be installed +TOTAL_MINIMUM_DISK_SIZE=50 + + +# set of langugase for install (used in /etc/rpm/macros) +INSTALL_LANGS=en_US + + +# number of auth failures before starting debug mode +NUM_AUTH_FAILURES_BEFORE_DEBUG=2 diff --git a/source/debug_files/debug_root_ssh_key b/source/debug_files/debug_root_ssh_key new file mode 100644 index 0000000..0f4105e --- /dev/null +++ b/source/debug_files/debug_root_ssh_key @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAs3jl1PRq97O4WKngafKUe4LTkQrKqgaHUj6sUKfC9KT40ek19jlzU2YWnuoaxEpSLks+Z0KPnSAIyZW5fnFYasIh9mrLSbY06d2Mor5919sCv9fIm/6QHq6gBiFjs50HITx53jWjeu/nmZeLOBsBtioLkNW2vBMQKHz6+q+wea2nh+YX3X5ZRpSp6znPR5fjaWzm0TEfA6oStUfsOIBds98XswghfT0GtWehG5FpPT/X9g7EObQKN/fzSSe1SdMSEMLPl+e0+KQ0+jB/pCULfSm9Qlw6I5cYQXwxKeT2tEPIcmLPe/U1hhoqGyaADo+a0OmCQ84yJ3obMNMWGH0uIQ== debug@planet-lab.org diff --git a/source/debug_files/sshd_config_v2 b/source/debug_files/sshd_config_v2 new file mode 100644 index 0000000..76e0092 --- /dev/null +++ b/source/debug_files/sshd_config_v2 @@ -0,0 +1,93 @@ +# boot cd version 3.x sshd configuration file for debug mode + +#Port 22 +Protocol 2 +#ListenAddress 0.0.0.0 +#ListenAddress :: + +# HostKey for protocol version 1 +#HostKey /etc/ssh/ssh_host_key +# HostKeys for protocol version 2 +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_dsa_key + +# Lifetime and size of ephemeral version 1 server key +#KeyRegenerationInterval 1h +#ServerKeyBits 768 + +# Logging +#obsoletes QuietMode and FascistLogging +#SyslogFacility AUTH +SyslogFacility AUTHPRIV +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +#PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 + +#RSAAuthentication yes +#PubkeyAuthentication yes +#AuthorizedKeysFile .ssh/authorized_keys + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#RhostsRSAAuthentication no +# similar for protocol version 2 +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# RhostsRSAAuthentication and HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PermitEmptyPasswords no +PasswordAuthentication no + +# Change to no to disable s/key passwords +#ChallengeResponseAuthentication yes +ChallengeResponseAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication mechanism. +# Depending on your PAM configuration, this may bypass the setting of +# PasswordAuthentication, PermitEmptyPasswords, and +# "PermitRootLogin without-password". If you just want the PAM account and +# session checks to run without PAM authentication, then enable this but set +# ChallengeResponseAuthentication=no +PAMAuthenticationViaKbdInt no + +#AllowTcpForwarding yes +#GatewayPorts no +#X11Forwarding no +X11Forwarding yes +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#UseLogin no +#UsePrivilegeSeparation yes +#PermitUserEnvironment no +#Compression yes +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS yes +#PidFile /var/run/sshd.pid +#MaxStartups 10 +#ShowPatchLevel no + +# no default banner path +#Banner /some/path + +# override default of no subsystems +Subsystem sftp /usr/libexec/openssh/sftp-server diff --git a/source/debug_files/sshd_config_v3 b/source/debug_files/sshd_config_v3 new file mode 100644 index 0000000..2a8f428 --- /dev/null +++ b/source/debug_files/sshd_config_v3 @@ -0,0 +1,92 @@ +# boot cd version 3.x sshd configuration file for debug mode + +#Port 22 +Protocol 2 +#ListenAddress 0.0.0.0 +#ListenAddress :: + +# HostKey for protocol version 1 +#HostKey /etc/ssh/ssh_host_key +# HostKeys for protocol version 2 +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_dsa_key + +# Lifetime and size of ephemeral version 1 server key +#KeyRegenerationInterval 1h +#ServerKeyBits 768 + +# Logging +#obsoletes QuietMode and FascistLogging +#SyslogFacility AUTH +SyslogFacility AUTHPRIV +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +#PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 + +#RSAAuthentication yes +#PubkeyAuthentication yes +#AuthorizedKeysFile .ssh/authorized_keys + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#RhostsRSAAuthentication no +# similar for protocol version 2 +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# RhostsRSAAuthentication and HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PermitEmptyPasswords no +PasswordAuthentication no + +# Change to no to disable s/key passwords +#ChallengeResponseAuthentication yes +ChallengeResponseAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication mechanism. +# Depending on your PAM configuration, this may bypass the setting of +# PasswordAuthentication, PermitEmptyPasswords, and +# "PermitRootLogin without-password". If you just want the PAM account and +# session checks to run without PAM authentication, then enable this but set +# ChallengeResponseAuthentication=no + +#AllowTcpForwarding yes +#GatewayPorts no +#X11Forwarding no +X11Forwarding yes +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#UseLogin no +#UsePrivilegeSeparation yes +#PermitUserEnvironment no +#Compression yes +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS yes +#PidFile /var/run/sshd.pid +#MaxStartups 10 +#ShowPatchLevel no + +# no default banner path +#Banner /some/path + +# override default of no subsystems +Subsystem sftp /usr/libexec/openssh/sftp-server diff --git a/source/merge_hw_tables.py b/source/merge_hw_tables.py new file mode 100755 index 0000000..213ac00 --- /dev/null +++ b/source/merge_hw_tables.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python + +""" +The point of this small utility is to take a file in the format +of /lib/modules/`uname -r`/modules.pcimap and /usr/share/hwdata/pcitable +and output a condensed, more easily used format for module detection. This is +done by first getting a list of all the built modules, then loading the +pci ids for each of those modules from modules.pcimap, then finally merging +in the contents of pcitable (for built modules only). The result should be +a file with a pretty comprehensive mapping of pci ids to module names. + +The output is used by the PlanetLab boot cd (3.0+) and the pl_hwinit script +to load all the applicable modules by scanning lspci output. + + + +Expected format of file modules.dep includes lines of: + +/full/module/path/mod.ko: + +Expected format of file modules.pcimap includes lines of: + +# pci_module vendor device subvendor subdevice class class_mask driver_data +cciss 0x00000e11 0x0000b060 0x00000e11 0x00004070 0x00000000 0x00000000 0x0 +cciss 0x00000e11 0x0000b178 0x00000e11 0x00004070 0x00000000 0x00000000 0x0 + +Expected format of file pcitable includes lines of: + +# ("%d\t%d\t%s\t"%s"\n", vendid, devid, moduleName, cardDescription) +# or ("%d\t%d\t%d\t%d\t%s\t"%s"\n", vendid, devid, subvendid, subdevid, moduleNa +# me, cardDescription) +0x0e11 0x0508 "tmspci" "Compaq|Netelligent 4/16 Token Ring" +0x1000 0x0407 0x8086 0x0532 "megaraid" "Storage RAID Controller SRCU42X" + +Lines listing a module name of ignore or unknown from pcitable are skipped + + + +Output format, for each line that matches the above lines: +cciss 0e11:b060 0e11:b178 + +""" + +import os, sys +import string + + + +class merge_hw_tables: + + def merge_files(self, modules_dep_path, modules_pcimap_path, pcitable_path): + """ + merge the three files as described above, and return a dictionary. + keys are module names, value is a list of pci ids for that module, + in the form "0e11:b178" + """ + + try: + modulesdep_file= file(modules_dep_path,"r") + except IOError: + sys.stderr.write( "Unable to open modules.dep: %s\n" % + modules_dep_path ) + return + + try: + pcimap_file= file(modules_pcimap_path,"r") + except IOError: + sys.stderr.write( "Unable to open modules.pcimap: %s\n" % + modules_pcimap_path ) + return + + try: + pcitable_file= file(pcitable_path,"r") + except IOError: + sys.stderr.write( "Unable to open pcitable: %s\n" % + pcitable_path ) + return + + + # associative array to store all matches of module -> ['vendor:device',..] + # entries + all_modules= {} + + + # first step, create an associative array of all the built modules + for line in modulesdep_file: + parts= string.split(line,":") + if len(parts) < 2: + continue + + full_mod_path= parts[0] + parts= string.split(full_mod_path,"/") + module_name= parts[len(parts)-1] + module_name_len= len(module_name) + if module_name[module_name_len-3:] == ".ko": + all_modules[module_name[:-3]]= [] + + + # now, parse the pcimap and add devices + line_num= 0 + for line in pcimap_file: + line_num= line_num+1 + + # skip blank lines, or lines that begin with # (comments) + line= string.strip(line) + if len(line) == 0: + continue + + if line[0] == "#": + continue + + line_parts= string.split(line) + if line_parts is None or len(line_parts) != 8: + sys.stderr.write( "Skipping line %d in pcimap " \ + "(incorrect format)\n" % line_num ) + continue + + # first two parts are always vendor / device id + module= line_parts[0] + vendor_id= line_parts[1] + device_id= line_parts[2] + + + # valid vendor and devices are 10 chars (0xXXXXXXXX) and begin with 0x + if len(vendor_id) != 10 or len(device_id) != 10: + sys.stderr.write( "Skipping line %d in pcimap " \ + "(invalid vendor/device id length)\n" % + line_num ) + continue + + if string.lower(vendor_id[:2]) != "0x" \ + or string.lower(device_id[:2]) != "0x": + sys.stderr.write( "Skipping line %d in pcimap " \ + "(invalid vendor/device id format)\n" % line_num ) + continue + + # cut down the ids, only need last 4 bytes + # start at 6 = (10 total chars - 4 last chars need) + vendor_id= string.lower(vendor_id[6:]) + device_id= string.lower(device_id[6:]) + + full_id= "%s:%s" % (vendor_id, device_id) + + if all_modules.has_key(module): + if full_id not in all_modules[module]: + all_modules[module].append( full_id ) + else: + # normally shouldn't get here, as the list is + # prepopulated with all the built modules + all_modules[module]= [full_id,] + + + # parse pcitable, add any more ids for the devices + line_num= 0 + for line in pcitable_file: + line_num= line_num+1 + + # skip blank lines, or lines that begin with # (comments) + line= string.strip(line) + if len(line) == 0: + continue + + if line[0] == "#": + continue + + line_parts= string.split(line) + if line_parts is None or len(line_parts) <= 2: + sys.stderr.write( "Skipping line %d in pcitable " \ + "(incorrect format 1)\n" % line_num ) + continue + + # vendor id is always the first field, device the second. also, + # strip off first two chars (the 0x) + vendor_id= string.lower(line_parts[0][2:]) + device_id= string.lower(line_parts[1][2:]) + + full_id= "%s:%s" % (vendor_id, device_id) + + # if the first char of the third field is a double + # quote, the third field is a module, else if the first + # char of the third field is a 0 (zero), the fifth field is + # the module name. it would nice if there was any easy way + # to split a string on spaces, but recognize quoted strings, + # so they wouldn't be split up. that is the reason for this wierd check + if line_parts[2][0] == '"': + module= line_parts[2] + elif line_parts[2][0] == '0': + try: + module= line_parts[4] + except ValueError, e: + sys.stderr.write( "Skipping line %d in pcitable " \ + "(incorrect format 2)\n" % line_num ) + continue + else: + sys.stderr.write( "Skipping line %d in pcitable " \ + "(incorrect format 3)\n" % line_num ) + continue + + # remove the first and last char of module (quote marks) + module= module[1:] + module= module[:len(module)-1] + + # now add it if we don't already have this module -> id mapping + if all_modules.has_key(module): + if full_id not in all_modules[module]: + all_modules[module].append( full_id ) + else: + # don't add any modules from pcitable that we don't + # already know about + pass + + pcitable_file.close() + pcimap_file.close() + modulesdep_file.close() + + return all_modules + + + +if __name__ == "__main__": + def usage(): + print( "\nUsage:" ) + print( "rewrite-pcitable.py " \ + " []" ) + print( "" ) + + + if len(sys.argv) < 4: + usage() + sys.exit(1) + + + if len(sys.argv) > 4: + output_file_name= sys.argv[4] + try: + output_file= file(output_file_name,"w") + except IOError: + sys.stderr.write( "Unable to open %s for writing.\n" % output_file ) + sys.exit(1) + else: + output_file= sys.stdout + + + all_modules= merge_hw_tables().merge_files( sys.argv[1], sys.argv[2], + sys.argv[3] ) + + if all_modules is not None: + for module in all_modules.keys(): + devices= all_modules[module] + if len(devices) > 0: + devices_str= string.join( all_modules[module], " " ) + output_file.write( "%s %s\n" % (module,devices_str) ) + else: + sys.stderr.write( "Unable to list modules.\n" ) + + output_file.close() diff --git a/source/notify_messages.py b/source/notify_messages.py new file mode 100644 index 0000000..b33dabd --- /dev/null +++ b/source/notify_messages.py @@ -0,0 +1,11 @@ +""" +this file contains the ids of messages that we can send the contacts +at a site, through the BootNotifyOwners call +""" + +MSG_INSTALL_FINISHED= "installfinished" +MSG_INSUFFICIENT_DISK= "insufficientdisk" +MSG_INSUFFICIENT_MEMORY= "insufficientmemory" +MSG_NO_NODE_CONFIG_FILE= "noconfig" +MSG_AUTH_FAIL= "authfail" +MSG_NODE_NOT_INSTALLED= "notinstalled" diff --git a/source/steps/AuthenticateWithPLC.py b/source/steps/AuthenticateWithPLC.py new file mode 100644 index 0000000..bad3c16 --- /dev/null +++ b/source/steps/AuthenticateWithPLC.py @@ -0,0 +1,78 @@ +import os + +from Exceptions import * +import BootAPI +import StartDebug + + +AUTH_FAILURE_COUNT_FILE= "/tmp/authfailurecount" + + +def Run( vars, log ): + """ + Authenticate this node with PLC. This ensures that the node can operate + as normal, and that our management authority has authorized it. + + For this, just call the PLC api function BootCheckAuthentication + + Return 1 if authorized, a BootManagerException if not or the + call fails entirely. + + If there are two consecutive authentication failures, put the node + into debug mode and exit the bootmanager. + + Expect the following variables from the store: + NUM_AUTH_FAILURES_BEFORE_DEBUG How many failures before debug + """ + + log.write( "\n\nStep: Authenticating node with PLC.\n" ) + + # make sure we have the variables we need + try: + NUM_AUTH_FAILURES_BEFORE_DEBUG= int(vars["NUM_AUTH_FAILURES_BEFORE_DEBUG"]) + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + try: + authorized= BootAPI.call_api_function( vars, "BootCheckAuthentication", () ) + if authorized == 1: + log.write( "Authentication successful.\n" ) + + try: + os.unlink( AUTH_FAILURE_COUNT_FILE ) + except OSError, e: + pass + + return 1 + except BootManagerException, e: + log.write( "Authentication failed: %s.\n" % e ) + + # increment auth failure + auth_failure_count= 0 + try: + auth_failure_count= int(file(AUTH_FAILURE_COUNT_FILE,"r").read().strip()) + except IOError: + pass + except ValueError: + pass + + auth_failure_count += 1 + + try: + fail_file= file(AUTH_FAILURE_COUNT_FILE,"w") + fail_file.write( str(auth_failure_count) ) + fail_file.close() + except IOError: + pass + + if auth_failure_count >= NUM_AUTH_FAILURES_BEFORE_DEBUG: + log.write( "Maximum number of authentication failures reached.\n" ) + log.write( "Canceling boot process and going into debug mode.\n" ) + + StartDebug.Run( vars, log ) + + raise BootManagerException, "Unable to authenticate node." + + diff --git a/source/steps/ChainBootNode.py b/source/steps/ChainBootNode.py new file mode 100644 index 0000000..9cc16b8 --- /dev/null +++ b/source/steps/ChainBootNode.py @@ -0,0 +1,138 @@ +import string + +from Exceptions import * +import utils +import compatibility +from systeminfo import systeminfo +import BootAPI + + +def Run( vars, log ): + """ + Load the kernel off of a node and boot to it. + This step assumes the disks are mounted on SYSIMG_PATH. + + Expect the following variables: + BOOT_CD_VERSION A tuple of the current bootcd version + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + ROOT_MOUNTED the node root file system is mounted + + Sets the following variables: + ROOT_MOUNTED the node root file system is mounted + """ + + log.write( "\n\nStep: Chain booting node.\n" ) + + # make sure we have the variables we need + try: + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + ROOT_MOUNTED= 0 + if 'ROOT_MOUNTED' in vars.keys(): + ROOT_MOUNTED= vars['ROOT_MOUNTED'] + + if ROOT_MOUNTED == 0: + log.write( "Mounting node partitions\n" ) + + # old cds need extra utilities to run lvm + if BOOT_CD_VERSION[0] == 2: + compatibility.setup_lvm_2x_cd( vars, log ) + + # simply creating an instance of this class and listing the system + # block devices will make them show up so vgscan can find the planetlab + # volume group + systeminfo().get_block_device_list() + + utils.sysexec( "vgscan", log ) + utils.sysexec( "vgchange -ay planetlab", log ) + + utils.makedirs( SYSIMG_PATH ) + + utils.sysexec( "mount /dev/planetlab/root %s" % SYSIMG_PATH, log ) + utils.sysexec( "mount /dev/planetlab/vservers %s/vservers" % + SYSIMG_PATH, log ) + + ROOT_MOUNTED= 1 + vars['ROOT_MOUNTED']= 1 + + + node_update_cmd= "/usr/local/planetlab/bin/NodeUpdate.py start noreboot" + + log.write( "Running node update.\n" ) + utils.sysexec( "chroot %s %s" % (SYSIMG_PATH,node_update_cmd), log ) + + log.write( "Updating ssh public host key with PLC.\n" ) + ssh_host_key= "" + try: + ssh_host_key_file= file("%s/etc/ssh/ssh_host_rsa_key.pub"%SYSIMG_PATH,"r") + ssh_host_key= ssh_host_key_file.read().strip() + ssh_host_key_file.close() + ssh_host_key_file= None + except IOError, e: + pass + + update_vals= {} + update_vals['ssh_host_key']= ssh_host_key + BootAPI.call_api_function( vars, "BootUpdateNode", (update_vals,) ) + + + log.write( "Copying kernel and initrd for booting.\n" ) + utils.sysexec( "cp %s/boot/kernel-boot /tmp/kernel" % SYSIMG_PATH, log ) + utils.sysexec( "cp %s/boot/initrd-boot /tmp/initrd" % SYSIMG_PATH, log ) + + log.write( "Unmounting disks.\n" ) + utils.sysexec_noerr( "chroot %s umount /rcfs" % SYSIMG_PATH, log ) + utils.sysexec_noerr( "umount -r /dev/planetlab/vservers", log ) + utils.sysexec_noerr( "umount -r /dev/planetlab/root", log ) + utils.sysexec_noerr( "vgchange -an", log ) + + ROOT_MOUNTED= 0 + vars['ROOT_MOUNTED']= 0 + + if BOOT_CD_VERSION[0] == 2: + log.write( "Unloading modules and chaining booting to new kernel.\n" ) + else: + log.write( "Chaining booting to new kernel.\n" ) + + # further use of log after Upload will only output to screen + log.Upload() + + # on 2.x cds (2.4 kernel) for sure, we need to shutdown everything to + # get kexec to work correctly + + utils.sysexec_noerr( "ifconfig eth0 down", log ) + + if BOOT_CD_VERSION[0] == 2: + utils.sysexec_noerr( "killall dhcpcd", log ) + elif BOOT_CD_VERSION[0] == 3: + utils.sysexec_noerr( "killall dhclient", log ) + + utils.sysexec_noerr( "umount -a -r -t ext2,ext3", log ) + utils.sysexec_noerr( "modprobe -r lvm-mod", log ) + + try: + modules= file("/tmp/loadedmodules","r") + + for line in modules: + module= string.strip(line) + if module != "": + utils.sysexec_noerr( "modprobe -r %s" % module, log ) + except IOError: + log.write( "Couldn't load /tmp/loadedmodules to unload, continuing.\n" ) + + utils.sysexec( "kexec --force --initrd=/tmp/initrd " \ + "--append=ramdisk_size=8192 /tmp/kernel" ) + + return diff --git a/source/steps/CheckForNewDisks.py b/source/steps/CheckForNewDisks.py new file mode 100644 index 0000000..51ed6a3 --- /dev/null +++ b/source/steps/CheckForNewDisks.py @@ -0,0 +1,161 @@ +import string + +import InstallPartitionDisks +from Exceptions import * +from systeminfo import systeminfo +import compatibility +import utils + + +def Run( vars, log ): + """ + Find any new large block devices we can add to the vservers volume group + + Expect the following variables to be set: + SYSIMG_PATH the path where the system image will be mounted + BOOT_CD_VERSION A tuple of the current bootcd version + MINIMUM_DISK_SIZE any disks smaller than this size, in GB, are not used + + Set the following variables upon successfully running: + ROOT_MOUNTED the node root file system is mounted + """ + + log.write( "\n\nStep: Checking for unused disks to add to LVM.\n" ) + + # make sure we have the variables we need + try: + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + MINIMUM_DISK_SIZE= int(vars["MINIMUM_DISK_SIZE"]) + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + sysinfo= systeminfo() + + all_devices= sysinfo.get_block_device_list() + + # find out if there are unused disks in all_devices that are greater + # than old cds need extra utilities to run lvm + if BOOT_CD_VERSION[0] == 2: + compatibility.setup_lvm_2x_cd( vars, log ) + + # will contain the new devices to add to the volume group + new_devices= [] + + # total amount of new space in gb + extended_gb_size= 0 + + for device in all_devices.keys(): + + (major,minor,blocks,gb_size,readonly)= all_devices[device] + + if device[:14] == "/dev/planetlab": + log.write( "Skipping device %s in volume group.\n" % device ) + continue + + if readonly: + log.write( "Skipping read only device %s\n" % device ) + continue + + if gb_size < MINIMUM_DISK_SIZE: + log.write( "Skipping too small device %s (%4.2f)\n" % + (device,gb_size) ) + continue + + log.write( "Checking device %s to see if it is part " \ + "of the volume group.\n" % device ) + + # this is the lvm partition, if it exists on that device + lvm_partition= "%s1" % device + already_added= utils.sysexec_noerr( "pvdisplay %s | grep -q 'planetlab'" % + lvm_partition ) + + if already_added: + log.write( "It appears %s is part of the volume group, continuing.\n" % + device ) + continue + + # just to be extra paranoid, ignore the device if it already has + # an lvm partition on it (new disks won't have this, and that is + # what this code is for, so it should be ok). + has_lvm= utils.sysexec_noerr( "sfdisk -l %s | grep -q 'Linux LVM'" % + device ) + if has_lvm: + log.write( "It appears %s has/had lvm already setup on "\ + "it, continuing.\n" % device ) + continue + + + log.write( "Attempting to add %s to the volume group\n" % device ) + + if not InstallPartitionDisks.single_partition_device( device, vars, log ): + log.write( "Unable to partition %s, not using it.\n" % device ) + continue + + log.write( "Successfully initialized %s\n" % device ) + + part_path= InstallPartitionDisks.get_partition_path_from_device( device, + vars, log ) + if not InstallPartitionDisks.create_lvm_physical_volume( part_path, + vars, log ): + log.write( "Unable to create lvm physical volume %s, not using it.\n" % + part_path ) + continue + + log.write( "Adding %s to list of devices to add to " \ + "planetlab volume group.\n" % device ) + + extended_gb_size= extended_gb_size + gb_size + new_devices.append( part_path ) + + + if len(new_devices) > 0: + + log.write( "Extending planetlab volume group.\n" ) + + log.write( "Unmounting disks.\n" ) + utils.sysexec_noerr( "chroot %s umount /rcfs" % SYSIMG_PATH, log ) + utils.sysexec_noerr( "umount /dev/planetlab/vservers", log ) + utils.sysexec_noerr( "umount /dev/planetlab/root", log ) + utils.sysexec( "vgchange -an", log ) + + vars['ROOT_MOUNTED']= 0 + + if not utils.sysexec_noerr( "vgextend planetlab %s" % + string.join(new_devices," "), log ): + log.write( "Failed to add physical volumes %s to " \ + "volume group, continuing.\n" % string.join(new_devices," ")) + return 1 + + # now, get the number of unused extents, and extend the vserver + # logical volume by that much. + remaining_extents= \ + InstallPartitionDisks.get_remaining_extents_on_vg( vars, log ) + + log.write( "Extending vservers logical volume.\n" ) + + if not utils.sysexec_noerr("lvextend -l +%s /dev/planetlab/vservers" % + remaining_extents, log): + log.write( "Failed to extend vservers logical volume, continuing\n" ) + return 1 + + log.write( "making the ext3 filesystem match new logical volume size.\n" ) + if not utils.sysexec_noerr("resize2fs /dev/planetlab/vservers",log): + log.write( "Failed to make ext3 file system match, continuing\n" ) + return 1 + + log.write( "Succesfully extended vservers partition by %4.2f GB\n" % + extended_gb_size ) + else: + log.write( "No new disk devices to add to volume group.\n" ) + + return 1 diff --git a/source/steps/CheckHardwareRequirements.py b/source/steps/CheckHardwareRequirements.py new file mode 100644 index 0000000..1344161 --- /dev/null +++ b/source/steps/CheckHardwareRequirements.py @@ -0,0 +1,293 @@ +# 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. + + +import os +import popen2 +import string + +from systeminfo import systeminfo +from Exceptions import * +import utils +import notify_messages +import BootAPI + + +def Run( vars, log ): + """ + Make sure the hardware we are running on is sufficient for + the PlanetLab OS to be installed on. In the process, identify + the list of block devices that may be used for a node installation, + and identify the cdrom device that we booted off of. + + Return 1 if requiremenst met, 0 if requirements not met. Raise + BootManagerException if any problems occur that prevent the requirements + from being checked. + + Expect the following variables from the store: + + MINIMUM_MEMORY minimum amount of memory in kb required + for install + NODE_ID the node_id from the database for this node + MINIMUM_DISK_SIZE any disks smaller than this size, in GB, are not used + TOTAL_MINIMUM_DISK_SIZE total disk size in GB, if all usable disks + meet this number, there isn't enough disk space for + this node to be usable after install + SKIP_HARDWARE_REQUIREMENT_CHECK + If set, don't check if minimum requirements are met + BOOT_CD_VERSION A tuple of the current bootcd version + Sets the following variables: + INSTALL_BLOCK_DEVICES list of block devices to install onto + """ + + log.write( "\n\nStep: Checking if hardware requirements met.\n" ) + + try: + MINIMUM_MEMORY= int(vars["MINIMUM_MEMORY"]) + if MINIMUM_MEMORY == "": + raise ValueError, "MINIMUM_MEMORY" + + NODE_ID= vars["NODE_ID"] + if NODE_ID == "": + raise ValueError("NODE_ID") + + MINIMUM_DISK_SIZE= int(vars["MINIMUM_DISK_SIZE"]) + + TOTAL_MINIMUM_DISK_SIZE= \ + int(vars["TOTAL_MINIMUM_DISK_SIZE"]) + + SKIP_HARDWARE_REQUIREMENT_CHECK= \ + int(vars["SKIP_HARDWARE_REQUIREMENT_CHECK"]) + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + except KeyError, var: + raise BootManagerException, \ + "Missing variable in install store: %s" % var + except ValueError, var: + raise BootManagerException, \ + "Variable in install store blank, shouldn't be: %s" % var + + sysinfo= systeminfo() + + # lets see if we have enough memory to run + log.write( "Checking for available memory.\n" ) + + total_mem= sysinfo.get_total_phsyical_mem() + if total_mem is None: + raise BootManagerException, "Unable to read total physical memory" + + if total_mem < MINIMUM_MEMORY: + if not SKIP_HARDWARE_REQUIREMENT_CHECK: + log.write( "Insufficient memory to run node: %s kb\n" % total_mem ) + log.write( "Required memory: %s kb\n" % MINIMUM_MEMORY ) + + include_pis= 0 + include_techs= 1 + include_support= 0 + + sent= 0 + try: + sent= BootAPI.call_api_function( vars, "BootNotifyOwners", + (notify_messages.MSG_INSUFFICIENT_MEMORY, + include_pis, + include_techs, + include_support) ) + except BootManagerException, e: + log.write( "Call to BootNotifyOwners failed: %s.\n" % e ) + + if sent == 0: + log.write( "Unable to notify site contacts of problem.\n" ) + else: + log.write( "Notified contacts of problem.\n" ) + + return 0 + else: + log.write( "Memory requirements not met, but running anyway: %s kb\n" + % total_mem ) + else: + log.write( "Looks like we have enough memory: %s kb\n" % total_mem ) + + + + # get a list of block devices to attempt to install on + # (may include cdrom devices) + install_devices= sysinfo.get_block_device_list() + + # save the list of block devices in the log + log.write( "Detected block devices:\n" ) + log.write( repr(install_devices) + "\n" ) + + if not install_devices or len(install_devices) == 0: + log.write( "No block devices detected.\n" ) + + include_pis= 0 + include_techs= 1 + include_support= 0 + + sent= 0 + try: + sent= BootAPI.call_api_function( vars, "BootNotifyOwners", + (notify_messages.MSG_INSUFFICIENT_DISK, + include_pis, + include_techs, + include_support) ) + except BootManagerException, e: + log.write( "Call to BootNotifyOwners failed: %s.\n" % e ) + + if sent == 0: + log.write( "Unable to notify site contacts of problem.\n" ) + + return 0 + + # now, lets remove any block devices we know won't work (readonly,cdroms), + # or could be other writable removable disks (usb keychains, zip disks, etc) + # i'm not aware of anything that helps with the latter test, so, + # what we'll probably do is simply not use any block device below + # some size threshold (set in installstore) + + # also, keep track of the total size for all devices that appear usable + total_size= 0 + + for device in install_devices.keys(): + + (major,minor,blocks,gb_size,readonly)= install_devices[device] + + # if the device string starts with + # planetlab, ignore it (could be old lvm setup) + if device[:14] == "/dev/planetlab": + del install_devices[device] + continue + + if gb_size < MINIMUM_DISK_SIZE: + log.write( "Device is too small to use: %s \n(appears" \ + " to be %4.2f GB)\n" % (device,gb_size) ) + try: + del install_devices[device] + except KeyError, e: + pass + continue + + if readonly: + log.write( "Device is readonly, not using: %s\n" % device ) + try: + del install_devices[device] + except KeyError, e: + pass + continue + + # add this sector count to the total count of usable + # sectors we've found. + total_size= total_size + gb_size + + + if len(install_devices) == 0: + log.write( "No suitable block devices found for install.\n" ) + + include_pis= 0 + include_techs= 1 + include_support= 0 + + sent= 0 + try: + sent= BootAPI.call_api_function( vars, "BootNotifyOwners", + (notify_messages.MSG_INSUFFICIENT_DISK, + include_pis, + include_techs, + include_support) ) + except BootManagerException, e: + log.write( "Call to BootNotifyOwners failed: %s.\n" % e ) + + if sent == 0: + log.write( "Unable to notify site contacts of problem.\n" ) + + return 0 + + + # show the devices we found that are usable + log.write( "Usable block devices:\n" ) + log.write( repr(install_devices.keys()) + "\n" ) + + # save the list of devices for the following steps + vars["INSTALL_BLOCK_DEVICES"]= install_devices.keys() + + + # ensure the total disk size is large enough. if + # not, we need to email the tech contacts the problem, and + # put the node into debug mode. + if total_size < TOTAL_MINIMUM_DISK_SIZE: + if not SKIP_HARDWARE_REQUIREMENT_CHECK: + log.write( "The total usable disk size of all disks is " \ + "insufficient to be usable as a PlanetLab node.\n" ) + include_pis= 0 + include_techs= 1 + include_support= 0 + + sent= 0 + try: + sent= BootAPI.call_api_function( vars, "BootNotifyOwners", + (notify_messages.MSG_INSUFFICIENT_DISK, + include_pis, + include_techs, + include_support) ) + except BootManagerException, e: + log.write( "Call to BootNotifyOwners failed: %s.\n" % e ) + + if sent == 0: + log.write( "Unable to notify site contacts of problem.\n" ) + + return 0 + + else: + log.write( "The total usable disk size of all disks is " \ + "insufficient, but running anyway.\n" ) + + log.write( "Total size for all usable block devices: %4.2f GB\n" % total_size ) + + # turn off UDMA for all block devices on 2.x cds (2.4 kernel) + if BOOT_CD_VERSION[0] == 2: + for device in install_devices: + log.write( "Disabling UDMA on %s\n" % device ) + utils.sysexec_noerr( "/sbin/hdparm -d0 %s" % device, log ) + + return 1 diff --git a/source/steps/ConfirmInstallWithUser.py b/source/steps/ConfirmInstallWithUser.py new file mode 100644 index 0000000..3773ea9 --- /dev/null +++ b/source/steps/ConfirmInstallWithUser.py @@ -0,0 +1,60 @@ +from Exceptions import * + +welcome_message= \ +""" +******************************************************************************** +* * +* Welcome to PlanetLab * +* ~~~~~~~~~~~~~~~~~~~~ * +* * +* The PlanetLab boot CD allows you to automatically install this machine as a * +* node within the PlanetLab overlay network. * +* * +* PlanetLab is a global overlay network for developing and accessing new * +* network services. Our goal is to grow to 1000 geographically distributed * +* nodes, connected by a diverse collection of links. Toward this end, we are * +* putting PlanetLab nodes into edge sites, co-location and routing centers, * +* and homes (i.e., at the end of DSL lines and cable modems). PlanetLab is * +* designed to support both short-term experiments and long-running services. * +* Currently running services include network weather maps, network-embedded * +* storage, peer-to-peer networks, and content distribution networks. * +* * +* Information on joining PlanetLab available at planet-lab.org/consortium/ * +* * +******************************************************************************** + +WARNING : Installing PlanetLab will remove any existing operating system and + data from this computer. +""" + + +def Run( vars, log ): + """ + Ask the user if we really want to wipe this machine. + + Return 1 if the user accept, 0 if the user denied, and + a BootManagerException if anything unexpected occurred. + """ + + log.write( "\n\nStep: Confirming install with user.\n" ) + + try: + confirmation= "" + install= 0 + print welcome_message + + while confirmation not in ("yes","no"): + confirmation= \ + raw_input("Are you sure you wish to continue (yes/no):") + install= confirmation=="yes" + except EOFError, e: + pass + except KeyboardInterrupt, e: + pass + + if install: + log.write( "\nUser accepted install.\n" ) + else: + log.write( "\nUser canceled install.\n" ) + + return install diff --git a/source/steps/GetAndUpdateNodeDetails.py b/source/steps/GetAndUpdateNodeDetails.py new file mode 100644 index 0000000..48416fb --- /dev/null +++ b/source/steps/GetAndUpdateNodeDetails.py @@ -0,0 +1,122 @@ +import string + +from Exceptions import * +import BootAPI + + +SKIP_MODEL_STRING= "/minhw" + + +def Run( vars, log ): + """ + Contact PLC and get the attributes for this node. Also, if the node model + string ends in SKIP_MODEL_STRING, override the hardware requirements + configuration value, and skip the checks. + + Also, update any node network settings at PLC, minus the ip address, + so, upload the mac (if node_id was in conf file), gateway, network, + broadcast, netmask, dns1/2, and the hostname/domainname. + + Expect the following keys to be set: + BOOT_CD_VERSION A tuple of the current bootcd version + SKIP_HARDWARE_REQUIREMENT_CHECK Whether or not we should skip hardware + requirement checks + + The following keys are set/updated: + WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file + WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file + BOOT_STATE The current node boot state + NODE_MODEL The user specified model of this node + NETWORK_SETTINGS A dictionary of the values of the network settings + SKIP_HARDWARE_REQUIREMENT_CHECK Whether or not we should skip hardware + requirement checks + + Return 1 if able to contact PLC and get node info. + Raise a BootManagerException if anything fails. + """ + + log.write( "\n\nStep: Retrieving details of node from PLC.\n" ) + + # make sure we have the variables we need + try: + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + SKIP_HARDWARE_REQUIREMENT_CHECK= vars["SKIP_HARDWARE_REQUIREMENT_CHECK"] + if SKIP_HARDWARE_REQUIREMENT_CHECK == "": + raise ValueError, "SKIP_HARDWARE_REQUIREMENT_CHECK" + + NETWORK_SETTINGS= vars["NETWORK_SETTINGS"] + if NETWORK_SETTINGS == "": + raise ValueError, "NETWORK_SETTINGS" + + WAS_NODE_ID_IN_CONF= vars["WAS_NODE_ID_IN_CONF"] + if WAS_NODE_ID_IN_CONF == "": + raise ValueError, "WAS_NODE_ID_IN_CONF" + + WAS_NODE_KEY_IN_CONF= vars["WAS_NODE_KEY_IN_CONF"] + if WAS_NODE_KEY_IN_CONF == "": + raise ValueError, "WAS_NODE_KEY_IN_CONF" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + details= BootAPI.call_api_function( vars, "BootGetNodeDetails", () ) + + vars['BOOT_STATE']= details['boot_state'] + vars['NODE_MODEL']= string.strip(details['model']) + + log.write( "Successfully retrieved node record.\n" ) + log.write( "Current boot state: %s\n" % vars['BOOT_STATE'] ) + log.write( "Node make/model: %s\n" % vars['NODE_MODEL'] ) + + # if the end of NODE_MODEL string ends in SKIP_MODEL_STRING, skip hardware + # requirement checks (overrides configuration) + model= vars['NODE_MODEL'] + len_skip_str= len(SKIP_MODEL_STRING) + if model[-len_skip_str:] == SKIP_MODEL_STRING: + vars['SKIP_HARDWARE_REQUIREMENT_CHECK']= 1 + log.write( "node model indicates override to hardware requirements.\n" ) + + + # this contains all the node networks, for now, we are only concerned + # in the primary network + node_networks= details['networks'] + got_primary= 0 + for network in node_networks: + if network['is_primary'] == 1: + got_primary= 1 + break + + if not got_primary: + raise BootManagerException, "Node did not have a primary network." + + log.write( "Primary network as returned from PLC: %s\n" % str(network) ) + + # if we got this far, the ip on the floppy and the ip in plc match, + # make the rest of the PLC information match whats on the floppy + network['method']= NETWORK_SETTINGS['method'] + + # only nodes that have the node_id specified directly in the configuration + # file can change their mac address + if BOOT_CD_VERSION[0] == 3 and WAS_NODE_ID_IN_CONF == 1: + network['mac']= NETWORK_SETTINGS['mac'] + + network['gateway']= NETWORK_SETTINGS['gateway'] + network['network']= NETWORK_SETTINGS['network'] + network['broadcast']= NETWORK_SETTINGS['broadcast'] + network['netmask']= NETWORK_SETTINGS['netmask'] + network['dns1']= NETWORK_SETTINGS['dns1'] + network['dns2']= NETWORK_SETTINGS['dns2'] + + log.write( "Updating network settings at PLC to match floppy " \ + "(except for node ip).\n" ) + update_vals= {} + update_vals['primary_network']= network + BootAPI.call_api_function( vars, "BootUpdateNode", (update_vals,) ) + + return 1 diff --git a/source/steps/InitializeBootManager.py b/source/steps/InitializeBootManager.py new file mode 100644 index 0000000..c129d63 --- /dev/null +++ b/source/steps/InitializeBootManager.py @@ -0,0 +1,439 @@ +import os +import xmlrpclib +import socket +import string + +from Exceptions import * +import utils + + +# locations of boot os version files +BOOT_VERSION_2X_FILE='/usr/bootme/ID' +BOOT_VERSION_3X_FILE='/pl_version' + +# minimium version of the boot os we need to run, as a (major,minor) tuple +MINIMUM_BOOT_VERSION= (2,0) + +# minimum version of python required to run the boot manager +MINIMUM_PYTHON_VERSION= (2,2,0) + + +def Run( vars, log ): + """ + Setup the boot manager so it can run, do any extra necessary + hardware setup (to fix old cd problems) + + Sets the following variables: + BOOT_CD_VERSION A two number tuple of the boot cd version + """ + + log.write( "\n\nStep: Initializing the BootManager.\n" ) + + + log.write( "Opening connection to API server\n" ) + try: + api_inst= xmlrpclib.Server( vars['BOOT_API_SERVER'], verbose=0 ) + except KeyError, e: + raise BootManagerException, \ + "configuration file does not specify API server URL" + + vars['API_SERVER_INST']= api_inst + + if not __check_boot_version( vars, log ): + raise BootManagerException, \ + "Boot CD version insufficient to run the Boot Manager" + else: + log.write( "Running on boot cd version: %s\n" % + str(vars['BOOT_CD_VERSION']) ) + + BOOT_CD_VERSION= vars['BOOT_CD_VERSION'] + + # old cds need extra modules loaded for compaq smart array + if BOOT_CD_VERSION[0] == 2: + + has_smartarray= utils.sysexec_noerr( + 'lspci | egrep "0e11:b178|0e11:4070|0e11:4080|0e11:4082|0e11:4083"') + + if has_smartarray: + log.write( "Loading support for Compaq smart array\n" ) + utils.sysexec_noerr( "modprobe cciss", log ) + _create_cciss_dev_entries() + + + has_fusion= utils.sysexec_noerr('lspci | egrep "1000:0030"') + + if has_fusion: + log.write( "Loading support for Fusion MPT SCSI controllers\n" ) + utils.sysexec_noerr( "modprobe mptscsih", log ) + + # for anything that needs to know we are running under the boot cd and + # not the runtime os + os.environ['PL_BOOTCD']= "1" + + return 1 + + + +def __check_boot_version( vars, log ): + """ + identify which version of the boot os we are running on, and whether + or not we can run at all on the given version. later, this will be + used to identify extra packages to download to enable the boot manager + to run on any supported version. + + 2.x cds have the version file in /usr/bootme/ID, which looked like: + 'PlanetLab BootCD v2.0.3' + + 3.x cds have the version file in /pl_version, which lookes like: + 'PlanetLab BootCD 3.0-beta0.3' + + All current known version strings that we support: + PlanetLab BootCD 3.0 + PlanetLab BootCD 3.0-beta0.1 + PlanetLab BootCD 3.0-beta0.3 + PlanetLab BootCD v2.0 + PlanetLab BootCD v2.0.1 + PlanetLab BootCD v2.0.2 + PlanetLab BootCD v2.0.3 + + Returns 1 if the boot os version is identified and will work + to run the boot manager. Two class variables are set: + BOOT_OS_MAJOR_VERSION + BOOT_OS_MINOR_VERSION + version strings with three parts parts to the version ignore the + middle number (so 2.0.3 is major 2, minor 3) + + Returns 0 if the boot os is insufficient to run the boot manager + """ + + try: + # check for a 3.x version first + version_file= file(BOOT_VERSION_3X_FILE,'r') + full_version= string.strip(version_file.read()) + version_file.close() + + version_parts= string.split(full_version) + version= version_parts[-1] + + version_numbers= string.split(version,".") + if len(version_numbers) == 2: + BOOT_OS_MAJOR_VERSION= int(version_numbers[0]) + BOOT_OS_MINOR_VERSION= int(version_numbers[1]) + else: + # for 3.x cds, if there are more than two parts + # separated by a ., its one of the beta cds. + # hardcode as a 3.0 cd + BOOT_OS_MAJOR_VERSION= 3 + BOOT_OS_MINOR_VERSION= 0 + + vars['BOOT_CD_VERSION']= (BOOT_OS_MAJOR_VERSION,BOOT_OS_MINOR_VERSION) + + if (BOOT_OS_MAJOR_VERSION,BOOT_OS_MINOR_VERSION) >= \ + MINIMUM_BOOT_VERSION: + return 1 + + except IOError, e: + pass + except IndexError, e: + pass + except TypeError, e: + pass + + + try: + # check for a 2.x version first + version_file= file(BOOT_VERSION_2X_FILE,'r') + full_version= string.strip(version_file.read()) + version_file.close() + + version_parts= string.split(full_version) + version= version_parts[-1] + if version[0] == 'v': + version= version[1:] + + version_numbers= string.split(version,".") + if len(version_numbers) == 2: + BOOT_OS_MAJOR_VERSION= int(version_numbers[0]) + BOOT_OS_MINOR_VERSION= int(version_numbers[1]) + else: + BOOT_OS_MAJOR_VERSION= int(version_numbers[0]) + BOOT_OS_MINOR_VERSION= int(version_numbers[2]) + + vars['BOOT_CD_VERSION']= (BOOT_OS_MAJOR_VERSION,BOOT_OS_MINOR_VERSION) + + if (BOOT_OS_MAJOR_VERSION,BOOT_OS_MINOR_VERSION) >= \ + MINIMUM_BOOT_VERSION: + return 1 + + except IOError, e: + pass + except IndexError, e: + pass + except TypeError, e: + pass + + + return 0 + + + +def _create_cciss_dev_entries(): + utils.sysexec_noerr( "mknod /dev/cciss/c0d0 b 104 0" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p1 b 104 1" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p2 b 104 2" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p3 b 104 3" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p4 b 104 4" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p5 b 104 5" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p6 b 104 6" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p7 b 104 7" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p8 b 104 8" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p9 b 104 9" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p10 b 104 10" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p11 b 104 11" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p12 b 104 12" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p13 b 104 13" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p14 b 104 14" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d0p15 b 104 15" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1 b 104 16" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p1 b 104 17" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p2 b 104 18" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p3 b 104 19" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p4 b 104 20" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p5 b 104 21" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p6 b 104 22" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p7 b 104 23" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p8 b 104 24" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p9 b 104 25" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p10 b 104 26" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p11 b 104 27" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p12 b 104 28" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p13 b 104 29" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p14 b 104 30" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d1p15 b 104 31" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2 b 104 32" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p1 b 104 33" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p2 b 104 34" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p3 b 104 35" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p4 b 104 36" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p5 b 104 37" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p6 b 104 38" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p7 b 104 39" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p8 b 104 40" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p9 b 104 41" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p10 b 104 42" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p11 b 104 43" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p12 b 104 44" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p13 b 104 45" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p14 b 104 46" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d2p15 b 104 47" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3 b 104 48" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p1 b 104 49" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p2 b 104 50" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p3 b 104 51" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p4 b 104 52" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p5 b 104 53" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p6 b 104 54" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p7 b 104 55" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p8 b 104 56" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p9 b 104 57" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p10 b 104 58" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p11 b 104 59" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p12 b 104 60" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p13 b 104 61" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p14 b 104 62" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d3p15 b 104 63" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4 b 104 64" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p1 b 104 65" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p2 b 104 66" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p3 b 104 67" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p4 b 104 68" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p5 b 104 69" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p6 b 104 70" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p7 b 104 71" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p8 b 104 72" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p9 b 104 73" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p10 b 104 74" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p11 b 104 75" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p12 b 104 76" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p13 b 104 77" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p14 b 104 78" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d4p15 b 104 79" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5 b 104 80" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p1 b 104 81" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p2 b 104 82" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p3 b 104 83" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p4 b 104 84" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p5 b 104 85" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p6 b 104 86" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p7 b 104 87" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p8 b 104 88" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p9 b 104 89" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p10 b 104 90" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p11 b 104 91" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p12 b 104 92" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p13 b 104 93" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p14 b 104 94" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d5p15 b 104 95" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6 b 104 96" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p1 b 104 97" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p2 b 104 98" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p3 b 104 99" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p4 b 104 100" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p5 b 104 101" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p6 b 104 102" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p7 b 104 103" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p8 b 104 104" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p9 b 104 105" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p10 b 104 106" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p11 b 104 107" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p12 b 104 108" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p13 b 104 109" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p14 b 104 110" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d6p15 b 104 111" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7 b 104 112" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p1 b 104 113" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p2 b 104 114" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p3 b 104 115" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p4 b 104 116" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p5 b 104 117" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p6 b 104 118" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p7 b 104 119" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p8 b 104 120" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p9 b 104 121" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p10 b 104 122" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p11 b 104 123" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p12 b 104 124" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p13 b 104 125" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p14 b 104 126" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d7p15 b 104 127" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8 b 104 128" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p1 b 104 129" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p2 b 104 130" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p3 b 104 131" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p4 b 104 132" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p5 b 104 133" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p6 b 104 134" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p7 b 104 135" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p8 b 104 136" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p9 b 104 137" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p10 b 104 138" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p11 b 104 139" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p12 b 104 140" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p13 b 104 141" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p14 b 104 142" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d8p15 b 104 143" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9 b 104 144" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p1 b 104 145" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p2 b 104 146" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p3 b 104 147" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p4 b 104 148" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p5 b 104 149" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p6 b 104 150" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p7 b 104 151" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p8 b 104 152" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p9 b 104 153" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p10 b 104 154" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p11 b 104 155" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p12 b 104 156" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p13 b 104 157" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p14 b 104 158" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d9p15 b 104 159" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10 b 104 160" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p1 b 104 161" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p2 b 104 162" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p3 b 104 163" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p4 b 104 164" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p5 b 104 165" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p6 b 104 166" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p7 b 104 167" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p8 b 104 168" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p9 b 104 169" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p10 b 104 170" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p11 b 104 171" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p12 b 104 172" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p13 b 104 173" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p14 b 104 174" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d10p15 b 104 175" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11 b 104 176" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p1 b 104 177" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p2 b 104 178" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p3 b 104 179" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p4 b 104 180" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p5 b 104 181" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p6 b 104 182" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p7 b 104 183" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p8 b 104 184" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p9 b 104 185" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p10 b 104 186" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p11 b 104 187" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p12 b 104 188" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p13 b 104 189" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p14 b 104 190" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d11p15 b 104 191" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12 b 104 192" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p1 b 104 193" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p2 b 104 194" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p3 b 104 195" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p4 b 104 196" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p5 b 104 197" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p6 b 104 198" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p7 b 104 199" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p8 b 104 200" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p9 b 104 201" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p10 b 104 202" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p11 b 104 203" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p12 b 104 204" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p13 b 104 205" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p14 b 104 206" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d12p15 b 104 207" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13 b 104 208" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p1 b 104 209" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p2 b 104 210" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p3 b 104 211" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p4 b 104 212" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p5 b 104 213" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p6 b 104 214" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p7 b 104 215" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p8 b 104 216" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p9 b 104 217" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p10 b 104 218" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p11 b 104 219" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p12 b 104 220" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p13 b 104 221" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p14 b 104 222" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d13p15 b 104 223" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14 b 104 224" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p1 b 104 225" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p2 b 104 226" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p3 b 104 227" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p4 b 104 228" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p5 b 104 229" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p6 b 104 230" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p7 b 104 231" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p8 b 104 232" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p9 b 104 233" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p10 b 104 234" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p11 b 104 235" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p12 b 104 236" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p13 b 104 237" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p14 b 104 238" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d14p15 b 104 239" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15 b 104 240" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p1 b 104 241" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p2 b 104 242" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p3 b 104 243" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p4 b 104 244" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p5 b 104 245" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p6 b 104 246" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p7 b 104 247" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p8 b 104 248" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p9 b 104 249" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p10 b 104 250" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p11 b 104 251" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p12 b 104 252" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p13 b 104 253" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p14 b 104 254" ) + utils.sysexec_noerr( "mknod /dev/cciss/c0d15p15 b 104 255" ) + + + diff --git a/source/steps/InstallBase.py b/source/steps/InstallBase.py new file mode 100644 index 0000000..54d5735 --- /dev/null +++ b/source/steps/InstallBase.py @@ -0,0 +1,115 @@ +# 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. + + +import os + +from Exceptions import * +import utils + + +def Run( vars, log ): + """ + Install the base system group of RPMs + + Except the following variables from the store: + TEMP_PATH the path to download and store temp files to + CACERT_PATH path where the cacerts on the bootcd live + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + INSTALL_LANGS languages for install (used by rpm) + + Sets the following variables: + None + + """ + + log.write( "\n\nStep: Install: Installing base OS.\n" ) + + # make sure we have the variables we need + try: + TEMP_PATH= vars["TEMP_PATH"] + if TEMP_PATH == "": + raise ValueError, "TEMP_PATH" + + CACERT_PATH= vars["CACERT_PATH"] + if CACERT_PATH == "": + raise ValueError, "CACERT_PATH" + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + INSTALL_LANGS= vars["INSTALL_LANGS"] + if INSTALL_LANGS == "": + raise ValueError, "INSTALL_LANGS" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + # this will prevent all the locales from being installed + log.write( "Setting up RPM configuration in sysimg\n" ) + utils.makedirs( "%s/etc/rpm" % SYSIMG_PATH ) + rpm_macros= file( "%s/etc/rpm/macros" % SYSIMG_PATH, "w" ) + rpm_macros.write( "%%_install_langs %s\n" % INSTALL_LANGS ) + rpm_macros.write( "%_excludedocs 1\n" ) + rpm_macros.write( "%__file_context_path /dev/null\n" ) + rpm_macros.close() + rpm_macros= None + + log.write( "Running base group install using yum\n" ) + + # groupupdate instead of groupinstall since BootstrapRPM + # already installed a pre-configured RPM + # database. 'PlanetLab' is now a subset of Fedora Core 2 + # Core/Base plus PlanetLab specific RPMs, so that we don't + # have to run yum multiple times. The group is called + # 'PlanetLab' for backward compatibility with NodeUpdate, + # which is hard-coded to update the 'PlanetLab' group. + yum_cmd= "chroot %s yum --sslcertdir %s -y groupupdate 'PlanetLab'" % \ + (SYSIMG_PATH, CACERT_PATH) + + utils.sysexec( yum_cmd, log ); + + return 1 diff --git a/source/steps/InstallBootstrapRPM.py b/source/steps/InstallBootstrapRPM.py new file mode 100644 index 0000000..6d3e22d --- /dev/null +++ b/source/steps/InstallBootstrapRPM.py @@ -0,0 +1,211 @@ +# 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. + + + +import os, sys, string +import popen2 + +from Exceptions import * +import utils +import BootServerRequest + + +def Run( vars, log ): + """ + Download enough files to run rpm and yum from a chroot in + the system image directory + + Expect the following variables from the store: + SYSIMG_PATH the path where the system image will be mounted + PARTITIONS dictionary of generic part. types (root/swap) + and their associated devices. + ALPINA_SERVER_DIR directory on the boot servers containing alpina + scripts and support files + INSTALL_LANGS languages for install (used by rpm) + NODE_ID the id of this machine + + Sets the following variables: + TEMP_BOOTCD_PATH where the boot cd is remounted in the temp + path + """ + + log.write( "\n\nStep: Install: Bootstrapping RPM.\n" ) + + # make sure we have the variables we need + try: + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + PARTITIONS= vars["PARTITIONS"] + if PARTITIONS == None: + raise ValueError, "PARTITIONS" + + ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"] + if ALPINA_SERVER_DIR == None: + raise ValueError, "ALPINA_SERVER_DIR" + + INSTALL_LANGS= vars["INSTALL_LANGS"] + if INSTALL_LANGS == "": + raise ValueError, "INSTALL_LANGS" + + NODE_ID= vars["NODE_ID"] + if NODE_ID == "": + raise ValueError, "NODE_ID" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + try: + # make sure the required partitions exist + val= PARTITIONS["root"] + val= PARTITIONS["swap"] + val= PARTITIONS["vservers"] + except KeyError, part: + log.write( "Missing partition in PARTITIONS: %s\n" % part ) + return 0 + + bs_request= BootServerRequest.BootServerRequest() + + log.write( "turning on swap space\n" ) + utils.sysexec( "swapon %s" % PARTITIONS["swap"], log ) + + # make sure the sysimg dir is present + utils.makedirs( SYSIMG_PATH ) + + log.write( "mounting root file system\n" ) + utils.sysexec( "mount -t ext3 %s %s" % (PARTITIONS["root"],SYSIMG_PATH), log ) + + log.write( "mounting vserver partition in root file system\n" ) + utils.makedirs( SYSIMG_PATH + "/vservers" ) + utils.sysexec( "mount -t ext3 %s %s/vservers" % (PARTITIONS["vservers"], + SYSIMG_PATH), log ) + + # download and extract support tarball for + # this step, which has everything + # we need to successfully run + step_support_file= "alpina-BootstrapRPM.tar.bz2" + source_file= "%s/%s" % (ALPINA_SERVER_DIR,step_support_file) + dest_file= "%s/%s" % (SYSIMG_PATH, step_support_file) + + # 30 is the connect timeout, 7200 is the max transfer time + # in seconds (2 hours) + log.write( "downloading %s\n" % step_support_file ) + result= bs_request.DownloadFile( source_file, None, None, + 1, 1, dest_file, + 30, 7200) + if not result: + raise BootManagerException, "Unable to download %s from server." % \ + source_file + + log.write( "extracting %s in %s\n" % (dest_file,SYSIMG_PATH) ) + result= utils.sysexec( "tar -C %s -xpjf %s" % (SYSIMG_PATH,dest_file), log ) + utils.removefile( dest_file ) + + # get the yum configuration file for this node (yum.conf). + # this needs to come from the configuration file service, + # so, if its a beta node, it'll install the beta rpms from + # the beginning. The configuration file service will return + # the url for the file we need to request to get the actual + # conf file, so two requests need to be made. + + # the only changes we will need to make to it are to change + # the cache and log directories, so when we run yum from + # the chrooted tempfs mount, it'll cache the rpms on the + # sysimg partition + + log.write( "Fetching URL for yum.conf from configuration file service\n" ) + + postVars= {"node_id" : NODE_ID, + "file" : "/etc/yum.conf"} + + yum_conf_url_file= "/tmp/yumconf.url" + + result= bs_request.DownloadFile( + "/db/plnodeconf/getsinglefile.php", + None, postVars, 1, 1, yum_conf_url_file) + + if result == 0: + log.write( "Unable to make request to get url for yum.conf\n" ) + return 0 + + try: + yum_conf_url= file(yum_conf_url_file,"r").read() + yum_conf_url= string.strip(yum_conf_url) + if yum_conf_url == "": + raise BootManagerException, \ + "Downloaded yum configuration file URL is empty." + except IOError: + raise BootManagerException, \ + "Unable to open downloaded yum configuration file URL." + + # now, get the actual contents of yum.conf for this node + log.write( "Fetching yum.conf contents from configuration file service\n" ) + + postVars= {} + download_file_loc= "%s/etc/yum.conf" % SYSIMG_PATH + + result= bs_request.DownloadFile( yum_conf_url, + None, postVars, 1, 1, + download_file_loc) + + if result == 0: + log.write( "Unable to make request to get yum.conf\n" ) + return 0 + + # copy resolv.conf from the base system into our temp dir + # so DNS lookups work correctly while we are chrooted + log.write( "Copying resolv.conf to temp dir\n" ) + utils.sysexec( "cp /etc/resolv.conf %s/etc/" % SYSIMG_PATH, log ) + + # mount the boot cd in the temp path, under /mnt/cdrom. this way, + # we can use the certs when programs are running + # chrooted in the temp path + cdrom_mount_point= "%s/mnt/cdrom" % SYSIMG_PATH + utils.makedirs( cdrom_mount_point ) + log.write( "Copying contents of /usr/bootme to /mnt/cdrom\n" ) + utils.sysexec( "cp -r /usr/bootme %s/mnt/cdrom/" % SYSIMG_PATH, log ) + + return 1 diff --git a/source/steps/InstallBuildVServer.py b/source/steps/InstallBuildVServer.py new file mode 100644 index 0000000..1f63179 --- /dev/null +++ b/source/steps/InstallBuildVServer.py @@ -0,0 +1,189 @@ +# 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. + + +import os +import string + +from Exceptions import * +import utils + + +# if this file is present in the vservers /etc directory, +# the resolv.conf and hosts files will automatically be updated +# by the bootmanager +UPDATE_FILE_FLAG= "AUTO_UPDATE_NET_FILES" + +# the name of the vserver-reference directory +VSERVER_REFERENCE_DIR_NAME='vserver-reference' + + +def Run( vars, log ): + """ + Setup directories for building vserver reference image. + + Except the following variables from the store: + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + NETWORK_SETTINGS A dictionary of the values from the network + configuration file + + Sets the following variables: + None + + """ + + log.write( "\n\nStep: Install: Setting up VServer image.\n" ) + + # make sure we have the variables we need + try: + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + NETWORK_SETTINGS= vars["NETWORK_SETTINGS"] + if NETWORK_SETTINGS == "": + raise ValueError, "NETWORK_SETTINGS" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + vserver_ref_dir= "/vservers/vserver-reference" + full_vserver_ref_path= "%s/%s" % (SYSIMG_PATH,vserver_ref_dir) + + utils.makedirs( full_vserver_ref_path ) + utils.makedirs( "%s/etc" % full_vserver_ref_path ) + + log.write( "Setting permissions on directories\n" ) + utils.sysexec( "chmod 0000 %s/vservers/" % SYSIMG_PATH, log ) + + return 1 + + + +def update_vserver_network_files( vserver_dir, vars, log ): + """ + Update the /etc/resolv.conf and /etc/hosts files in the specified + vserver directory. If the files do not exist, write them out. If they + do exist, rewrite them with new values if the file UPDATE_FILE_FLAG + exists it /etc. if this is called with the vserver-reference directory, + always update the network config files and create the UPDATE_FILE_FLAG. + + This is currently called when setting up the initial vserver reference, + and later when nodes boot to update existing vserver images. + + Expect the following variables from the store: + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + NETWORK_SETTINGS A dictionary of the values from the network + configuration file + """ + + try: + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + NETWORK_SETTINGS= vars["NETWORK_SETTINGS"] + if NETWORK_SETTINGS == "": + raise ValueError, "NETWORK_SETTINGS" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + try: + ip= NETWORK_SETTINGS['ip'] + method= NETWORK_SETTINGS['method'] + hostname= NETWORK_SETTINGS['hostname'] + domainname= NETWORK_SETTINGS['domainname'] + except KeyError, var: + raise BootManagerException, \ + "Missing network value %s in var NETWORK_SETTINGS\n" % var + + try: + os.listdir(vserver_dir) + except OSError: + log.write( "Directory %s does not exist to write network conf in.\n" % + vserver_dir ) + return + + file_path= "%s/etc/%s" % (vserver_dir,UPDATE_FILE_FLAG) + update_files= 0 + if os.access(file_path,os.F_OK): + update_files= 1 + + + if vserver_dir.find(VSERVER_REFERENCE_DIR_NAME) != -1: + log.write( "Forcing update on vserver-reference directory:\n%s\n" % + vserver_dir ) + utils.sysexec_noerr( "echo '%s' > %s/etc/%s" % + (UPDATE_FILE_FLAG,vserver_dir,UPDATE_FILE_FLAG), + log ) + update_files= 1 + + + if update_files: + log.write( "Updating network files in %s.\n" % vserver_dir ) + + file_path= "%s/etc/hosts" % vserver_dir + hosts_file= file(file_path, "w" ) + hosts_file.write( "127.0.0.1 localhost\n" ) + if method == "static": + hosts_file.write( "%s %s.%s\n" % (ip, hostname, domainname) ) + hosts_file.close() + hosts_file= None + + + file_path= "%s/etc/resolv.conf" % vserver_dir + if method == "dhcp": + # copy the resolv.conf from the boot cd env. + utils.sysexec( "cp /etc/resolv.conf %s/etc" % vserver_dir, log ) + else: + # copy the generated resolv.conf from the system image, since + # we generated it via static settings + utils.sysexec( "cp %s/etc/resolv.conf %s/etc" % \ + (SYSIMG_PATH,vserver_dir), log ) + + return diff --git a/source/steps/InstallInit.py b/source/steps/InstallInit.py new file mode 100644 index 0000000..aa76186 --- /dev/null +++ b/source/steps/InstallInit.py @@ -0,0 +1,116 @@ +# 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. + + + +import os, sys, shutil +import string + +import utils + + +def Run( vars, log ): + """ + Setup the install environment: + - unmount anything in the temp/sysimg path (possible from previous + aborted installs + - create temp directories + + Expect the following variables from the store: + TEMP_PATH the path to download and store temp files to + SYSIMG_DIR the directory name of the system image + contained in TEMP_PATH + PLCONF_DIR The directory to store the configuration file in + ALPINA_SERVER_DIR The dir on the server where the support files are + + Sets the following variables: + SYSIMG_PATH the directory where the system image will be mounted, + (= TEMP_PATH/SYSIMG_DIR) + """ + + log.write( "\n\nStep: Install: Initializing.\n" ) + + # make sure we have the variables we need + try: + TEMP_PATH= vars["TEMP_PATH"] + if TEMP_PATH == "": + raise ValueError("TEMP_PATH") + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError("SYSIMG_PATH") + + PLCONF_DIR= vars["PLCONF_DIR"] + if PLCONF_DIR == "": + raise ValueError, "PLCONF_DIR" + + ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"] + if ALPINA_SERVER_DIR == "": + raise ValueError, "ALPINA_SERVER_DIR" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + # if this is a fresh install, then nothing should be + # here, but we support restarted installs without rebooting + # so who knows what the current state is + + log.write( "Unmounting any previous mounts\n" ) + utils.sysexec_noerr( "chroot %s umount /rcfs" % SYSIMG_PATH, log ) + utils.sysexec_noerr( "umount %s/proc" % SYSIMG_PATH, log ) + utils.sysexec_noerr( "umount %s/mnt/cdrom" % SYSIMG_PATH, log ) + utils.sysexec_noerr( "umount %s/vservers" % SYSIMG_PATH, log ) + utils.sysexec_noerr( "umount %s" % SYSIMG_PATH, log ) + + log.write( "Removing any old files, directories\n" ) + utils.removedir( TEMP_PATH ) + + log.write( "Cleaning up any existing PlanetLab config files\n" ) + utils.removedir( PLCONF_DIR ) + + # create the temp path and sysimg path. since sysimg + # path is in temp path, both are created here + log.write( "Creating system image path\n" ) + utils.makedirs( SYSIMG_PATH ) + + return 1 diff --git a/source/steps/InstallNodeInit.py b/source/steps/InstallNodeInit.py new file mode 100644 index 0000000..32319e6 --- /dev/null +++ b/source/steps/InstallNodeInit.py @@ -0,0 +1,88 @@ +# 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. + + +from Exceptions import * +import utils + + +def Run( vars, log ): + """ + Initialize the node: + - runs planetlabconf + + Except the following variables from the store: + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + + Sets the following variables: + None + + """ + + log.write( "\n\nStep: Install: Final node initialization.\n" ) + + # make sure we have the variables we need + try: + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + log.write( "Running PlanetLabConf to update any configuration files\n" ) + + if not utils.sysexec( "chroot %s PlanetLabConf.py noscripts" % + SYSIMG_PATH, log ): + log.write( "PlanetLabConf failed, install incomplete.\n" ) + return 0 + + services= [ "netfs", "rawdevices", "cpuspeed", "smartd" ] + for service in services: + log.write( "Disabling unneeded service: %s\n" % service ) + utils.sysexec( "chroot %s chkconfig --level 12345 %s off" % + (SYSIMG_PATH,service), log ) + + return 1 diff --git a/source/steps/InstallPartitionDisks.py b/source/steps/InstallPartitionDisks.py new file mode 100644 index 0000000..02245c2 --- /dev/null +++ b/source/steps/InstallPartitionDisks.py @@ -0,0 +1,358 @@ +# 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. + + +import os, sys +import string +import popen2 + + +from Exceptions import * +import utils +import BootServerRequest +import compatibility + + + +def Run( vars, log ): + """ + Setup the block devices for install, partition them w/ LVM + + Expect the following variables from the store: + INSTALL_BLOCK_DEVICES list of block devices to install onto + TEMP_PATH somewhere to store what we need to run + ROOT_SIZE the size of the root logical volume + SWAP_SIZE the size of the swap partition + ALPINA_SERVER_DIR directory on the boot servers containing alpina + scripts and support files + BOOT_CD_VERSION A tuple of the current bootcd version + + Sets the following variables: + PARTITIONS diction of generic part. types (root/swap) + and their associated devices. + Current keys/values: + root /dev/planetlab/root + swap /dev/planetlab/swap + + """ + + log.write( "\n\nStep: Install: partitioning disks.\n" ) + + # make sure we have the variables we need + try: + TEMP_PATH= vars["TEMP_PATH"] + if TEMP_PATH == "": + raise ValueError, "TEMP_PATH" + + INSTALL_BLOCK_DEVICES= vars["INSTALL_BLOCK_DEVICES"] + if( len(INSTALL_BLOCK_DEVICES) == 0 ): + raise ValueError, "INSTALL_BLOCK_DEVICES is empty" + + ROOT_SIZE= vars["ROOT_SIZE"] + if ROOT_SIZE == "" or ROOT_SIZE == 0: + raise ValueError, "ROOT_SIZE invalid" + + SWAP_SIZE= vars["SWAP_SIZE"] + if SWAP_SIZE == "" or SWAP_SIZE == 0: + raise ValueError, "SWAP_SIZE invalid" + + ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"] + if ALPINA_SERVER_DIR == None: + raise ValueError, "ALPINA_SERVER_DIR" + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + bs_request= BootServerRequest.BootServerRequest() + + + # old cds need extra utilities to partition disks and setup lvm + if BOOT_CD_VERSION[0] == 2: + compatibility.setup_partdisks_2x_cd( vars, log ) + + import parted + + # define the basic partition paths + PARTITIONS= {} + PARTITIONS["root"]= "/dev/planetlab/root" + PARTITIONS["swap"]= "/dev/planetlab/swap" + PARTITIONS["vservers"]= "/dev/planetlab/vservers" + # Linux 2.6 mounts LVM with device mapper + PARTITIONS["mapper-root"]= "/dev/mapper/planetlab-root" + PARTITIONS["mapper-swap"]= "/dev/mapper/planetlab-swap" + PARTITIONS["mapper-vservers"]= "/dev/mapper/planetlab-vservers" + vars["PARTITIONS"]= PARTITIONS + + + # disable swap if its on + utils.sysexec_noerr( "swapoff %s" % PARTITIONS["swap"], log ) + + # shutdown and remove any lvm groups/volumes + utils.sysexec_noerr( "vgscan", log ) + utils.sysexec_noerr( "vgchange -ay", log ) + utils.sysexec_noerr( "lvremove -f /dev/planetlab/root", log ) + utils.sysexec_noerr( "lvremove -f /dev/planetlab/swap", log ) + utils.sysexec_noerr( "lvremove -f /dev/planetlab/vservers", log ) + utils.sysexec_noerr( "vgchange -an", log ) + utils.sysexec_noerr( "vgremove planetlab", log ) + + log.write( "Running vgscan for devices\n" ) + utils.sysexec_noerr( "vgscan", log ) + + used_devices= [] + + for device in INSTALL_BLOCK_DEVICES: + + if single_partition_device( device, vars, log ): + used_devices.append( device ) + log.write( "Successfully initialized %s\n" % device ) + else: + log.write( "Unable to partition %s, not using it.\n" % device ) + continue + + # list of devices to be used with vgcreate + vg_device_list= "" + + # initialize the physical volumes + for device in used_devices: + + part_path= get_partition_path_from_device( device, vars, log ) + + if not create_lvm_physical_volume( part_path, vars, log ): + raise BootManagerException, "Could not create lvm physical volume " \ + "on partition %s" % part_path + + vg_device_list = vg_device_list + " " + part_path + + # create an lvm volume group + utils.sysexec( "vgcreate -s32M planetlab %s" % vg_device_list, log) + + # create swap logical volume + utils.sysexec( "lvcreate -L%s -nswap planetlab" % SWAP_SIZE, log ) + + # create root logical volume + utils.sysexec( "lvcreate -L%s -nroot planetlab" % ROOT_SIZE, log ) + + # create vservers logical volume with all remaining space + # first, we need to get the number of remaining extents we can use + remaining_extents= get_remaining_extents_on_vg( vars, log ) + + utils.sysexec( "lvcreate -l%s -nvservers planetlab" % remaining_extents, log ) + + # activate volume group (should already be active) + #utils.sysexec( TEMP_PATH + "vgchange -ay planetlab", log ) + + # make swap + utils.sysexec( "mkswap %s" % PARTITIONS["swap"], log ) + + # make root file system + utils.sysexec( "mkfs.ext2 -j %s" % PARTITIONS["root"], log ) + + # make vservers file system + utils.sysexec( "mkfs.ext2 -m 0 -j %s" % PARTITIONS["vservers"], log ) + + # save the list of block devices in the log + log.write( "Block devices used (in lvm):\n" ) + log.write( repr(used_devices) + "\n" ) + log.write( "End of block devices used (in lvm).\n" ) + + # list of block devices used may be updated + vars["INSTALL_BLOCK_DEVICES"]= used_devices + + return 1 + + + +def single_partition_device( device, vars, log ): + """ + initialize a disk by removing the old partition tables, + and creating a new single partition that fills the disk. + + return 1 if sucessful, 0 otherwise + """ + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION[0] == 2: + compatibility.setup_partdisks_2x_cd( vars, log ) + + import parted + + lvm_flag= parted.partition_flag_get_by_name('lvm') + + try: + # wipe the old partition table + utils.sysexec( "dd if=/dev/zero of=%s bs=512 count=1" % device, log ) + + # get the device + dev= parted.PedDevice.get(device) + + # 2.x cds have different libparted that 3.x cds, and they have + # different interfaces + if BOOT_CD_VERSION[0] == 3: + + # create a new partition table + disk= dev.disk_new_fresh(parted.disk_type_get("msdos")) + + # create one big partition on each block device + constraint= dev.constraint_any() + + new_part= disk.partition_new( + parted.PARTITION_PRIMARY, + parted.file_system_type_get("ext2"), + 0, 1 ) + + # make it an lvm partition + new_part.set_flag(lvm_flag,1) + + # actually add the partition to the disk + disk.add_partition(new_part, constraint) + + disk.maximize_partition(new_part,constraint) + + disk.commit() + del disk + else: + # create a new partition table + dev.disk_create(parted.disk_type_get("msdos")) + + # get the disk + disk= parted.PedDisk.open(dev) + + # create one big partition on each block device + part= disk.next_partition() + while part: + if part.type == parted.PARTITION_FREESPACE: + new_part= disk.partition_new( + parted.PARTITION_PRIMARY, + parted.file_system_type_get("ext2"), + part.geom.start, + part.geom.end ) + + constraint = disk.constraint_any() + + # make it an lvm partition + new_part.set_flag(lvm_flag,1) + + # actually add the partition to the disk + disk.add_partition(new_part, constraint) + + break + + part= disk.next_partition(part) + + disk.write() + disk.close() + del disk + + except BootManagerException, e: + log.write( "BootManagerException while running: %s\n" % str(e) ) + return 0 + + except parted.error, e: + log.write( "parted exception while running: %s\n" % str(e) ) + return 0 + + return 1 + + + +def create_lvm_physical_volume( part_path, vars, log ): + """ + make the specificed partition a lvm physical volume. + + return 1 if successful, 0 otherwise. + """ + + try: + # again, wipe any old data, this time on the partition + utils.sysexec( "dd if=/dev/zero of=%s bs=512 count=1" % part_path, log ) + utils.sysexec( "pvcreate -fy %s" % part_path, log ) + except BootManagerException, e: + log.write( "create_lvm_physical_volume failed.\n" ) + return 0 + + return 1 + + + +def get_partition_path_from_device( device, vars, log ): + """ + given a device, return the path of the first partition on the device + """ + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + + # those who wrote the cciss driver just had to make it difficult + if BOOT_CD_VERSION[0] == 3: + cciss_test= "/dev/cciss" + if device[:len(cciss_test)] == cciss_test: + part_path= device + "p1" + else: + part_path= device + "1" + else: + # since device ends in /disc, we need to make it end in + # /part1 to indicate the first partition (for devfs based 2.x cds) + dev_parts= string.split(device,"/") + dev_parts[len(dev_parts)-1]= "part1" + part_path= string.join(dev_parts,"/") + + return part_path + + + +def get_remaining_extents_on_vg( vars, log ): + """ + return the free amount of extents on the planetlab volume group + """ + + c_stdout, c_stdin = popen2.popen2("vgdisplay -c planetlab") + result= string.strip(c_stdout.readline()) + c_stdout.close() + c_stdin.close() + remaining_extents= string.split(result,":")[15] + + return remaining_extents diff --git a/source/steps/InstallUninitHardware.py b/source/steps/InstallUninitHardware.py new file mode 100644 index 0000000..7a28612 --- /dev/null +++ b/source/steps/InstallUninitHardware.py @@ -0,0 +1,137 @@ +# 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. + + +import os + +from Exceptions import * +import utils + + + +def Run( vars, log ): + """ + Unitializes hardware: + - unmount all previously mounted partitions + + Except the following variables from the store: + TEMP_PATH the path to download and store temp files to + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + PARTITIONS dictionary of generic part. types (root/swap) + and their associated devices. + NODE_ID the node_id from the database for this node + + this is needed to make any requests back to the server + + Sets the following variables: + None + + """ + + log.write( "\n\nStep: Install: Shutting down installer.\n" ) + + # make sure we have the variables we need + try: + TEMP_PATH= vars["TEMP_PATH"] + if TEMP_PATH == "": + raise ValueError, "TEMP_PATH" + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + PARTITIONS= vars["PARTITIONS"] + if PARTITIONS == None: + raise ValueError, "PARTITIONS" + + NODE_ID= vars["NODE_ID"] + if NODE_ID == "": + raise ValueError("NODE_ID") + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + try: + # make sure the required partitions exist + val= PARTITIONS["root"] + val= PARTITIONS["swap"] + val= PARTITIONS["vservers"] + except KeyError, part: + raise BootManagerException, "Missing partition in PARTITIONS: %s\n" % part + + # workaround + utils.sysexec_noerr( "chroot %s umount /rcfs" % SYSIMG_PATH, log ) + + log.write( "Unmounting proc.\n" ) + utils.sysexec( "umount %s/proc" % SYSIMG_PATH, log ) + + log.write( "Unmounting vserver partition.\n" ) + utils.sysexec( "umount %s" % PARTITIONS["vservers"], log ) + + log.write( "Unmounting rcfs file system in image.\n" ) + utils.sysexec_noerr( "chroot %s umount /rcfs" % SYSIMG_PATH, log ) + + log.write( "Unmounting system image.\n" ) + utils.sysexec( "umount %s" % PARTITIONS["root"], log ) + + log.write( "Shutting down swap\n" ) + utils.sysexec( "swapoff %s" % PARTITIONS["swap"], log ) + + # as one of the last steps, upload /var/log/messages if it exists + + # send a notification that the install is complete + #action= "email" + #message= "installfinished" + #nodestate= "" + + #try: + # result= utils.notifybootserver( BS_REQUEST, NODE_ID, + # NODE_NONCE, + # action, message, nodestate ) + #except AlpinaError, desc: + # log.write( "Unable to notify boot server of " \ + # "install complete (not critical): %s" % desc ) + + return 1 diff --git a/source/steps/InstallWriteConfig.py b/source/steps/InstallWriteConfig.py new file mode 100644 index 0000000..d7fddcc --- /dev/null +++ b/source/steps/InstallWriteConfig.py @@ -0,0 +1,414 @@ +# 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. + + +import os, string + +from Exceptions import * +import utils +from systeminfo import systeminfo +import BootAPI + + +def Run( vars, log ): + + """ + Writes out the following configuration files for the node: + /etc/fstab + /etc/hosts + /etc/sysconfig/network-scripts/ifcfg-eth0 + /etc/resolv.conf (if applicable) + /etc/sysconfig/network + /etc/modprobe.conf + /etc/ssh/ssh_host_key + /etc/ssh/ssh_host_rsa_key + /etc/ssh/ssh_host_dsa_key + + Expect the following variables from the store: + VERSION the version of the install + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + PARTITIONS dictionary of generic part. types (root/swap) + and their associated devices. + PLCONF_DIR The directory to store the configuration file in + NETWORK_SETTINGS A dictionary of the values from the network + configuration file + BOOT_CD_VERSION A tuple of the current bootcd version + + Sets the following variables: + None + + """ + + log.write( "\n\nStep: Install: Writing configuration files.\n" ) + + # make sure we have the variables we need + try: + VERSION= vars["VERSION"] + if VERSION == "": + raise ValueError, "VERSION" + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + PARTITIONS= vars["PARTITIONS"] + if PARTITIONS == None: + raise ValueError, "PARTITIONS" + + PLCONF_DIR= vars["PLCONF_DIR"] + if PLCONF_DIR == "": + raise ValueError, "PLCONF_DIR" + + NETWORK_SETTINGS= vars["NETWORK_SETTINGS"] + if NETWORK_SETTINGS == "": + raise ValueError, "NETWORK_SETTINGS" + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + try: + # we need to keys in PARTITIONS, root and swap, make sure + # they exist + val= PARTITIONS["root"] + val= PARTITIONS["swap"] + val= PARTITIONS["vservers"] + val= PARTITIONS["mapper-root"] + val= PARTITIONS["mapper-swap"] + val= PARTITIONS["mapper-vservers"] + except KeyError, part: + log.write( "Missing partition in PARTITIONS: %s\n" % part ) + return 0 + + + log.write( "Setting local time to UTC\n" ) + utils.sysexec( "chroot %s ln -sf /usr/share/zoneinfo/UTC /etc/localtime" % \ + SYSIMG_PATH, log ) + + + log.write( "Enabling ntp at boot\n" ) + utils.sysexec( "chroot %s chkconfig ntpd on" % SYSIMG_PATH, log ) + + log.write( "Creating system directory %s\n" % PLCONF_DIR ) + if not utils.makedirs( "%s/%s" % (SYSIMG_PATH,PLCONF_DIR) ): + log.write( "Unable to create directory\n" ) + return 0 + + + log.write( "Writing network configuration\n" ) + write_network_configuration( vars, log ) + + # write out the modprobe.conf file for the system. make sure + # the order of the ethernet devices are listed in the same order + # as the boot cd loaded the modules. this is found in /tmp/loadedmodules + # ultimately, the order will only match the boot cd order if + # the kernel modules have the same name - which should be true for the later + # version boot cds because they use the same kernel version. + # older boot cds use a 2.4.19 kernel, and its possible some of the network + # module names have changed, in which case the system might not boot + # if the network modules are activated in a different order that the + # boot cd. + log.write( "Writing /etc/modprobe.conf\n" ) + + sysinfo= systeminfo() + sysmods= sysinfo.get_system_modules(SYSIMG_PATH) + if sysmods is None: + raise BootManagerException, "Unable to get list of system modules." + + eth_count= 0 + scsi_count= 0 + + modulesconf_file= file("%s/etc/modprobe.conf" % SYSIMG_PATH, "w" ) + + for type in sysmods: + if type == sysinfo.MODULE_CLASS_SCSI: + for a_mod in sysmods[type]: + if scsi_count == 0: + modulesconf_file.write( "alias scsi_hostadapter %s\n" % + a_mod ) + else: + modulesconf_file.write( "alias scsi_hostadapter%d %s\n" % + (scsi_count,a_mod) ) + scsi_count= scsi_count + 1 + + elif type == sysinfo.MODULE_CLASS_NETWORK: + for a_mod in sysmods[type]: + modulesconf_file.write( "alias eth%d %s\n" % + (eth_count,a_mod) ) + eth_count= eth_count + 1 + + modulesconf_file.close() + modulesconf_file= None + + + # dump the modprobe.conf file to the log (not to screen) + log.write( "Contents of new modprobe.conf file:\n" ) + modulesconf_file= file("%s/etc/modprobe.conf" % SYSIMG_PATH, "r" ) + contents= modulesconf_file.read() + log.write( contents + "\n" ) + modulesconf_file.close() + modulesconf_file= None + log.write( "End contents of new modprobe.conf file.\n" ) + + log.write( "Writing system /etc/fstab\n" ) + fstab= file( "%s/etc/fstab" % SYSIMG_PATH, "w" ) + fstab.write( "%s none swap sw 0 0\n" % \ + PARTITIONS["mapper-swap"] ) + fstab.write( "%s / ext3 defaults 0 0\n" % \ + PARTITIONS["mapper-root"] ) + fstab.write( "%s /vservers ext3 tagxid,defaults 0 0\n" % \ + PARTITIONS["mapper-vservers"] ) + fstab.write( "none /proc proc defaults 0 0\n" ) + fstab.write( "none /dev/shm tmpfs defaults 0 0\n" ) + fstab.write( "none /dev/pts devpts defaults 0 0\n" ) + fstab.write( "none /rcfs rcfs defaults 0 0\n" ) + fstab.close() + + + log.write( "Writing system /etc/issue\n" ) + issue= file( "%s/etc/issue" % SYSIMG_PATH, "w" ) + issue.write( "PlanetLab Node: \\n\n" ) + issue.write( "Kernel \\r on an \\m\n" ) + issue.write( "http://www.planet-lab.org\n\n" ) + issue.close() + + log.write( "Setting up authentication (non-ssh)\n" ) + utils.sysexec( "chroot %s authconfig --nostart --kickstart --enablemd5 " \ + "--enableshadow" % SYSIMG_PATH, log ) + utils.sysexec( "sed -e 's/^root\:\:/root\:*\:/g' " \ + "%s/etc/shadow > %s/etc/shadow.new" % \ + (SYSIMG_PATH,SYSIMG_PATH), log ) + utils.sysexec( "chroot %s mv " \ + "/etc/shadow.new /etc/shadow" % SYSIMG_PATH, log ) + utils.sysexec( "chroot %s chmod 400 /etc/shadow" % SYSIMG_PATH, log ) + + # if we are setup with dhcp, copy the current /etc/resolv.conf into + # the system image so we can run programs inside that need network access + method= "" + try: + method= vars['NETWORK_SETTINGS']['method'] + except: + pass + + if method == "dhcp": + utils.sysexec( "cp /etc/resolv.conf %s/etc/" % SYSIMG_PATH, log ) + + # the kernel rpm should have already done this, so don't fail the + # install if it fails + log.write( "Mounting /proc in system image\n" ) + utils.sysexec_noerr( "mount -t proc proc %s/proc" % SYSIMG_PATH, log ) + + # mkinitrd references both /etc/modprobe.conf and /etc/fstab + # as well as /proc/lvm/global. The kernel RPM installation + # likely created an improper initrd since these files did not + # yet exist. Re-create the initrd here. + log.write( "Making initrd\n" ) + + # trick mkinitrd in case the current environment does not have device mapper + fake_root_lvm= 0 + if not os.path.exists( "%s/%s" % (SYSIMG_PATH,PARTITIONS["mapper-root"]) ): + fake_root_lvm= 1 + utils.makedirs( "%s/dev/mapper" % SYSIMG_PATH ) + rootdev= file( "%s/%s" % (SYSIMG_PATH,PARTITIONS["mapper-root"]), "w" ) + rootdev.close() + + utils.sysexec( "chroot %s sh -c '" \ + "kernelversion=`ls /lib/modules | tail -1` && " \ + "rm -f /boot/initrd-$kernelversion.img && " \ + "mkinitrd /boot/initrd-$kernelversion.img $kernelversion'" % \ + SYSIMG_PATH, log ) + + if fake_root_lvm == 1: + utils.removefile( "%s/%s" % (SYSIMG_PATH,PARTITIONS["mapper-root"]) ) + + log.write( "Writing node install version\n" ) + utils.makedirs( "%s/etc/planetlab" % SYSIMG_PATH ) + ver= file( "%s/etc/planetlab/install_version" % SYSIMG_PATH, "w" ) + ver.write( "%s\n" % VERSION ) + ver.close() + + log.write( "Creating ssh host keys\n" ) + key_gen_prog= "/usr/bin/ssh-keygen" + + log.write( "Generating SSH1 RSA host key:\n" ) + key_file= "/etc/ssh/ssh_host_key" + utils.sysexec( "chroot %s %s -q -t rsa1 -f %s -C '' -N ''" % + (SYSIMG_PATH,key_gen_prog,key_file), log ) + utils.sysexec( "chmod 600 %s/%s" % (SYSIMG_PATH,key_file), log ) + utils.sysexec( "chmod 644 %s/%s.pub" % (SYSIMG_PATH,key_file), log ) + + log.write( "Generating SSH2 RSA host key:\n" ) + key_file= "/etc/ssh/ssh_host_rsa_key" + utils.sysexec( "chroot %s %s -q -t rsa -f %s -C '' -N ''" % + (SYSIMG_PATH,key_gen_prog,key_file), log ) + utils.sysexec( "chmod 600 %s/%s" % (SYSIMG_PATH,key_file), log ) + utils.sysexec( "chmod 644 %s/%s.pub" % (SYSIMG_PATH,key_file), log ) + + log.write( "Generating SSH2 DSA host key:\n" ) + key_file= "/etc/ssh/ssh_host_dsa_key" + utils.sysexec( "chroot %s %s -q -t dsa -f %s -C '' -N ''" % + (SYSIMG_PATH,key_gen_prog,key_file), log ) + utils.sysexec( "chmod 600 %s/%s" % (SYSIMG_PATH,key_file), log ) + utils.sysexec( "chmod 644 %s/%s.pub" % (SYSIMG_PATH,key_file), log ) + + return 1 + + + +def write_network_configuration( vars, log ): + """ + Write out the network configuration for this machine: + /etc/hosts + /etc/sysconfig/network-scripts/ifcfg-eth0 + /etc/resolv.conf (if applicable) + /etc/sysconfig/network + + It is assumed the caller mounted the root partition and the vserver partition + starting on SYSIMG_PATH - it is not checked here. + + The values to be used for the network settings are to be set in vars + in the variable 'NETWORK_SETTINGS', which is a dictionary + with keys: + + Key Used by this function + ----------------------------------------------- + node_id + node_key + method x + ip x + mac x (optional) + gateway x + network x + broadcast x + netmask x + dns1 x + dns2 x (optional) + hostname x + domainname x + """ + + try: + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + try: + network_settings= vars['NETWORK_SETTINGS'] + except KeyError, e: + raise BootManagerException, "No network settings found in vars." + + try: + hostname= network_settings['hostname'] + domainname= network_settings['domainname'] + method= network_settings['method'] + ip= network_settings['ip'] + gateway= network_settings['gateway'] + network= network_settings['network'] + netmask= network_settings['netmask'] + dns1= network_settings['dns1'] + except KeyError, e: + raise BootManagerException, "Missing value %s in network settings." % str(e) + + try: + dns2= '' + dns2= network_settings['dns2'] + except KeyError, e: + pass + + + log.write( "Writing /etc/hosts\n" ) + hosts_file= file("%s/etc/hosts" % SYSIMG_PATH, "w" ) + hosts_file.write( "127.0.0.1 localhost\n" ) + if method == "static": + hosts_file.write( "%s %s.%s\n" % (ip, hostname, domainname) ) + hosts_file.close() + hosts_file= None + + + log.write( "Writing /etc/sysconfig/network-scripts/ifcfg-eth0\n" ) + eth0_file= file("%s/etc/sysconfig/network-scripts/ifcfg-eth0" % + SYSIMG_PATH, "w" ) + eth0_file.write( "DEVICE=eth0\n" ) + if method == "static": + eth0_file.write( "BOOTPROTO=static\n" ) + eth0_file.write( "IPADDR=%s\n" % ip ) + eth0_file.write( "NETMASK=%s\n" % netmask ) + eth0_file.write( "GATEWAY=%s\n" % gateway ) + else: + eth0_file.write( "BOOTPROTO=dhcp\n" ) + eth0_file.write( "DHCP_HOSTNAME=%s\n" % hostname ) + eth0_file.write( "ONBOOT=yes\n" ) + eth0_file.write( "USERCTL=no\n" ) + eth0_file.close() + eth0_file= None + + if method == "static": + log.write( "Writing /etc/resolv.conf\n" ) + resolv_file= file("%s/etc/resolv.conf" % SYSIMG_PATH, "w" ) + if dns1 != "": + resolv_file.write( "nameserver %s\n" % dns1 ) + if dns2 != "": + resolv_file.write( "nameserver %s\n" % dns2 ) + resolv_file.write( "search %s\n" % domainname ) + resolv_file.close() + resolv_file= None + + log.write( "Writing /etc/sysconfig/network\n" ) + network_file= file("%s/etc/sysconfig/network" % SYSIMG_PATH, "w" ) + network_file.write( "NETWORKING=yes\n" ) + network_file.write( "HOSTNAME=%s.%s\n" % (hostname, domainname) ) + if method == "static": + network_file.write( "GATEWAY=%s\n" % gateway ) + network_file.close() + network_file= None + diff --git a/source/steps/ReadNodeConfiguration.py b/source/steps/ReadNodeConfiguration.py new file mode 100644 index 0000000..3fcc414 --- /dev/null +++ b/source/steps/ReadNodeConfiguration.py @@ -0,0 +1,546 @@ +# 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. + + +import sys, os, traceback +import string +import socket + +import utils +from Exceptions import * +import BootServerRequest + + +# two possible names of the configuration files +NEW_CONF_FILE_NAME= "plnode.txt" +OLD_CONF_FILE_NAME= "planet.cnf" + + +def Run( vars, log ): + """ + read the machines node configuration file, which contains + the node key and the node_id for this machine. + + 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) + planet.cnf 3 + + The locations will be searched in the above order, plnode.txt + will be checked first, then planet.cnf. Flash devices will only + be searched on 3.0 cds. + + Because some of the earlier + boot cds don't validate the configuration file (which results + in a file named /tmp/planet-clean.cnf), and some do, lets + bypass this, and mount and attempt to read in the conf + file ourselves. If it doesn't exist, we cannot continue, and a + BootManagerException will be raised. If the configuration file is found + and read, return 1. + + 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 + scripts and support files + + Sets the following variables from the configuration file: + WAS_NODE_ID_IN_CONF Set to 1 if the node id was in the conf file + WAS_NODE_KEY_IN_CONF Set to 1 if the node key was in the conf file + NONE_ID The db node_id for this machine + NODE_KEY The key for this node + NETWORK_SETTINGS A dictionary of the values from the network + configuration file. keys set: + method + ip + mac + gateway + network + broadcast + netmask + dns1 + dns2 + hostname + domainname + """ + + log.write( "\n\nStep: Reading node configuration file.\n" ) + + + # make sure we have the variables we need + try: + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + 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" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + NETWORK_SETTINGS= {} + NETWORK_SETTINGS['method']= "dhcp" + NETWORK_SETTINGS['ip']= "" + NETWORK_SETTINGS['mac']= "" + NETWORK_SETTINGS['gateway']= "" + NETWORK_SETTINGS['network']= "" + NETWORK_SETTINGS['broadcast']= "" + NETWORK_SETTINGS['netmask']= "" + NETWORK_SETTINGS['dns1']= "" + NETWORK_SETTINGS['dns2']= "" + NETWORK_SETTINGS['hostname']= "localhost" + NETWORK_SETTINGS['domainname']= "localdomain" + vars['NETWORK_SETTINGS']= NETWORK_SETTINGS + + vars['NODE_ID']= 0 + vars['NODE_KEY']= "" + + vars['WAS_NODE_ID_IN_CONF']= 0 + vars['WAS_NODE_KEY_IN_CONF']= 0 + + # for any devices that need to be mounted to get the configuration + # file, mount them here. + mount_point= "/tmp/conffilemount" + utils.makedirs( mount_point ) + + old_conf_file_contents= None + conf_file_contents= None + + + # 1. check the regular floppy device + log.write( "Checking standard floppy disk for plnode.txt file.\n" ) + + 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) + 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() + except IOError, e: + pass + + utils.sysexec_noerr( "umount /dev/fd0", 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." ) + + + # try the old file name, same device. its actually number 3 on the search + # 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) + 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() + except IOError, e: + pass + + utils.sysexec_noerr( "umount /dev/fd0", log ) + + + + if BOOT_CD_VERSION[0] == 3: + # 2. check flash devices on 3.0 based cds + log.write( "Checking flash devices for plnode.txt file.\n" ) + + # this is done the same way the 3.0 cds do it, by attempting + # to mount and sd*1 devices that are removable + devices= os.listdir("/sys/block/") + + for device in devices: + if device[:2] != "sd": + continue + + # test removable + removable_file_path= "/sys/block/%s/removable" % device + try: + removable= int(file(removable_file_path,"r").read().strip()) + except ValueError, e: + continue + except IOError, e: + continue + + if not removable: + 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 ): + 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.") + + + + # 3. check standard floppy disk for old file name planet.cnf + log.write( "Checking standard floppy disk for planet.cnf file.\n" ) + + if old_conf_file_contents: + if __parse_configuration_file( vars, log, old_conf_file_contents): + return 1 + else: + raise BootManagerException( "Found configuration file planet.cnf " \ + "on floppy, but was unable to parse it." ) + + + # 4. 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 + 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() + except IOError, e: + pass + + if __parse_configuration_file( vars, log, conf_file_contents): + return 1 + else: + raise BootManagerException( "Found configuration file plnode.txt " \ + "in /usr/boot, but was unable to parse it.") + + + + # 5. 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 + 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() + except IOError, e: + pass + + if __parse_configuration_file( vars, log, conf_file_contents): + return 1 + else: + raise BootManagerException( "Found configuration file plnode.txt " \ + "in /usr, but was unable to parse it.") + + + raise BootManagerException, "Unable to find and read a node configuration file." + + + + +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) + """ + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + ALPINA_SERVER_DIR= vars["ALPINA_SERVER_DIR"] + NETWORK_SETTINGS= vars["NETWORK_SETTINGS"] + + if file_contents is None: + return 0 + + try: + line_num= 0 + for line in file_contents.split("\n"): + + line_num = line_num + 1 + + # if its a comment or a whitespace line, ignore + if line[:1] == "#" or string.strip(line) == "": + continue + + # file is setup as name="value" pairs + parts= string.split(line,"=") + if len(parts) != 2: + log.write( "Invalid line %d in configuration file:\n" % line_num ) + log.write( line + "\n" ) + return 0 + + name= string.strip(parts[0]) + value= string.strip(parts[1]) + + # make sure value starts and ends with + # single or double quotes + quotes= value[0] + value[len(value)-1] + if quotes != "''" and quotes != '""': + log.write( "Invalid line %d in configuration file:\n" % line_num ) + log.write( line + "\n" ) + return 0 + + # get rid of the quotes around the value + value= string.strip(value[1:len(value)-1]) + + if name == "NODE_ID": + try: + vars['NODE_ID']= int(value) + vars['WAS_NODE_ID_IN_CONF']= 1 + except ValueError, e: + log.write( "Non-numeric node_id in configuration file.\n" ) + return 0 + + if name == "NODE_KEY": + vars['NODE_KEY']= value + vars['WAS_NODE_KEY_IN_CONF']= 1 + + if name == "IP_METHOD": + value= string.lower(value) + if value != "static" and value != "dhcp": + log.write( "Invalid IP_METHOD in configuration file:\n" ) + log.write( line + "\n" ) + return 0 + NETWORK_SETTINGS['method']= value.strip() + + if name == "IP_ADDRESS": + NETWORK_SETTINGS['ip']= value.strip() + + if name == "IP_GATEWAY": + NETWORK_SETTINGS['gateway']= value.strip() + + if name == "IP_NETMASK": + NETWORK_SETTINGS['netmask']= value.strip() + + if name == "IP_NETADDR": + NETWORK_SETTINGS['network']= value.strip() + + if name == "IP_BROADCASTADDR": + NETWORK_SETTINGS['broadcast']= value.strip() + + if name == "IP_DNS1": + NETWORK_SETTINGS['dns1']= value.strip() + + if name == "IP_DNS2": + NETWORK_SETTINGS['dns2']= value.strip() + + if name == "HOST_NAME": + NETWORK_SETTINGS['hostname']= string.lower(value) + + if name == "DOMAIN_NAME": + NETWORK_SETTINGS['domainname']= string.lower(value) + + except IndexError, e: + log.write( "Unable to parse configuration file\n" ) + return 0 + + # now if we are set to dhcp, clear out any fields + # that don't make sense + if NETWORK_SETTINGS["method"] == "dhcp": + NETWORK_SETTINGS["ip"]= "" + NETWORK_SETTINGS["gateway"]= "" + NETWORK_SETTINGS["netmask"]= "" + NETWORK_SETTINGS["network"]= "" + NETWORK_SETTINGS["broadcast"]= "" + NETWORK_SETTINGS["dns1"]= "" + NETWORK_SETTINGS["dns2"]= "" + + + log.write("Successfully read and parsed node configuration file.\n" ) + + + 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} + result= bs_request.DownloadFile( "%s/getnodeid.php" % + ALPINA_SERVER_DIR, + None, postVars, 1, 1, + "/tmp/node_id") + if result == 0: + log.write( "Unable to make request to get node_id.\n" ) + return 0 + + try: + node_id_file= file("/tmp/node_id","r") + node_id= string.strip(node_id_file.read()) + node_id_file.close() + except IOError: + log.write( "Unable to read node_id from /tmp/node_id\n" ) + return 0 + + try: + node_id= int(string.strip(node_id)) + except ValueError: + log.write( "Got node_id from PLC, but not numeric: %s" % str(node_id) ) + return 0 + + if node_id == -1: + log.write( "Got node_id, but it returned -1\n" ) + return 0 + + log.write( "Got node_id from PLC: %s\n" % str(node_id) ) + vars['NODE_ID']= node_id + + + + if vars['NODE_KEY'] is None or vars['NODE_KEY'] == "": + log.write( "Configuration file does not contain a node_key value.\n" ) + log.write( "Using boot nonce instead.\n" ) + + # 3.x cds stored the file in /tmp/nonce in ascii form, so they + # can be read and used directly. 2.x cds stored in the same place + # but in binary form, so we need to convert it to ascii the same + # way the old boot scripts did so it matches whats in the db + # (php uses bin2hex, + if BOOT_CD_VERSION[0] == 2: + read_mode= "rb" + else: + read_mode= "r" + + try: + nonce_file= file("/tmp/nonce",read_mode) + nonce= nonce_file.read() + nonce_file.close() + except IOError: + log.write( "Unable to read nonce from /tmp/nonce\n" ) + return 0 + + if BOOT_CD_VERSION[0] == 2: + nonce= nonce.encode('hex') + + # there is this nice bug in the php that currently accepts the + # nonce for the old scripts, in that if the nonce contains + # null chars (2.x cds sent as binary), then + # the nonce is truncated. so, do the same here, truncate the nonce + # at the first null ('00'). This could leave us with an empty string. + nonce_len= len(nonce) + for byte_index in range(0,nonce_len,2): + if nonce[byte_index:byte_index+2] == '00': + nonce= nonce[:byte_index] + break + else: + nonce= string.strip(nonce) + + log.write( "Read nonce, using as key.\n" ) + vars['NODE_KEY']= nonce + + + # at this point, we've read the network configuration file. + # if we were setup using dhcp, get this system's current ip + # address and update the vars key ip, because it + # 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. + + hostname= NETWORK_SETTINGS['hostname'] + "." + \ + NETWORK_SETTINGS['domainname'] + + log.write( "Checking that hostname %s resolves\n" % hostname ) + try: + resolved_node_ip= socket.gethostbyname(hostname) + except socket.gaierror, e: + raise BootManagerException, \ + "Configured node hostname does not resolve." + + if NETWORK_SETTINGS['method'] == "dhcp": + NETWORK_SETTINGS['ip']= resolved_node_ip + node_ip= resolved_node_ip + 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) ) + + # 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 + + NETWORK_SETTINGS['mac']= hw_addr + + + vars["NETWORK_SETTINGS"]= NETWORK_SETTINGS + + return 1 diff --git a/source/steps/SendHardwareConfigToPLC.py b/source/steps/SendHardwareConfigToPLC.py new file mode 100644 index 0000000..4d8fa0c --- /dev/null +++ b/source/steps/SendHardwareConfigToPLC.py @@ -0,0 +1,10 @@ +from Exceptions import * + + +def Run( vars, log ): + + log.write( "\n\nStep: Sending hardware configuration to PLC.\n" ) + + log.write( "Not implemented, continuing.\n" ) + + return diff --git a/source/steps/StartDebug.py b/source/steps/StartDebug.py new file mode 100644 index 0000000..ebb7858 --- /dev/null +++ b/source/steps/StartDebug.py @@ -0,0 +1,124 @@ +import os + +from Exceptions import * +import utils +import compatibility + + +message= \ +""" +--------------------------------------------------------- +This machine has entered a temporary debug state, so +Planetlab Support can login and fix any problems that +might have occurred. + +Please do not reboot this machine at this point, unless +specifically asked to. + +Thank you. +--------------------------------------------------------- +""" + + +def Run( vars, log ): + """ + Bring up sshd inside the boot cd environment for debug purposes. + + Once its running, touch the file /tmp/SSHD_RUNNING so future + calls to this function don't do anything. + + Expect the following variables in vars to be set: + BM_SOURCE_DIR The source dir for the boot manager sources that + we are currently running from + BOOT_CD_VERSION A tuple of the current bootcd version + """ + + log.write( "\n\nStep: Starting debug mode.\n" ) + + # make sure we have the variables we need + try: + BM_SOURCE_DIR= vars["BM_SOURCE_DIR"] + if BM_SOURCE_DIR == "": + raise ValueError, "BM_SOURCE_DIR" + + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + log.write( "Starting debug environment\n" ) + + ssh_source_files= "%s/debug_files/" % BM_SOURCE_DIR + ssh_dir= "/etc/ssh/" + ssh_home= "/root/.ssh" + cancel_boot_flag= "/tmp/CANCEL_BOOT" + sshd_started_flag= "/tmp/SSHD_RUNNING" + + sshd_started= 0 + try: + os.stat(sshd_started_flag) + sshd_started= 1 + except OSError, e: + pass + + if not sshd_started: + log.write( "Creating ssh host keys\n" ) + + utils.makedirs( ssh_dir ) + utils.sysexec( "ssh-keygen -t rsa1 -b 1024 -f %s/ssh_host_key -N ''" % + ssh_dir, log ) + utils.sysexec( "ssh-keygen -t rsa -f %s/ssh_host_rsa_key -N ''" % + ssh_dir, log ) + utils.sysexec( "ssh-keygen -d -f %s/ssh_host_dsa_key -N ''" % + ssh_dir, log ) + + if BOOT_CD_VERSION[0] == 3: + utils.sysexec( "cp -f %s/sshd_config_v3 %s/sshd_config" % + (ssh_source_files,ssh_dir), log ) + else: + utils.sysexec( "cp -f %s/sshd_config_v2 %s/sshd_config" % + (ssh_source_files,ssh_dir), log ) + else: + log.write( "ssh host keys already created\n" ) + + + # always update the key, may have change in this instance of the bootmanager + log.write( "Installing debug ssh key for root user\n" ) + + utils.makedirs( ssh_home ) + utils.sysexec( "cp -f %s/debug_root_ssh_key %s/authorized_keys" % + (ssh_source_files,ssh_home), log ) + utils.sysexec( "chmod 700 %s" % ssh_home, log ) + utils.sysexec( "chmod 600 %s/authorized_keys" % ssh_home, log ) + + if not sshd_started: + log.write( "Starting sshd\n" ) + + if BOOT_CD_VERSION[0] == 2: + utils.sysexec( "/usr/sbin/sshd", log ) + else: + utils.sysexec( "service sshd start", log ) + + # flag that ssh is running + utils.sysexec( "touch %s" % sshd_started_flag, log ) + else: + log.write( "sshd already running\n" ) + + + # for ease of use, setup lvm on 2.x cds + if BOOT_CD_VERSION[0] == 2: + compatibility.setup_lvm_2x_cd(vars,log) + + + # this will make the initial script stop requesting scripts from PLC + utils.sysexec( "touch %s" % cancel_boot_flag, log ) + + print message + + return + diff --git a/source/steps/UpdateBootStateWithPLC.py b/source/steps/UpdateBootStateWithPLC.py new file mode 100644 index 0000000..417dd12 --- /dev/null +++ b/source/steps/UpdateBootStateWithPLC.py @@ -0,0 +1,50 @@ +from Exceptions import * +import BootAPI +import notify_messages + + +def Run( vars, log ): + """ + Change this nodes boot state at PLC. + + The current value of the BOOT_STATE key in vars is used. + Optionally, notify the contacts of the boot state change. + If this is the case, the following keys/values + should be set in vars before calling this step: + STATE_CHANGE_NOTIFY= 1 + STATE_CHANGE_NOTIFY_MESSAGE= "" + The second value is a message to send the users from notify_messages.py + + Return 1 if succesfull, a BootManagerException otherwise. + """ + + log.write( "\n\nStep: Updating node boot state at PLC.\n" ) + + update_vals= {} + update_vals['boot_state']= vars['BOOT_STATE'] + BootAPI.call_api_function( vars, "BootUpdateNode", (update_vals,) ) + + log.write( "Successfully updated boot state for this node at PLC\n" ) + + + if "STATE_CHANGE_NOTIFY" in vars.keys(): + if vars["STATE_CHANGE_NOTIFY"] == 1: + message= vars['STATE_CHANGE_NOTIFY_MESSAGE'] + include_pis= 0 + include_techs= 1 + include_support= 0 + + sent= 0 + try: + sent= BootAPI.call_api_function( vars, "BootNotifyOwners", + (message, + include_pis, + include_techs, + include_support) ) + except BootManagerException, e: + log.write( "Call to BootNotifyOwners failed: %s.\n" % e ) + + if sent == 0: + log.write( "Unable to notify site contacts of state change.\n" ) + + return 1 diff --git a/source/steps/UpdateNodeConfiguration.py b/source/steps/UpdateNodeConfiguration.py new file mode 100644 index 0000000..f7830e5 --- /dev/null +++ b/source/steps/UpdateNodeConfiguration.py @@ -0,0 +1,101 @@ +import os + +import InstallWriteConfig +import InstallBuildVServer +from Exceptions import * +import utils + + + +def Run( vars, log ): + """ + Reconfigure a node if necessary, including rewriting any network init + scripts based on what PLC has. Also, update any slivers on the machine + incase their network files are out of date (primarily /etc/hosts). + + This step expects the root to be already mounted on SYSIMG_PATH. + + Except the following keys to be set: + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + ROOT_MOUNTED the node root file system is mounted + NETWORK_SETTINGS A dictionary of the values from the network + configuration file + """ + + log.write( "\n\nStep: Updating node configuration.\n" ) + + # make sure we have the variables we need + try: + NETWORK_SETTINGS= vars["NETWORK_SETTINGS"] + if NETWORK_SETTINGS == "": + raise ValueError, "NETWORK_SETTINGS" + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + ROOT_MOUNTED= vars["ROOT_MOUNTED"] + if ROOT_MOUNTED == "": + raise ValueError, "ROOT_MOUNTED" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + try: + ip= NETWORK_SETTINGS['ip'] + method= NETWORK_SETTINGS['method'] + hostname= NETWORK_SETTINGS['hostname'] + domainname= NETWORK_SETTINGS['domainname'] + except KeyError, var: + raise BootManagerException, \ + "Missing network value %s in var NETWORK_SETTINGS\n" % var + + + if not ROOT_MOUNTED: + raise BootManagerException, "Root isn't mounted on SYSIMG_PATH\n" + + + log.write( "Updating node network configuration\n" ) + InstallWriteConfig.write_network_configuration( vars, log ) + + + log.write( "Updating vserver's /etc/hosts and /etc/resolv.conf files\n" ) + + # create a list of the full directory paths of all the vserver images that + # need to be updated. + update_path_list= [] + + for base_dir in ('/vservers','/vservers/.vcache'): + try: + full_dir_path= "%s/%s" % (SYSIMG_PATH,base_dir) + slices= os.listdir( full_dir_path ) + + try: + slices.remove("lost+found") + except ValueError, e: + pass + + update_path_list= update_path_list + map(lambda x: \ + full_dir_path+"/"+x, + slices) + except OSError, e: + continue + + + log.write( "Updating network configuration in:\n" ) + if len(update_path_list) == 0: + log.write( "No vserver images found to update.\n" ) + else: + for base_dir in update_path_list: + log.write( "%s\n" % base_dir ) + + + # now, update /etc/hosts and /etc/resolv.conf in each dir if + # the update flag is there + for base_dir in update_path_list: + InstallBuildVServer.update_vserver_network_files(base_dir,vars,log) + + return diff --git a/source/steps/ValidateNodeInstall.py b/source/steps/ValidateNodeInstall.py new file mode 100644 index 0000000..db4680c --- /dev/null +++ b/source/steps/ValidateNodeInstall.py @@ -0,0 +1,93 @@ +import os + +from Exceptions import * +import utils +from systeminfo import systeminfo +import compatibility + + +def Run( vars, log ): + """ + See if a node installation is valid. More checks should certainly be + done in the future, but for now, make sure that the sym links kernel-boot + and initrd-boot exist in /boot + + Expect the following variables to be set: + SYSIMG_PATH the path where the system image will be mounted + (always starts with TEMP_PATH) + BOOT_CD_VERSION A tuple of the current bootcd version + ROOT_MOUNTED the node root file system is mounted + + Set the following variables upon successfully running: + ROOT_MOUNTED the node root file system is mounted + """ + + log.write( "\n\nStep: Validating node installation.\n" ) + + # make sure we have the variables we need + try: + BOOT_CD_VERSION= vars["BOOT_CD_VERSION"] + if BOOT_CD_VERSION == "": + raise ValueError, "BOOT_CD_VERSION" + + SYSIMG_PATH= vars["SYSIMG_PATH"] + if SYSIMG_PATH == "": + raise ValueError, "SYSIMG_PATH" + + except KeyError, var: + raise BootManagerException, "Missing variable in vars: %s\n" % var + except ValueError, var: + raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var + + + ROOT_MOUNTED= 0 + if 'ROOT_MOUNTED' in vars.keys(): + ROOT_MOUNTED= vars['ROOT_MOUNTED'] + + # old cds need extra utilities to run lvm + if BOOT_CD_VERSION[0] == 2: + compatibility.setup_lvm_2x_cd( vars, log ) + + # simply creating an instance of this class and listing the system + # block devices will make them show up so vgscan can find the planetlab + # volume group + systeminfo().get_block_device_list() + + # mount the root system image if we haven't already. + # capture BootManagerExceptions during the vgscan/change and mount + # calls, so we can return 0 instead + if ROOT_MOUNTED == 0: + try: + utils.sysexec( "vgscan", log ) + utils.sysexec( "vgchange -ay planetlab", log ) + except BootManagerException, e: + log.write( "BootManagerException during vgscan/vgchange: %s\n" % + str(e) ) + return 0 + + utils.makedirs( SYSIMG_PATH ) + + try: + utils.sysexec( "mount /dev/planetlab/root %s" % SYSIMG_PATH, log ) + utils.sysexec( "mount /dev/planetlab/vservers %s/vservers" % + SYSIMG_PATH, log ) + except BootManagerException, e: + log.write( "BootManagerException during vgscan/vgchange: %s\n" % + str(e) ) + return 0 + + ROOT_MOUNTED= 1 + vars['ROOT_MOUNTED']= 1 + + valid= 0 + + if os.access("%s/boot/kernel-boot" % SYSIMG_PATH, os.F_OK | os.R_OK) and \ + os.access("%s/boot/initrd-boot" % SYSIMG_PATH, os.F_OK | os.R_OK): + valid= 1 + + if not valid: + log.write( "Node does not appear to be installed correctly:\n" ) + log.write( "missing file /boot/ initrd-boot or kernel-boot\n" ) + return 0 + + return 1 diff --git a/source/steps/__init__.py b/source/steps/__init__.py new file mode 100644 index 0000000..2a7a663 --- /dev/null +++ b/source/steps/__init__.py @@ -0,0 +1,25 @@ +""" +This directory contains individual step classes +""" + +__all__ = ["ReadNodeConfiguration", + "AuthenticateWithPLC", + "GetAndUpdateNodeDetails", + "ConfirmInstallWithUser", + "UpdateBootStateWithPLC", + "CheckHardwareRequirements", + "SendHardwareConfigToPLC", + "InitializeBootManager", + "UpdateNodeConfiguration", + "CheckForNewDisks", + "ChainBootNode", + "ValidateNodeInstall", + "StartDebug", + "InstallBootstrapRPM", + "InstallBuildVServer", + "InstallInit", + "InstallBase", + "InstallNodeInit", + "InstallPartitionDisks", + "InstallUninitHardware", + "InstallWriteConfig"] diff --git a/source/systeminfo.py b/source/systeminfo.py new file mode 100755 index 0000000..2faf3c2 --- /dev/null +++ b/source/systeminfo.py @@ -0,0 +1,453 @@ +#!/usr/bin/python2 + +# -------------- +# THIS file used to be named 'blockdevicescan.py', but has been renamed +# systeminfo.py and made more generic (now includes info about memory, +# and other hardware info on the machine) +# -------------- + +# 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. + + +# expected /proc/partitions format +# +#---------------------------------------------------- +#major minor #blocks name +# +#3 0 40017915 hda +#3 1 208813 hda1 +#3 2 20482875 hda2 +#3 3 522112 hda3 +#3 4 18804082 hda4 +#---------------------------------------------------- + + +import string +import os +import popen2 +from merge_hw_tables import merge_hw_tables + + +class systeminfo: + """ + a utility class for finding and returning information about + block devices, memory, and other hardware on the system + """ + + PROC_MEMINFO_PATH= "/proc/meminfo" + + + PROC_PARTITIONS_PATH= "/proc/partitions" + + # set when the sfdisk -l trick has been done to make + # all devices show up + DEVICES_SCANNED_FLAG= "/tmp/devices_scanned" + + # a /proc/partitions block is 1024 bytes + # a GB to a HDD manufacturer is 10^9 bytes + BLOCKS_PER_GB = pow(10, 9) / 1024.0; + + + # -n is numeric ids (no lookup), -m is machine readable + LSPCI_CMD= "/sbin/lspci -nm" + + MODULE_CLASS_NETWORK= "network" + MODULE_CLASS_SCSI= "scsi" + + PCI_CLASS_NETWORK= "0200" + PCI_CLASS_RAID= "0104" + PCI_CLASS_RAID2= "0100" + + + def get_total_phsyical_mem(self): + """ + return the total physical memory of the machine, in kilobytes. + + Return None if /proc/meminfo not readable. + """ + + try: + meminfo_file= file(self.PROC_MEMINFO_PATH,"r") + except IOError, e: + return + + total_memory= None + + for line in meminfo_file: + + try: + (fieldname,value)= string.split(line,":") + except ValueError, e: + # this will happen for lines that don't have two values + # (like the first line on 2.4 kernels) + continue + + fieldname= string.strip(fieldname) + value= string.strip(value) + + if fieldname == "MemTotal": + try: + (total_memory,units)= string.split(value) + except ValueError, e: + return + + if total_memory == "" or total_memory == None or \ + units == "" or units == None: + return + + if string.lower(units) != "kb": + return + + try: + total_memory= int(total_memory) + except ValueError, e: + return + + break + + meminfo_file.close() + + return total_memory + + + + def get_block_device_list(self): + """ + get a list of block devices from this system. + return an associative array, where the device name + (full /dev/device path) is the key, and the value + is a tuple of (major,minor,numblocks,gb_size,readonly) + """ + + # make sure we can access to the files/directories in /proc + if not os.access(self.PROC_PARTITIONS_PATH, os.F_OK): + return None + + + # only do this once every system boot + if not os.access(self.DEVICES_SCANNED_FLAG, os.R_OK): + + # this is ugly. under devfs, device + # entries in /dev/scsi/.. and /dev/ide/... + # don't show up until you attempt to read + # from the associated device at /dev (/dev/sda). + # so, lets run sfdisk -l (list partitions) against + # most possible block devices, that way they show + # up when it comes time to do the install. + for dev_prefix in ('sd','hd'): + block_dev_num= 0 + while block_dev_num < 10: + if block_dev_num < 26: + devicename= "/dev/%s%c" % \ + (dev_prefix, chr(ord('a')+block_dev_num)) + else: + devicename= "/dev/%s%c%c" % \ + ( dev_prefix, + chr(ord('a')+((block_dev_num/26)-1)), + chr(ord('a')+(block_dev_num%26)) ) + + os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename ) + block_dev_num = block_dev_num + 1 + + additional_scan_devices= ("/dev/cciss/c0d0p", "/dev/cciss/c0d1p", + "/dev/cciss/c0d2p", "/dev/cciss/c0d3p", + "/dev/cciss/c0d4p", "/dev/cciss/c0d5p", + "/dev/cciss/c0d6p", "/dev/cciss/c0d7p", + "/dev/cciss/c1d0p", "/dev/cciss/c1d1p", + "/dev/cciss/c1d2p", "/dev/cciss/c1d3p", + "/dev/cciss/c1d4p", "/dev/cciss/c1d5p", + "/dev/cciss/c1d6p", "/dev/cciss/c1d7p",) + + for devicename in additional_scan_devices: + os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename ) + + os.system( "touch %s" % self.DEVICES_SCANNED_FLAG ) + + + devicelist= {} + + partitions_file= file(self.PROC_PARTITIONS_PATH,"r") + line_count= 0 + + for line in partitions_file: + line_count= line_count + 1 + + # skip the first two lines always + if line_count < 2: + continue + + parts= string.split(line) + + if len(parts) < 4: + continue + + device= parts[3] + + # if the last char in device is a number, its + # a partition, and we ignore it + + if device[len(device)-1].isdigit(): + continue + + dev_name= "/dev/%s" % device + + try: + major= int(parts[0]) + minor= int(parts[1]) + blocks= int(parts[2]) + except ValueError, err: + continue + + gb_size= blocks/self.BLOCKS_PER_GB + + # parse the output of hdparm to get the readonly flag; + # if this fails, assume it isn't read only + readonly= 0 + + hdparm_cmd = popen2.Popen3("hdparm %s 2> /dev/null" % dev_name) + status= hdparm_cmd.wait() + + if os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0: + try: + hdparm_output= hdparm_cmd.fromchild.read() + hdparm_cmd= None + + # parse the output of hdparm, the lines we are interested + # in look + # like: + # + # readonly = 0 (off) + # + + for line in string.split(hdparm_output,"\n"): + + line= string.strip(line) + if line == "": + continue + + line_parts= string.split(line,"=") + if len(line_parts) < 2: + continue + + name= string.strip(line_parts[0]) + + if name == "readonly": + value= string.strip(line_parts[1]) + if len(value) == 0: + break + + if value[0] == "1": + readonly= 1 + break + + except IOError: + pass + + devicelist[dev_name]= (major,minor,blocks,gb_size,readonly) + + + return devicelist + + + + def get_system_modules( self, install_root ): + """ + Return a list of kernel modules that this system requires. + This requires access to the installed system's root + directory, as the following files must exist and are used: + /usr/share/hwdata/pcitable + /lib/modules/(first entry)/modules.pcimap + /lib/modules/(first entry)/modules.dep + + Note, that this assumes there is only one kernel + that is installed. If there are more than one, then + only the first one in a directory listing is used. + + Returns a dictionary, keys being the type of module: + - scsi MODULE_CLASS_SCSI + - network MODULE_CLASS_NETWORK + The value being the kernel module name to load. + """ + + # get the kernel version we are assuming + try: + kernel_version= os.listdir( "%s/lib/modules/" % install_root ) + except OSError, e: + return + + if len(kernel_version) == 0: + return + + if len(kernel_version) > 1: + print( "WARNING: We may be returning modules for the wrong kernel." ) + + kernel_version= kernel_version[0] + print( "Using kernel version %s" % kernel_version ) + + # test to make sure the three files we need are present + pcitable_path = "%s/usr/share/hwdata/pcitable" % install_root + modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \ + (install_root,kernel_version) + modules_dep_path = "%s/lib/modules/%s/modules.dep" % \ + (install_root,kernel_version) + + for path in (pcitable_path,modules_pcimap_path,modules_dep_path): + if not os.access(path,os.R_OK): + print( "Unable to read %s" % path ) + return + + # now, with those three files, merge them all into one easy to + # use lookup table + all_modules= merge_hw_tables().merge_files( modules_dep_path, + modules_pcimap_path, + pcitable_path ) + + if all_modules is None: + print( "Unable to merge pci id tables." ) + return + + + # this is the actual data structure we return + system_mods= {} + + # these are the lists that will be in system_mods + network_mods= [] + scsi_mods= [] + + + # get all the system devices from lspci + lspci_prog= popen2.Popen3( self.LSPCI_CMD, 1 ) + if lspci_prog is None: + print( "Unable to run %s with popen2.Popen3" % self.LSPCI_CMD ) + return + + returncode= lspci_prog.wait() + if returncode != 0: + print( "Running %s failed" % self.LSPCI_CMD ) + return + else: + print( "Successfully ran %s" % self.LSPCI_CMD ) + + for line in lspci_prog.fromchild: + if string.strip(line) == "": + continue + + parts= string.split(line) + + try: + classid= self.remove_quotes(parts[2]) + vendorid= self.remove_quotes(parts[3]) + deviceid= self.remove_quotes(parts[4]) + except IndexError: + print( "Skipping invalid line:", string.strip(line) ) + continue + + if classid not in (self.PCI_CLASS_NETWORK, + self.PCI_CLASS_RAID, + self.PCI_CLASS_RAID2): + continue + + full_deviceid= "%s:%s" % (vendorid,deviceid) + + for module in all_modules.keys(): + if full_deviceid in all_modules[module]: + if classid == self.PCI_CLASS_NETWORK: + network_mods.append(module) + elif classid in (self.PCI_CLASS_RAID, + self.PCI_CLASS_RAID2): + scsi_mods.append(module) + + system_mods[self.MODULE_CLASS_SCSI]= scsi_mods + system_mods[self.MODULE_CLASS_NETWORK]= network_mods + + return system_mods + + + def remove_quotes( self, str ): + """ + remove double quotes off of a string if they exist + """ + if str == "" or str == None: + return str + if str[:1] == '"': + str= str[1:] + if str[len(str)-1] == '"': + str= str[:len(str)-1] + return str + + + +if __name__ == "__main__": + info= systeminfo() + + devices= info.get_block_device_list() + print "block devices detected:" + if not devices: + print "no devices found!" + else: + for dev in devices.keys(): + print "%s %s" % (dev, repr(devices[dev])) + + + print "" + memory= info.get_total_phsyical_mem() + if not memory: + print "unable to read /proc/meminfo for memory" + else: + print "total physical memory: %d kb" % memory + + + print "" + modules= info.get_system_modules("/") + if not modules: + print "unable to list system modules" + else: + for type in modules: + if type == info.MODULE_CLASS_SCSI: + print( "all scsi modules:" ) + for a_mod in modules[type]: + print a_mod + elif type == info.MODULE_CLASS_NETWORK: + print( "all network modules:" ) + for a_mod in modules[type]: + print a_mod + diff --git a/source/utils.py b/source/utils.py new file mode 100644 index 0000000..b9a3746 --- /dev/null +++ b/source/utils.py @@ -0,0 +1,160 @@ +# 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. + + +import os, sys, shutil +import popen2 + +from Exceptions import * + + +def makedirs( path ): + """ + from python docs for os.makedirs: + Throws an error exception if the leaf directory + already exists or cannot be created. + + That is real useful. Instead, we'll create the directory, then use a + separate function to test for its existance. + + Return 1 if the directory exists and/or has been created, a BootManagerException + otherwise. Does not test the writability of said directory. + """ + try: + os.makedirs( path ) + except OSError: + pass + try: + os.listdir( path ) + except OSError: + raise BootManagerException, "Unable to create directory tree: %s" % path + + return 1 + + + +def removedir( path ): + """ + remove a directory tree, return 1 if successful, a BootManagerException + if failure. + """ + try: + os.listdir( path ) + except OSError: + return 1 + + try: + shutil.rmtree( path ) + except OSError, desc: + raise BootManagerException, "Unable to remove directory tree: %s" % path + + return 1 + + + +def sysexec( cmd, log= None ): + """ + execute a system command, output the results to the logger + if log <> None + + return 1 if command completed (return code of non-zero), + 0 if failed. A BootManagerException is raised if the command + was unable to execute or was interrupted by the user with Ctrl+C + """ + prog= popen2.Popen4( cmd, 0 ) + if prog is None: + raise BootManagerException, \ + "Unable to create instance of popen2.Popen3 " \ + "for command: %s" % cmd + + if log is not None: + try: + for line in prog.fromchild: + log.write( line ) + except KeyboardInterrupt: + raise BootManagerException, "Interrupted by user" + + returncode= prog.wait() + if returncode != 0: + raise BootManagerException, "Running %s failed (rc=%d)" % (cmd,returncode) + + prog= None + return 1 + + +def sysexec_noerr( cmd, log= None ): + """ + same as sysexec, but capture boot manager exceptions + """ + try: + rc= 0 + rc= sysexec( cmd, log ) + except BootManagerException, e: + pass + + return rc + + + +def chdir( dir ): + """ + change to a directory, return 1 if successful, a BootManagerException if failure + """ + try: + os.chdir( dir ) + except OSError: + raise BootManagerException, "Unable to change to directory: %s" % dir + + return 1 + + + +def removefile( filepath ): + """ + removes a file, return 1 if successful, 0 if failure + """ + try: + os.remove( filepath ) + except OSError: + raise BootManagerException, "Unable to remove file: %s" % filepath + + return 1 +