fix make sync for the current state of the test infra
[bootmanager.git] / source / BootServerRequest.py
index f37b2ce..88cd081 100644 (file)
@@ -1,51 +1,16 @@
-#!/usr/bin/python2
-
+#!/usr/bin/python
+#
 # Copyright (c) 2003 Intel Corporation
 # All rights reserved.
-
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-
-#     * Redistributions of source code must retain the above copyright
-#       notice, this list of conditions and the following disclaimer.
-
-#     * Redistributions in binary form must reproduce the above
-#       copyright notice, this list of conditions and the following
-#       disclaimer in the documentation and/or other materials provided
-#       with the distribution.
-
-#     * Neither the name of the Intel Corporation nor the names of its
-#       contributors may be used to endorse or promote products derived
-#       from this software without specific prior written permission.
-
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-# EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
-# YOUR JURISDICTION. It is licensee's responsibility to comply with any
-# export regulations applicable in licensee's jurisdiction. Under
-# CURRENT (May 2000) U.S. export regulations this software is eligible
-# for export from the U.S. and can be downloaded by or otherwise
-# exported or reexported worldwide EXCEPT to U.S. embargoed destinations
-# which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
-# Afghanistan and any other country to which the U.S. has embargoed
-# goods and services.
-
+#
+# Copyright (c) 2004-2006 The Trustees of Princeton University
+# All rights reserved.
 
 import os, sys
 import re
 import string
 import urllib
+import tempfile
 
 # try to load pycurl
 try:
@@ -71,46 +36,29 @@ class BootServerRequest:
     # /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/")
+    BOOTSERVER_CERTS= {}
+    MONITORSERVER_CERTS= {}
+    BOOTCD_VERSION=""
+    HTTP_SUCCESS=200
+    HAS_BOOTCD=0
+    USE_PROXY=0
+    PROXY=0
 
-    # 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'
-
+    # 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
     # 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'
+    CURL_SSL_VERSION=3
 
-    # 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):
+    def __init__(self, vars, verbose=0):
 
         self.VERBOSE= verbose
+        self.VARS=vars
             
         # 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
@@ -122,7 +70,7 @@ class BootServerRequest:
 
             os.system("/bin/mount %s > /dev/null 2>&1" % path )
                 
-            version_file= path + self.BOOTCD_VERSION_FILE
+            version_file= self.VARS['BOOTCD_VERSION_FILE'] % {'path' : path}
             self.Message( "Looking for version file %s" % version_file )
 
             if os.access(version_file, os.R_OK) == 0:
@@ -132,7 +80,6 @@ class BootServerRequest:
                 self.HAS_BOOTCD=1
                 break
 
-
         if self.HAS_BOOTCD:
 
             # check the version of the boot cd, and locate the certs
@@ -151,29 +98,34 @@ class BootServerRequest:
             # 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()
+            self.Message( "Getting server from configuration" )
             
+            bootservers= [ self.VARS['BOOT_SERVER'] ]
             for bootserver in bootservers:
                 bootserver = string.strip(bootserver)
-                cacert_path= "%s/%s/%s/%s" % \
-                             (path,self.BOOTCD_SERVER_CERT_DIR,
-                              bootserver,self.CACERT_NAME)
+                cacert_path= "%s/%s/%s" % \
+                             (self.VARS['SERVER_CERT_DIR'] % {'path' : path},
+                              bootserver,self.VARS['CACERT_NAME'])
                 if os.access(cacert_path, os.R_OK):
                     self.BOOTSERVER_CERTS[bootserver]= cacert_path
 
+            monitorservers= [ self.VARS['MONITOR_SERVER'] ]
+            for monitorserver in monitorservers:
+                monitorserver = string.strip(monitorserver)
+                cacert_path= "%s/%s/%s" % \
+                             (self.VARS['SERVER_CERT_DIR'] % {'path' : path},
+                              monitorserver,self.VARS['CACERT_NAME'])
+                if os.access(cacert_path, os.R_OK):
+                    self.MONITORSERVER_CERTS[monitorserver]= cacert_path
+
             self.Message( "Set of servers to contact: %s" %
                           str(self.BOOTSERVER_CERTS) )
+            self.Message( "Set of servers to upload to: %s" %
+                          str(self.MONITORSERVER_CERTS) )
         else:
             self.Message( "Using default boot server address." )
