check in all bootmanager sources
authorAaron Klingaman <alk@cs.princeton.edu>
Thu, 26 May 2005 17:11:48 +0000 (17:11 +0000)
committerAaron Klingaman <alk@cs.princeton.edu>
Thu, 26 May 2005 17:11:48 +0000 (17:11 +0000)
35 files changed:
source/BootAPI.py [new file with mode: 0644]
source/BootManager.py [new file with mode: 0755]
source/BootServerRequest.py [new file with mode: 0644]
source/Exceptions.py [new file with mode: 0644]
source/compatibility.py [new file with mode: 0644]
source/configuration [new file with mode: 0644]
source/debug_files/debug_root_ssh_key [new file with mode: 0644]
source/debug_files/sshd_config_v2 [new file with mode: 0644]
source/debug_files/sshd_config_v3 [new file with mode: 0644]
source/merge_hw_tables.py [new file with mode: 0755]
source/notify_messages.py [new file with mode: 0644]
source/steps/AuthenticateWithPLC.py [new file with mode: 0644]
source/steps/ChainBootNode.py [new file with mode: 0644]
source/steps/CheckForNewDisks.py [new file with mode: 0644]
source/steps/CheckHardwareRequirements.py [new file with mode: 0644]
source/steps/ConfirmInstallWithUser.py [new file with mode: 0644]
source/steps/GetAndUpdateNodeDetails.py [new file with mode: 0644]
source/steps/InitializeBootManager.py [new file with mode: 0644]
source/steps/InstallBase.py [new file with mode: 0644]
source/steps/InstallBootstrapRPM.py [new file with mode: 0644]
source/steps/InstallBuildVServer.py [new file with mode: 0644]
source/steps/InstallInit.py [new file with mode: 0644]
source/steps/InstallNodeInit.py [new file with mode: 0644]
source/steps/InstallPartitionDisks.py [new file with mode: 0644]
source/steps/InstallUninitHardware.py [new file with mode: 0644]
source/steps/InstallWriteConfig.py [new file with mode: 0644]
source/steps/ReadNodeConfiguration.py [new file with mode: 0644]
source/steps/SendHardwareConfigToPLC.py [new file with mode: 0644]
source/steps/StartDebug.py [new file with mode: 0644]
source/steps/UpdateBootStateWithPLC.py [new file with mode: 0644]
source/steps/UpdateNodeConfiguration.py [new file with mode: 0644]
source/steps/ValidateNodeInstall.py [new file with mode: 0644]
source/steps/__init__.py [new file with mode: 0644]
source/systeminfo.py [new file with mode: 0755]
source/utils.py [new file with mode: 0644]

