@@ -34,6 +34,7 @@ import base64
3434import hmac
3535import traceback
3636import urllib2
37+ from xml .dom .minidom import parseString
3738
3839import XenAPIPlugin
3940sys .path .extend (["/opt/xensource/sm/" ])
@@ -260,15 +261,73 @@ class S3Client(object):
260261 sha ).digest ())[:- 1 ]
261262
262263 return signature , request_date
264+
265+ def getText (self , nodelist ):
266+ rc = []
267+ for node in nodelist :
268+ if node .nodeType == node .TEXT_NODE :
269+ rc .append (node .data )
270+ return '' .join (rc )
271+
272+ def multiUpload (self , bucket , key , src_fileName , chunkSize = 5 * 1024 * 1024 ):
273+ uploadId = {}
274+ def readInitalMultipart (response ):
275+ data = response .read ()
276+ xmlResult = parseString (data )
277+ result = xmlResult .getElementsByTagName ("InitiateMultipartUploadResult" )[0 ]
278+ upload = result .getElementsByTagName ("UploadId" )[0 ]
279+ uploadId ["0" ] = upload .childNodes [0 ].data
280+
281+ self .do_operation ('POST' , bucket , key + "?uploads" , fn_read = readInitalMultipart )
282+
283+ fileSize = os .path .getsize (src_fileName )
284+ parts = fileSize / chunkSize + ((fileSize % chunkSize ) and 1 )
285+ part = 1
286+ srcFile = open (src_fileName , 'rb' )
287+ etags = []
288+ while part <= parts :
289+ offset = part - 1
290+ size = min (fileSize - offset * chunkSize , chunkSize )
291+ headers = {
292+ self .HEADER_CONTENT_LENGTH : size
293+ }
294+ def send_body (connection ):
295+ srcFile .seek (offset * chunkSize )
296+ block = srcFile .read (size )
297+ connection .send (block )
298+ def read_multiPart (response ):
299+ etag = response .getheader ('ETag' )
300+ etags .append ((part , etag ))
301+ self .do_operation ("PUT" , bucket , "%s?partNumber=%s&uploadId=%s" % (key , part , uploadId ["0" ]), headers , send_body , read_multiPart )
302+ part = part + 1
303+ srcFile .close ()
304+
305+ data = []
306+ partXml = "<Part><PartNumber>%i</PartNumber><ETag>%s</ETag></Part>"
307+ for etag in etags :
308+ data .append (partXml % etag )
309+ msg = "<CompleteMultipartUpload>%s</CompleteMultipartUpload>" % ("" .join (data ))
310+ size = len (msg )
311+ headers = {
312+ self .HEADER_CONTENT_LENGTH : size
313+ }
314+ def send_complete_multipart (connection ):
315+ connection .send (msg )
316+ self .do_operation ("POST" , bucket , "%s?uploadId=%s" % (key , uploadId ["0" ]), headers , send_complete_multipart )
263317
264- def put (self , bucket , key , src_filename ):
318+ def put (self , bucket , key , src_filename , maxSingleUpload ):
265319
266320 if not os .path .isfile (src_filename ):
267321 raise Exception (
268322 "Attempt to put " + src_filename + " that does not exist." )
269323
324+ size = os .path .getsize (src_filename )
325+ if size > maxSingleUpload or maxSingleUpload == 0 :
326+ return self .multiUpload (bucket , key , src_filename )
327+
270328 headers = {
271329 self .HEADER_CONTENT_MD5 : compute_md5 (src_filename ),
330+
272331 self .HEADER_CONTENT_TYPE : 'application/octet-stream' ,
273332 self .HEADER_CONTENT_LENGTH : str (os .stat (src_filename ).st_size ),
274333 }
@@ -323,6 +382,7 @@ def parseArguments(args):
323382 bucket = args ['bucket' ]
324383 key = args ['key' ]
325384 filename = args ['filename' ]
385+ maxSingleUploadBytes = int (args ["maxSingleUploadSizeInBytes" ])
326386
327387 if is_blank (operation ):
328388 raise ValueError ('An operation must be specified.' )
@@ -336,18 +396,18 @@ def parseArguments(args):
336396 if is_blank (filename ):
337397 raise ValueError ('A filename must be specified.' )
338398
339- return client , operation , bucket , key , filename
399+ return client , operation , bucket , key , filename , maxSingleUploadBytes
340400
341401
342402@echo
343403def s3 (session , args ):
344404
345- client , operation , bucket , key , filename = parseArguments (args )
405+ client , operation , bucket , key , filename , maxSingleUploadBytes = parseArguments (args )
346406
347407 try :
348408
349409 if operation == 'put' :
350- client .put (bucket , key , filename )
410+ client .put (bucket , key , filename , maxSingleUploadBytes )
351411 elif operation == 'get' :
352412 client .get (bucket , key , filename )
353413 elif operation == 'delete' :
0 commit comments