-            self.BOOTSERVER_CERTS[self.DEFAULT_BOOT_SERVER]= ""
+            self.BOOTSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']]= ""
+            self.MONITORSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']]= ""
 
 
     def CheckProxy( self ):
@@ -181,11 +133,11 @@ class BootServerRequest:
         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())
+        if os.access(self.VARS['PROXY_FILE'], os.R_OK) and \
+               os.path.isfile(self.VARS['PROXY_FILE']):
+            self.PROXY= string.strip(file(self.VARS['PROXY_FILE'],'r').readline())
             self.USE_PROXY= 1
-            self.Message( "Using proxy %s." % self.PROXY )
+            self.Message( "Using proxy %s." % self.PROXY)
         else:
             self.Message( "Not using any proxy." )
 
@@ -210,126 +162,42 @@ class BootServerRequest:
     def MakeRequest( self, PartialPath, GetVars,
                      PostVars, DoSSL, DoCertCheck,
                      ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
-                     MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME):
+                     MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME,
+                     FormData= None):
 
-        if PYCURL_LOADED == 0:
-            self.Error( "MakeRequest method requires pycurl." )
-            return None
+        (fd, buffer_name) = tempfile.mkstemp("MakeRequest-XXXXXX")
+        os.close(fd)
+        buffer = open(buffer_name, "w+b")
 
-        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
+        # the file "buffer_name" will be deleted by DownloadFile()
 
-        # didn't pass a path in? just get the root doc
-        if PartialPath == "":
-            PartialPath= "/"
+        ok = self.DownloadFile(PartialPath, GetVars, PostVars,
+                               DoSSL, DoCertCheck, buffer_name,
+                               ConnectTimeout,
+                               MaxTransferTime,
+                               FormData)
 
-        # 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 )
+        # check the ok code, return the string only if it was successfull
+        if ok:
+            buffer.seek(0)
+            ret = buffer.read()
         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 )
+            ret = None
+
+        buffer.close()
+        try:
+            # just in case it is not deleted by DownloadFile()
+            os.unlink(buffer_name)
+        except OSError:
+            pass
             
-            # 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
-   
-
+        return ret
 
     def DownloadFile(self, PartialPath, GetVars, PostVars,
                      DoSSL, DoCertCheck, DestFilePath,
                      ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
-                     MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME):
+                     MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME,
+                     FormData= None):
 
         self.Message( "Attempting to retrieve %s" % PartialPath )
 
@@ -365,11 +233,15 @@ class BootServerRequest:
 
         # now, attempt to make the request, starting at the first
         # server in the list
+        if FormData:
+            cert_list = self.MONITORSERVER_CERTS
+        else:
+            cert_list = self.BOOTSERVER_CERTS
         
-        for server in self.BOOTSERVER_CERTS:
+        for server in cert_list:
             self.Message( "Contacting server %s." % server )
                         
-            certpath = self.BOOTSERVER_CERTS[server]
+            certpath = cert_list[server]
 
             
             # output what we are going to be doing
@@ -424,6 +296,10 @@ class BootServerRequest:
                 if dopostdata:
                     curl.setopt(pycurl.POSTFIELDS, postdata)
 
+                # setup multipart/form-data upload
+                if FormData:
+                    curl.setopt(pycurl.HTTPPOST, FormData)
+
                 curl.setopt(pycurl.URL, url)
             else:
 
@@ -439,6 +315,9 @@ class BootServerRequest:
                 if dopostdata:
                     cmdline = cmdline + "--data '" + postdata + "' "
 
+                if FormData:
+                    cmdline = cmdline + "".join(["--form '" + field + "' " for field in FormData])
+
                 if not self.VERBOSE:
                     cmdline = cmdline + "--silent "
                     
@@ -447,7 +326,6 @@ class BootServerRequest:
 
                 if DoSSL:
                     cmdline = cmdline + "--sslv%d " % self.CURL_SSL_VERSION
-
                     if DoCertCheck:
                         cmdline = cmdline + "--cacert %s " % certpath
                  
@@ -491,7 +369,7 @@ class BootServerRequest:
                 if not outfile.closed:
                     try:
                         os.unlink(DestFilePath)
-                        outfile.close
+                        outfile.close()
                     except OSError:
                         pass