diff --git a/source/BootAPI.py b/source/BootAPI.py
new file mode 100644 (file)
index 0000000..f1d418b
--- /dev/null
@@ -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 (executable)
index 0000000..478c714
--- /dev/null
@@ -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=\</tmp/ifconfig %s" % \
+                  (CURL_PATH, self.OutputFilePath, UPLOAD_LOG_URL)
+        os.system( curl_cmd )
+        
+    
+
+        
+
+
+class BootManager:
+
+    # file containing initial variables/constants
+    VARS_FILE = "configuration"
+
+    
+    def __init__(self, log):
+        # this contains a set of information used and updated
+        # by each step
+        self.VARS= {}
+
+        # the main logging point
+        self.LOG= log
+
+        # set to 1 if we can run after initialization
+        self.CAN_RUN = 0
+             
+        if not self.ReadBMConf():
+            self.LOG.LogEntry( "Unable to read configuration vars." )
+            return
+
+        # find out which directory we are running it, and set a variable
+        # for that. future steps may need to get files out of the bootmanager
+        # directory
+        current_dir= os.getcwd()
+        self.VARS['BM_SOURCE_DIR']= current_dir
+
+        # not sure what the current PATH is set to, replace it with what
+        # we know will work with all the boot cds
+        os.environ['PATH']= string.join(BIN_PATH,":")
+                   
+        self.CAN_RUN= 1
+        
+
+
+
+    def ReadBMConf(self):
+        """
+        read in and store all variables in VARS_FILE into
+        self.VARS
+        
+        each line is in the format name=val (any whitespace around
+        the = is removed. everything after the = to the end of
+        the line is the value
+        """
+        
+        vars_file= file(self.VARS_FILE,'r')
+        for line in vars_file:
+            # if its a comment or a whitespace line, ignore
+            if line[:1] == "#" or string.strip(line) == "":
+                continue
+
+            parts= string.split(line,"=")
+            if len(parts) != 2:
+                self.LOG.LogEntry( "Invalid line in vars file: %s" % line )
+                return 0
+
+            name= string.strip(parts[0])
+            value= string.strip(parts[1])
+
+            self.VARS[name]= value
+
+        return 1
+    
+
+    def Run(self):
+        """
+        core boot manager logic.
+
+        the way errors are handled is as such: if any particular step
+        cannot continue or unexpectibly fails, an exception is thrown.
+        in this case, the boot manager cannot continue running.
+
+        these step functions can also return a 0/1 depending on whether
+        or not it succeeded. In the case of steps like ConfirmInstallWithUser,
+        a 0 is returned and no exception is thrown if the user chose not
+        to confirm the install. The same goes with the CheckHardwareRequirements.
+        If requriements not met, but tests were succesfull, return 0.
+
+        for steps that run within the installer, they are expected to either
+        complete succesfully and return 1, or throw an execption.
+
+        For exact return values and expected operations, see the comments
+        at the top of each of the invididual step functions.
+        """
+        
+        try:
+            InitializeBootManager.Run( self.VARS, self.LOG )
+            ReadNodeConfiguration.Run( self.VARS, self.LOG )
+            AuthenticateWithPLC.Run( self.VARS, self.LOG )
+            GetAndUpdateNodeDetails.Run( self.VARS, self.LOG )
+            
+            if self.VARS['BOOT_STATE'] == 'new' or \
+                   self.VARS['BOOT_STATE'] == 'inst':
+                if not ConfirmInstallWithUser.Run( self.VARS, self.LOG ):
+                    return 0
+                
+                self.VARS['BOOT_STATE']= 'rins'
+                UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
+            
+                if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
+                    self.VARS['BOOT_STATE']= 'dbg'
+                    UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
+                    raise BootManagerException, "Hardware requirements not met."
+
+                self.RunInstaller()
+
+                if ValidateNodeInstall.Run( self.VARS, self.LOG ):
+                    SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
+                    ChainBootNode.Run( self.VARS, self.LOG )
+                else:
+                    self.VARS['BOOT_STATE']= 'dbg'
+                    self.VARS['STATE_CHANGE_NOTIFY']= 1
+                    self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
+                              notify_messages.MSG_NODE_NOT_INSTALLED
+                    UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
+                    
+
+            elif self.VARS['BOOT_STATE'] == 'rins':
+                if not CheckHardwareRequirements.Run( self.VARS, self.LOG ):
+                    self.VARS['BOOT_STATE']= 'dbg'
+                    UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
+                    raise BootManagerException, "Hardware requirements not met."
+                
+                self.RunInstaller()
+
+                if ValidateNodeInstall.Run( self.VARS, self.LOG ):
+                    SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
+                    ChainBootNode.Run( self.VARS, self.LOG )
+                else:
+                    self.VARS['BOOT_STATE']= 'dbg'
+                    self.VARS['STATE_CHANGE_NOTIFY']= 1
+                    self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
+                              notify_messages.MSG_NODE_NOT_INSTALLED
+                    UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
+
+            elif self.VARS['BOOT_STATE'] == 'boot':
+                if ValidateNodeInstall.Run( self.VARS, self.LOG ):
+                    UpdateNodeConfiguration.Run( self.VARS, self.LOG )
+                    CheckForNewDisks.Run( self.VARS, self.LOG )
+                    SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
+                    ChainBootNode.Run( self.VARS, self.LOG )
+                else:
+                    self.VARS['BOOT_STATE']= 'dbg'
+                    self.VARS['STATE_CHANGE_NOTIFY']= 1
+                    self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
+                              notify_messages.MSG_NODE_NOT_INSTALLED
+                    UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
+                    
+            elif self.VARS['BOOT_STATE'] == 'dbg':
+                StartDebug.Run( self.VARS, self.LOG )
+
+        except KeyError, e:
+            self.LOG.write( "\n\nKeyError while running: %s\n" % str(e) )
+        except BootManagerException, e:
+            self.LOG.write( "\n\nException while running: %s\n" % str(e) )
+        
+        return 1
+            
+
+            
+    def RunInstaller(self):
+        """
+        since the installer can be invoked at more than one place
+        in the boot manager logic, seperate the steps necessary
+        to do it here
+        """
+        
+        InstallInit.Run( self.VARS, self.LOG )                    
+        InstallPartitionDisks.Run( self.VARS, self.LOG )            
+        InstallBootstrapRPM.Run( self.VARS, self.LOG )            
+        InstallBase.Run( self.VARS, self.LOG )            
+        InstallWriteConfig.Run( self.VARS, self.LOG )
+        InstallBuildVServer.Run( self.VARS, self.LOG )
+        InstallNodeInit.Run( self.VARS, self.LOG )
+        InstallUninitHardware.Run( self.VARS, self.LOG )
+        
+        self.VARS['BOOT_STATE']= 'boot'
+        self.VARS['STATE_CHANGE_NOTIFY']= 1
+        self.VARS['STATE_CHANGE_NOTIFY_MESSAGE']= \
+                                       notify_messages.MSG_INSTALL_FINISHED
+        UpdateBootStateWithPLC.Run( self.VARS, self.LOG )
+
+        SendHardwareConfigToPLC.Run( self.VARS, self.LOG )
+
+    
+    
+if __name__ == "__main__":
+
+    # set to 0 if no error occurred
+    error= 1
+    
+    # all output goes through this class so we can save it and post
+    # the data back to PlanetLab central
+    LOG= log( LOG_FILE )
+
+    LOG.LogEntry( "BootManager started at: %s" % \
+                  strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) )
+
+    try:
+        bm= BootManager(LOG)
+        if bm.CAN_RUN == 0:
+            LOG.LogEntry( "Unable to initialize BootManager." )
+        else:
+            LOG.LogEntry( "Running version %s of BootManager." %
+                          bm.VARS['VERSION'] )
+            success= bm.Run()
+            if success:
+                LOG.LogEntry( "\nDone!" );
+            else:
+                LOG.LogEntry( "\nError occurred!" );
+
+    except:
+        traceback.print_exc(file=LOG.OutputFile)
+        traceback.print_exc()
+
+    LOG.LogEntry( "BootManager finished at: %s" % \
+                  strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) )
+
+    LOG.Upload()
+    
+    sys.exit(error)
diff --git a/source/BootServerRequest.py b/source/BootServerRequest.py
new file mode 100644 (file)
index 0000000..f37b2ce
--- /dev/null
@@ -0,0 +1,588 @@
+#!/usr/bin/python2
+
+# Copyright (c) 2003 Intel Corporation
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+
+#     * Redistributions in binary form must reproduce the above
+#       copyright notice, this list of conditions and the following
+#       disclaimer in the documentation and/or other materials provided
+#       with the distribution.
+
+#     * Neither the name of the Intel Corporation nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
+# YOUR JURISDICTION. It is licensee's responsibility to comply with any
+# export regulations applicable in licensee's jurisdiction. Under
+# CURRENT (May 2000) U.S. export regulations this software is eligible
+# for export from the U.S. and can be downloaded by or otherwise
+# exported or reexported worldwide EXCEPT to U.S. embargoed destinations
+# which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
+# Afghanistan and any other country to which the U.S. has embargoed
+# goods and services.
+
+
+import os, sys
+import re
+import string
+import urllib
+
+# try to load pycurl
+try:
+    import pycurl
+    PYCURL_LOADED= 1
+except:
+    PYCURL_LOADED= 0
+
+
+# if there is no cStringIO, fall back to the original
+try:
+    from cStringIO import StringIO
+except:
+    from StringIO import StringIO
+
+
+
+class BootServerRequest:
+
+    VERBOSE = 0
+
+    # all possible places to check the cdrom mount point.
+    # /mnt/cdrom is typically after the machine has come up,
+    # and /usr is when the boot cd is running
+    CDROM_MOUNT_PATH = ("/mnt/cdrom/","/usr/")
+
+    # this is the server to contact if we don't have a bootcd
+    DEFAULT_BOOT_SERVER = "boot.planet-lab.org"
+
+    BOOTCD_VERSION_FILE = "bootme/ID"
+    BOOTCD_SERVER_FILE = "bootme/BOOTSERVER"
+    BOOTCD_SERVER_CERT_DIR = "bootme/cacert"
+    CACERT_NAME = "cacert.pem"
+    
+    # location of file containing http/https proxy info, if needed
+    PROXY_FILE = '/etc/planetlab/http_proxy'
+
+    # location of curl executable, if pycurl isn't available
+    # and the DownloadFile method is called (backup, only
+    # really need for the boot cd environment where pycurl
+    # doesn't exist
+    CURL_CMD = 'curl'
+
+    # in seconds, how maximum time allowed for connect
+    DEFAULT_CURL_CONNECT_TIMEOUT = 30
+
+    # in seconds, maximum time allowed for any transfer
+    DEFAULT_CURL_MAX_TRANSFER_TIME = 3600
+
+    CURL_SSL_VERSION = 3
+
+    HTTP_SUCCESS = 200
+
+    # proxy variables
+    USE_PROXY = 0
+    PROXY = 0
+
+    # bootcd variables
+    HAS_BOOTCD = 0
+    BOOTCD_VERSION = ""
+    BOOTSERVER_CERTS= {}
+
+    def __init__(self, verbose=0):
+
+        self.VERBOSE= verbose
+            
+        # see if we have a boot cd mounted by checking for the version file
+        # if HAS_BOOTCD == 0 then either the machine doesn't have
+        # a boot cd, or something else is mounted
+        self.HAS_BOOTCD = 0
+
+        for path in self.CDROM_MOUNT_PATH:
+            self.Message( "Checking existance of boot cd on %s" % path )
+
+            os.system("/bin/mount %s > /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] <partialpath>
+Options:
+ -c/--checkcert        Check SSL certs. Ignored if -s/--ssl missing.
+ -h/--help             This help text
+ -o/--output <file>    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 (file)
index 0000000..fc81fd3
--- /dev/null
@@ -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 (file)
index 0000000..b2e7f02
--- /dev/null
@@ -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 (file)
index 0000000..fc8f872
--- /dev/null
@@ -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 (file)
index 0000000..0f4105e
--- /dev/null
@@ -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 (file)
index 0000000..76e0092
--- /dev/null
@@ -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 (file)
index 0000000..2a8f428
--- /dev/null
@@ -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 (executable)
index 0000000..213ac00
--- /dev/null
@@ -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: <dependencies>
+
+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 <modules.dep> <modules.pcimap> " \
+               "<pcitable> [<output>]" )
+        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 (file)
index 0000000..b33dabd
--- /dev/null
@@ -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 (file)
index 0000000..bad3c16
--- /dev/null
@@ -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 (file)
index 0000000..9cc16b8
--- /dev/null
@@ -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 (file)
index 0000000..51ed6a3
--- /dev/null
@@ -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 (file)
index 0000000..1344161
--- /dev/null
@@ -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 (file)
index 0000000..3773ea9
--- /dev/null
@@ -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 (file)
index 0000000..48416fb
--- /dev/null
@@ -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 (file)
index 0000000..c129d63
--- /dev/null
@@ -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 (file)
index 0000000..54d5735
--- /dev/null
@@ -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 (file)
index 0000000..6d3e22d
--- /dev/null
@@ -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 (file)
index 0000000..1f63179
--- /dev/null
@@ -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 (file)
index 0000000..aa76186
--- /dev/null
@@ -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 (file)
index 0000000..32319e6
--- /dev/null
@@ -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 (file)
index 0000000..02245c2
--- /dev/null
@@ -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 (file)
index 0000000..7a28612
--- /dev/null
@@ -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 (file)
index 0000000..d7fddcc
--- /dev/null
@@ -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 (file)
index 0000000..3fcc414
--- /dev/null
@@ -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 (file)
index 0000000..4d8fa0c
--- /dev/null
@@ -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 (file)
index 0000000..ebb7858
--- /dev/null
@@ -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 (file)
index 0000000..417dd12
--- /dev/null
@@ -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= "<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 (file)
index 0000000..f7830e5
--- /dev/null
@@ -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 (file)
index 0000000..db4680c
--- /dev/null
@@ -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 (file)
index 0000000..2a7a663
--- /dev/null
@@ -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 (executable)
index 0000000..2faf3c2
--- /dev/null
@@ -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 <dev> 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 <disk> 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:
+        <install_root>/usr/share/hwdata/pcitable
+        <install_root>/lib/modules/(first entry)/modules.pcimap
+        <install_root>/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 (file)
index 0000000..b9a3746
--- /dev/null
@@ -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
+