github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/swiftclient/object.go (about)

     1  // Swift Object-specific API access implementation
     2  
     3  package swiftclient
     4  
     5  import (
     6  	"fmt"
     7  	"runtime"
     8  	"strconv"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/creachadair/cityhash"
    13  	"github.com/swiftstack/sortedmap"
    14  
    15  	"github.com/swiftstack/ProxyFS/blunder"
    16  	"github.com/swiftstack/ProxyFS/evtlog"
    17  	"github.com/swiftstack/ProxyFS/logger"
    18  	"github.com/swiftstack/ProxyFS/stats"
    19  	"github.com/swiftstack/ProxyFS/trackedlock"
    20  )
    21  
    22  func objectContentLengthWithRetry(accountName string, containerName string, objectName string) (uint64, error) {
    23  	// request is a function that, through the miracle of closure, calls
    24  	// objectContentLength() with the paramaters passed to this function,
    25  	// stashes the relevant return values into the local variables of this
    26  	// function, and then returns err and whether it is retriable to
    27  	// RequestWithRetry()
    28  	var (
    29  		length uint64
    30  		err    error
    31  	)
    32  	request := func() (bool, error) {
    33  		var err error
    34  		length, err = objectContentLength(accountName, containerName, objectName)
    35  		return true, err
    36  	}
    37  
    38  	var (
    39  		retryObj *RetryCtrl = NewRetryCtrl(
    40  			globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
    41  		opname string = fmt.Sprintf("swiftclient.objectContentLength(\"%v/%v/%v\")",
    42  			accountName, containerName, objectName)
    43  
    44  		statnm requestStatistics = requestStatistics{
    45  			retryCnt:          &stats.SwiftObjContentLengthRetryOps,
    46  			retrySuccessCnt:   &stats.SwiftObjContentLengthRetrySuccessOps,
    47  			clientRequestTime: &globals.ObjectContentLengthUsec,
    48  			clientFailureCnt:  &globals.ObjectContentLengthFailure,
    49  			swiftRequestTime:  &globals.SwiftObjectContentLengthUsec,
    50  			swiftRetryOps:     &globals.SwiftObjectContentLengthRetryOps,
    51  		}
    52  	)
    53  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
    54  	return length, err
    55  }
    56  
    57  func objectContentLength(accountName string, containerName string, objectName string) (length uint64, err error) {
    58  	var (
    59  		connection         *connectionStruct
    60  		contentLengthAsInt int
    61  		fsErr              blunder.FsError
    62  		headers            map[string][]string
    63  		httpPayload        string
    64  		httpStatus         int
    65  		isError            bool
    66  	)
    67  
    68  	connection, err = acquireNonChunkedConnection()
    69  	if err != nil {
    70  		// acquireNonChunkedConnection()/openConnection() logged a warning
    71  		return
    72  	}
    73  
    74  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "HEAD", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), nil)
    75  	if nil != err {
    76  		releaseNonChunkedConnection(connection, false)
    77  		err = blunder.AddError(err, blunder.BadHTTPHeadError)
    78  		logger.WarnfWithError(err, "swiftclient.objectContentLength(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
    79  		return
    80  	}
    81  
    82  	httpStatus, headers, err = readHTTPStatusAndHeaders(connection.tcpConn)
    83  	if nil != err {
    84  		releaseNonChunkedConnection(connection, false)
    85  		err = blunder.AddError(err, blunder.BadHTTPHeadError)
    86  		logger.WarnfWithError(err, "swiftclient.objectContentLength(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
    87  		return
    88  	}
    89  	evtlog.Record(evtlog.FormatObjectHead, accountName, containerName, objectName, uint32(httpStatus))
    90  	isError, fsErr = httpStatusIsError(httpStatus)
    91  	if isError {
    92  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, headers)
    93  		releaseNonChunkedConnection(connection, false)
    94  		err = blunder.NewError(fsErr, "HEAD %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
    95  		err = blunder.AddHTTPCode(err, httpStatus)
    96  		logger.WarnfWithError(err, "swiftclient.objectContentLength(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
    97  		return
    98  	}
    99  
   100  	contentLengthAsInt, err = parseContentLength(headers)
   101  	if nil != err {
   102  		releaseNonChunkedConnection(connection, false)
   103  		err = blunder.AddError(err, blunder.BadHTTPHeadError)
   104  		logger.WarnfWithError(err, "swiftclient.objectContentLength(\"%v/%v/%v\") got parseContentLength() error", accountName, containerName, objectName)
   105  		return
   106  	}
   107  
   108  	releaseNonChunkedConnection(connection, parseConnection(headers))
   109  
   110  	length = uint64(contentLengthAsInt)
   111  
   112  	stats.IncrementOperations(&stats.SwiftObjContentLengthOps)
   113  
   114  	return
   115  }
   116  
   117  func objectCopy(srcAccountName string, srcContainerName string, srcObjectName string, dstAccountName string, dstContainerName string, dstObjectName string, chunkedCopyContext ChunkedCopyContext) (err error) {
   118  	var (
   119  		chunk                []byte
   120  		chunkSize            uint64
   121  		dstChunkedPutContext ChunkedPutContext
   122  		srcObjectPosition    = uint64(0)
   123  		srcObjectSize        uint64
   124  		clientReqTime        time.Time
   125  	)
   126  
   127  	clientReqTime = time.Now()
   128  	defer func() {
   129  		elapsedUsec := time.Since(clientReqTime).Nanoseconds() / time.Microsecond.Nanoseconds()
   130  		globals.ObjectCopyUsec.Add(uint64(elapsedUsec))
   131  	}()
   132  
   133  	srcObjectSize, err = objectContentLengthWithRetry(srcAccountName, srcContainerName, srcObjectName)
   134  	if nil != err {
   135  		return
   136  	}
   137  
   138  	dstChunkedPutContext, err = objectFetchChunkedPutContextWithRetry(dstAccountName, dstContainerName, dstObjectName, "")
   139  	if nil != err {
   140  		return
   141  	}
   142  
   143  	for srcObjectPosition < srcObjectSize {
   144  		chunkSize = chunkedCopyContext.BytesRemaining(srcObjectSize - srcObjectPosition)
   145  		if 0 == chunkSize {
   146  			err = dstChunkedPutContext.Close()
   147  			return
   148  		}
   149  
   150  		if (srcObjectPosition + chunkSize) > srcObjectSize {
   151  			chunkSize = srcObjectSize - srcObjectPosition
   152  
   153  			chunk, err = objectTailWithRetry(srcAccountName, srcContainerName, srcObjectName, chunkSize)
   154  		} else {
   155  			chunk, err = objectGetWithRetry(srcAccountName, srcContainerName, srcObjectName, srcObjectPosition, chunkSize)
   156  		}
   157  
   158  		srcObjectPosition += chunkSize
   159  
   160  		err = dstChunkedPutContext.SendChunk(chunk)
   161  		if nil != err {
   162  			return
   163  		}
   164  	}
   165  
   166  	err = dstChunkedPutContext.Close()
   167  
   168  	stats.IncrementOperations(&stats.SwiftObjCopyOps)
   169  
   170  	return
   171  }
   172  
   173  func objectDelete(accountName string, containerName string, objectName string, operationOptions OperationOptions) (err error) {
   174  	if (operationOptions & SkipRetry) == SkipRetry {
   175  		err = objectDeleteOneTime(accountName, containerName, objectName)
   176  	} else {
   177  		// request is a function that, through the miracle of closure, calls
   178  		// objectDelete() with the paramaters passed to this function, stashes
   179  		// the relevant return values into the local variables of this function,
   180  		// and then returns err and whether it is retriable to RequestWithRetry()
   181  		request := func() (bool, error) {
   182  			var err error
   183  			err = objectDeleteOneTime(accountName, containerName, objectName)
   184  			return true, err
   185  		}
   186  
   187  		var (
   188  			retryObj *RetryCtrl = NewRetryCtrl(
   189  				globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
   190  			opname string = fmt.Sprintf(
   191  				"swiftclient.objectDeleteOneTime(\"%v/%v/%v\")", accountName, containerName, objectName)
   192  
   193  			statnm requestStatistics = requestStatistics{
   194  				retryCnt:          &stats.SwiftObjDeleteRetryOps,
   195  				retrySuccessCnt:   &stats.SwiftObjDeleteRetrySuccessOps,
   196  				clientRequestTime: &globals.ObjectDeleteUsec,
   197  				clientFailureCnt:  &globals.ObjectDeleteFailure,
   198  				swiftRequestTime:  &globals.SwiftObjectDeleteUsec,
   199  				swiftRetryOps:     &globals.SwiftObjectDeleteRetryOps,
   200  			}
   201  		)
   202  		err = retryObj.RequestWithRetry(request, &opname, &statnm)
   203  	}
   204  
   205  	return
   206  }
   207  
   208  func objectDeleteOneTime(accountName string, containerName string, objectName string) (err error) {
   209  	var (
   210  		connection  *connectionStruct
   211  		fsErr       blunder.FsError
   212  		headers     map[string][]string
   213  		httpPayload string
   214  		httpStatus  int
   215  		isError     bool
   216  	)
   217  
   218  	connection, err = acquireNonChunkedConnection()
   219  	if err != nil {
   220  		// acquireNonChunkedConnection()/openConnection() logged a warning
   221  		return
   222  	}
   223  
   224  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "DELETE", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), nil)
   225  	if nil != err {
   226  		releaseNonChunkedConnection(connection, false)
   227  		err = blunder.AddError(err, blunder.BadHTTPDeleteError)
   228  		logger.WarnfWithError(err, "swiftclient.objectDelete(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
   229  		return
   230  	}
   231  
   232  	httpStatus, headers, err = readHTTPStatusAndHeaders(connection.tcpConn)
   233  	if nil != err {
   234  		releaseNonChunkedConnection(connection, false)
   235  		err = blunder.AddError(err, blunder.BadHTTPDeleteError)
   236  		logger.WarnfWithError(err, "swiftclient.objectDelete(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
   237  		return
   238  	}
   239  	evtlog.Record(evtlog.FormatObjectDelete, accountName, containerName, objectName, uint32(httpStatus))
   240  	isError, fsErr = httpStatusIsError(httpStatus)
   241  	if isError {
   242  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, headers)
   243  		releaseNonChunkedConnection(connection, false)
   244  		err = blunder.NewError(fsErr, "DELETE %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
   245  		err = blunder.AddHTTPCode(err, httpStatus)
   246  		logger.WarnfWithError(err, "swiftclient.objectDelete(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
   247  		return
   248  	}
   249  
   250  	releaseNonChunkedConnection(connection, parseConnection(headers))
   251  
   252  	stats.IncrementOperations(&stats.SwiftObjDeleteOps)
   253  
   254  	return
   255  }
   256  
   257  func objectGetWithRetry(accountName string, containerName string, objectName string,
   258  	offset uint64, length uint64) ([]byte, error) {
   259  
   260  	// request is a function that, through the miracle of closure, calls
   261  	// objectGet() with the paramaters passed to this function, stashes the
   262  	// relevant return values into the local variables of this function, and
   263  	// then returns err and whether it is retriable to RequestWithRetry()
   264  	var (
   265  		buf []byte
   266  		err error
   267  	)
   268  	request := func() (bool, error) {
   269  		var err error
   270  		buf, err = objectGet(accountName, containerName, objectName, offset, length)
   271  		return true, err
   272  	}
   273  
   274  	var (
   275  		retryObj *RetryCtrl = NewRetryCtrl(
   276  			globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
   277  		opname string = fmt.Sprintf(
   278  			"swiftclient.objectGet(\"%v/%v/%v\")", accountName, containerName, objectName)
   279  
   280  		statnm requestStatistics = requestStatistics{
   281  			retryCnt:          &stats.SwiftObjGetRetryOps,
   282  			retrySuccessCnt:   &stats.SwiftObjGetRetrySuccessOps,
   283  			clientRequestTime: &globals.ObjectGetUsec,
   284  			clientFailureCnt:  &globals.ObjectGetFailure,
   285  			swiftRequestTime:  &globals.SwiftObjectGetUsec,
   286  			swiftRetryOps:     &globals.SwiftObjectGetRetryOps,
   287  		}
   288  	)
   289  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
   290  	return buf, err
   291  }
   292  
   293  func objectGet(accountName string, containerName string, objectName string, offset uint64, length uint64) (buf []byte, err error) {
   294  	var (
   295  		connection    *connectionStruct
   296  		chunk         []byte
   297  		contentLength int
   298  		fsErr         blunder.FsError
   299  		headers       map[string][]string
   300  		httpPayload   string
   301  		httpStatus    int
   302  		isError       bool
   303  	)
   304  
   305  	headers = make(map[string][]string)
   306  	headers["Range"] = []string{"bytes=" + strconv.FormatUint(offset, 10) + "-" + strconv.FormatUint((offset+length-1), 10)}
   307  
   308  	connection, err = acquireNonChunkedConnection()
   309  	if err != nil {
   310  		// acquireNonChunkedConnection()/openConnection() logged a warning
   311  		return
   312  	}
   313  
   314  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "GET", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), headers)
   315  	if nil != err {
   316  		releaseNonChunkedConnection(connection, false)
   317  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   318  		logger.WarnfWithError(err, "swiftclient.objectGet(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
   319  		return
   320  	}
   321  
   322  	httpStatus, headers, err = readHTTPStatusAndHeaders(connection.tcpConn)
   323  	if nil != err {
   324  		releaseNonChunkedConnection(connection, false)
   325  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   326  		logger.WarnfWithError(err, "swiftclient.objectGet(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
   327  		return
   328  	}
   329  	evtlog.Record(evtlog.FormatObjectGet, accountName, containerName, objectName, offset, length, uint32(httpStatus))
   330  	isError, fsErr = httpStatusIsError(httpStatus)
   331  	if isError {
   332  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, headers)
   333  		releaseNonChunkedConnection(connection, false)
   334  		err = blunder.NewError(fsErr, "GET %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
   335  		err = blunder.AddHTTPCode(err, httpStatus)
   336  		logger.WarnfWithError(err, "swiftclient.objectGet(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
   337  		return
   338  	}
   339  
   340  	if parseTransferEncoding(headers) {
   341  		buf = make([]byte, 0)
   342  		for {
   343  			chunk, err = readHTTPChunk(connection.tcpConn)
   344  			if nil != err {
   345  				releaseNonChunkedConnection(connection, false)
   346  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   347  				logger.WarnfWithError(err, "swiftclient.objectGet(\"%v/%v/%v\") got readHTTPChunk() error", accountName, containerName, objectName)
   348  				return
   349  			}
   350  
   351  			if 0 == len(chunk) {
   352  				break
   353  			}
   354  
   355  			buf = append(buf, chunk...)
   356  		}
   357  	} else {
   358  		contentLength, err = parseContentLength(headers)
   359  		if nil != err {
   360  			releaseNonChunkedConnection(connection, false)
   361  			err = blunder.AddError(err, blunder.BadHTTPGetError)
   362  			logger.WarnfWithError(err, "swiftclient.objectGet(\"%v/%v/%v\") got parseContentLength() error", accountName, containerName, objectName)
   363  			return
   364  		}
   365  
   366  		if 0 == contentLength {
   367  			buf = make([]byte, 0)
   368  		} else {
   369  			buf, err = readBytesFromTCPConn(connection.tcpConn, contentLength)
   370  			if nil != err {
   371  				releaseNonChunkedConnection(connection, false)
   372  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   373  				logger.WarnfWithError(err, "swiftclient.objectGet(\"%v/%v/%v\") got readBytesFromTCPConn() error", accountName, containerName, objectName)
   374  				return
   375  			}
   376  		}
   377  	}
   378  
   379  	releaseNonChunkedConnection(connection, parseConnection(headers))
   380  
   381  	stats.IncrementOperationsAndBucketedBytes(stats.SwiftObjGet, uint64(len(buf)))
   382  	globals.ObjectGetBytes.Add(uint64(len(buf)))
   383  	return
   384  }
   385  
   386  func objectHeadWithRetry(accountName string, containerName string, objectName string) (map[string][]string, error) {
   387  	// request is a function that, through the miracle of closure, calls
   388  	// objectHead() with the paramaters passed to this function, stashes
   389  	// the relevant return values into the local variables of this function,
   390  	// and then returns err and whether it is retriable to RequestWithRetry()
   391  	var (
   392  		headers map[string][]string
   393  		err     error
   394  	)
   395  	request := func() (bool, error) {
   396  		var err error
   397  		headers, err = objectHead(accountName, containerName, objectName)
   398  		return true, err
   399  	}
   400  
   401  	var (
   402  		retryObj *RetryCtrl = NewRetryCtrl(
   403  			globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
   404  		opname string = fmt.Sprintf(
   405  			"swiftclient.objectHead(\"%v/%v/%v\")", accountName, containerName, objectName)
   406  
   407  		statnm requestStatistics = requestStatistics{
   408  			retryCnt:          &stats.SwiftObjHeadRetryOps,
   409  			retrySuccessCnt:   &stats.SwiftObjHeadRetrySuccessOps,
   410  			clientRequestTime: &globals.ObjectHeadUsec,
   411  			clientFailureCnt:  &globals.ObjectHeadFailure,
   412  			swiftRequestTime:  &globals.SwiftObjectHeadUsec,
   413  			swiftRetryOps:     &globals.SwiftObjectHeadRetryOps,
   414  		}
   415  	)
   416  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
   417  	return headers, err
   418  }
   419  
   420  func objectHead(accountName string, containerName string, objectName string) (headers map[string][]string, err error) {
   421  	var (
   422  		connection  *connectionStruct
   423  		fsErr       blunder.FsError
   424  		httpPayload string
   425  		httpStatus  int
   426  		isError     bool
   427  	)
   428  
   429  	connection, err = acquireNonChunkedConnection()
   430  	if err != nil {
   431  		// acquireNonChunkedConnection()/openConnection() logged a warning
   432  		return
   433  	}
   434  
   435  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "HEAD", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), nil)
   436  	if nil != err {
   437  		releaseNonChunkedConnection(connection, false)
   438  		err = blunder.AddError(err, blunder.BadHTTPHeadError)
   439  		logger.WarnfWithError(err, "swiftclient.objectHead(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
   440  		return
   441  	}
   442  
   443  	httpStatus, headers, err = readHTTPStatusAndHeaders(connection.tcpConn)
   444  	if nil != err {
   445  		releaseNonChunkedConnection(connection, false)
   446  		err = blunder.AddError(err, blunder.BadHTTPHeadError)
   447  		logger.WarnfWithError(err, "swiftclient.objectHead(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
   448  		return
   449  	}
   450  	evtlog.Record(evtlog.FormatObjectHead, accountName, containerName, objectName, uint32(httpStatus))
   451  	isError, fsErr = httpStatusIsError(httpStatus)
   452  	if isError {
   453  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, headers)
   454  		releaseNonChunkedConnection(connection, false)
   455  		err = blunder.NewError(fsErr, "HEAD %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
   456  		err = blunder.AddHTTPCode(err, httpStatus)
   457  		logger.WarnfWithError(err, "swiftclient.objectHead(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
   458  		return
   459  	}
   460  
   461  	releaseNonChunkedConnection(connection, parseConnection(headers))
   462  
   463  	stats.IncrementOperations(&stats.SwiftObjHeadOps)
   464  
   465  	return
   466  }
   467  
   468  func objectLoadWithRetry(accountName string, containerName string, objectName string) ([]byte, error) {
   469  	// request is a function that, through the miracle of closure, calls
   470  	// objectLoad() with the paramaters passed to this function, stashes the
   471  	// relevant return values into the local variables of this function, and
   472  	// then returns err and whether it is retriable to RequestWithRetry()
   473  	var (
   474  		buf []byte
   475  		err error
   476  	)
   477  	request := func() (bool, error) {
   478  		var err error
   479  		buf, err = objectLoad(accountName, containerName, objectName)
   480  		return true, err
   481  	}
   482  
   483  	var (
   484  		retryObj *RetryCtrl = NewRetryCtrl(
   485  			globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
   486  		opname string = fmt.Sprintf(
   487  			"swiftclient.objectLoad(\"%v/%v/%v\")", accountName, containerName, objectName)
   488  
   489  		statnm requestStatistics = requestStatistics{
   490  			retryCnt:          &stats.SwiftObjLoadRetryOps,
   491  			retrySuccessCnt:   &stats.SwiftObjLoadRetrySuccessOps,
   492  			clientRequestTime: &globals.ObjectLoadUsec,
   493  			clientFailureCnt:  &globals.ObjectLoadFailure,
   494  			swiftRequestTime:  &globals.SwiftObjectLoadUsec,
   495  			swiftRetryOps:     &globals.SwiftObjectLoadRetryOps,
   496  		}
   497  	)
   498  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
   499  	return buf, err
   500  }
   501  
   502  func objectLoad(accountName string, containerName string, objectName string) (buf []byte, err error) {
   503  	var (
   504  		connection    *connectionStruct
   505  		chunk         []byte
   506  		contentLength int
   507  		fsErr         blunder.FsError
   508  		headers       map[string][]string
   509  		httpPayload   string
   510  		httpStatus    int
   511  		isError       bool
   512  	)
   513  
   514  	connection, err = acquireNonChunkedConnection()
   515  	if err != nil {
   516  		// acquireNonChunkedConnection()/openConnection() logged a warning
   517  		return
   518  	}
   519  
   520  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "GET", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), nil)
   521  	if nil != err {
   522  		releaseNonChunkedConnection(connection, false)
   523  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   524  		logger.WarnfWithError(err, "swiftclient.objectLoad(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
   525  		return
   526  	}
   527  
   528  	httpStatus, headers, err = readHTTPStatusAndHeaders(connection.tcpConn)
   529  	if nil != err {
   530  		releaseNonChunkedConnection(connection, false)
   531  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   532  		logger.WarnfWithError(err, "swiftclient.objectLoad(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
   533  		return
   534  	}
   535  	evtlog.Record(evtlog.FormatObjectLoad, accountName, containerName, objectName, uint32(httpStatus))
   536  	isError, fsErr = httpStatusIsError(httpStatus)
   537  	if isError {
   538  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, headers)
   539  		releaseNonChunkedConnection(connection, false)
   540  		err = blunder.NewError(fsErr, "GET %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
   541  		err = blunder.AddHTTPCode(err, httpStatus)
   542  		logger.WarnfWithError(err, "swiftclient.objectLoad(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
   543  		return
   544  	}
   545  
   546  	if parseTransferEncoding(headers) {
   547  		buf = make([]byte, 0)
   548  		for {
   549  			chunk, err = readHTTPChunk(connection.tcpConn)
   550  			if nil != err {
   551  				releaseNonChunkedConnection(connection, false)
   552  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   553  				logger.WarnfWithError(err, "swiftclient.objectLoad(\"%v/%v/%v\") got readHTTPChunk() error", accountName, containerName, objectName)
   554  				return
   555  			}
   556  
   557  			if 0 == len(chunk) {
   558  				break
   559  			}
   560  
   561  			buf = append(buf, chunk...)
   562  		}
   563  	} else {
   564  		contentLength, err = parseContentLength(headers)
   565  		if nil != err {
   566  			releaseNonChunkedConnection(connection, false)
   567  			err = blunder.AddError(err, blunder.BadHTTPGetError)
   568  			logger.WarnfWithError(err, "swiftclient.objectLoad(\"%v/%v/%v\") got parseContentLength() error", accountName, containerName, objectName)
   569  			return
   570  		}
   571  
   572  		if 0 == contentLength {
   573  			buf = make([]byte, 0)
   574  		} else {
   575  			buf, err = readBytesFromTCPConn(connection.tcpConn, contentLength)
   576  			if nil != err {
   577  				releaseNonChunkedConnection(connection, false)
   578  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   579  				logger.WarnfWithError(err, "swiftclient.objectLoad(\"%v/%v/%v\") got readBytesFromTCPConn() error", accountName, containerName, objectName)
   580  				return
   581  			}
   582  		}
   583  	}
   584  
   585  	releaseNonChunkedConnection(connection, parseConnection(headers))
   586  
   587  	stats.IncrementOperationsAndBucketedBytes(stats.SwiftObjLoad, uint64(len(buf)))
   588  	globals.ObjectLoadBytes.Add(uint64(len(buf)))
   589  	return
   590  }
   591  
   592  func objectPostWithRetry(accountName string, containerName string, objectName string, requestHeaders map[string][]string) (err error) {
   593  	// request is a function that, through the miracle of closure, calls
   594  	// containerPost() with the paramaters passed to this function, stashes
   595  	// the relevant return values into the local variables of this function,
   596  	// and then returns err and whether it is retriable to RequestWithRetry()
   597  	request := func() (bool, error) {
   598  		var err error
   599  		err = objectPost(accountName, containerName, objectName, requestHeaders)
   600  		return true, err
   601  	}
   602  
   603  	var (
   604  		retryObj *RetryCtrl = NewRetryCtrl(globals.retryLimit, globals.retryDelay, globals.retryExpBackoff)
   605  		opname   string     = fmt.Sprintf("swiftclient.objectPost(\"%v/%v/%v\")", accountName, containerName, objectName)
   606  
   607  		statnm requestStatistics = requestStatistics{
   608  			retryCnt:          &stats.SwiftObjPostRetryOps,
   609  			retrySuccessCnt:   &stats.SwiftObjPostRetrySuccessOps,
   610  			clientRequestTime: &globals.ObjectPostUsec,
   611  			clientFailureCnt:  &globals.ObjectPostFailure,
   612  			swiftRequestTime:  &globals.SwiftObjectPostUsec,
   613  			swiftRetryOps:     &globals.SwiftObjectPostRetryOps,
   614  		}
   615  	)
   616  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
   617  	return err
   618  }
   619  
   620  func objectPost(accountName string, containerName string, objectName string, requestHeaders map[string][]string) (err error) {
   621  	var (
   622  		connection      *connectionStruct
   623  		contentLength   int
   624  		fsErr           blunder.FsError
   625  		httpPayload     string
   626  		httpStatus      int
   627  		isError         bool
   628  		responseHeaders map[string][]string
   629  	)
   630  
   631  	connection, err = acquireNonChunkedConnection()
   632  	if err != nil {
   633  		// acquireNonChunkedConnection()/openConnection() logged a warning
   634  		return
   635  	}
   636  
   637  	requestHeaders["Content-Length"] = []string{"0"}
   638  
   639  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "POST", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), requestHeaders)
   640  	if nil != err {
   641  		releaseNonChunkedConnection(connection, false)
   642  		err = blunder.AddError(err, blunder.BadHTTPPutError)
   643  		logger.WarnfWithError(err, "swiftclient.objectPost(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
   644  		return
   645  	}
   646  
   647  	httpStatus, responseHeaders, err = readHTTPStatusAndHeaders(connection.tcpConn)
   648  	if nil != err {
   649  		releaseNonChunkedConnection(connection, false)
   650  		err = blunder.AddError(err, blunder.BadHTTPPutError)
   651  		logger.WarnfWithError(err, "swiftclient.objectPost(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
   652  		return
   653  	}
   654  	evtlog.Record(evtlog.FormatContainerPost, accountName, containerName, uint32(httpStatus))
   655  	isError, fsErr = httpStatusIsError(httpStatus)
   656  	if isError {
   657  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, responseHeaders)
   658  		releaseNonChunkedConnection(connection, false)
   659  		err = blunder.NewError(fsErr, "POST %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
   660  		err = blunder.AddHTTPCode(err, httpStatus)
   661  		logger.WarnfWithError(err, "swiftclient.objectPost(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
   662  		return
   663  	}
   664  	contentLength, err = parseContentLength(responseHeaders)
   665  	if nil != err {
   666  		releaseNonChunkedConnection(connection, false)
   667  		err = blunder.AddError(err, blunder.BadHTTPPutError)
   668  		logger.WarnfWithError(err, "swiftclient.objectPost(\"%v/%v/%v\") got parseContentLength() error", accountName, containerName, objectName)
   669  		return
   670  	}
   671  	if 0 < contentLength {
   672  		_, err = readBytesFromTCPConn(connection.tcpConn, contentLength)
   673  		if nil != err {
   674  			releaseNonChunkedConnection(connection, false)
   675  			err = blunder.AddError(err, blunder.BadHTTPPutError)
   676  			logger.WarnfWithError(err, "swiftclient.objectPost(\"%v/%v/%v\") got readBytesFromTCPConn() error", accountName, containerName, objectName)
   677  			return
   678  		}
   679  	}
   680  
   681  	releaseNonChunkedConnection(connection, parseConnection(responseHeaders))
   682  
   683  	stats.IncrementOperations(&stats.SwiftObjPostOps)
   684  
   685  	return
   686  }
   687  
   688  func objectReadWithRetry(accountName string, containerName string, objectName string, offset uint64, buf []byte) (uint64, error) {
   689  	// request is a function that, through the miracle of closure, calls
   690  	// objectRead() with the paramaters passed to this function, stashes the
   691  	// relevant return values into the local variables of this function, and
   692  	// then returns err and whether it is retriable to RequestWithRetry()
   693  	var (
   694  		len uint64
   695  		err error
   696  	)
   697  	request := func() (bool, error) {
   698  		var err error
   699  		len, err = objectRead(accountName, containerName, objectName, offset, buf)
   700  		return true, err
   701  	}
   702  
   703  	var (
   704  		retryObj *RetryCtrl = NewRetryCtrl(
   705  			globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
   706  		opname string = fmt.Sprintf(
   707  			"swiftclient.objectRead(\"%v/%v/%v\", offset=0x%016X, len(buf)=0x%016X)", accountName, containerName, objectName, offset, cap(buf))
   708  
   709  		statnm requestStatistics = requestStatistics{
   710  			retryCnt:          &stats.SwiftObjReadRetryOps,
   711  			retrySuccessCnt:   &stats.SwiftObjReadRetrySuccessOps,
   712  			clientRequestTime: &globals.ObjectReadUsec,
   713  			clientFailureCnt:  &globals.ObjectReadFailure,
   714  			swiftRequestTime:  &globals.SwiftObjectReadUsec,
   715  			swiftRetryOps:     &globals.SwiftObjectReadRetryOps,
   716  		}
   717  	)
   718  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
   719  	return len, err
   720  }
   721  
   722  func objectRead(accountName string, containerName string, objectName string, offset uint64, buf []byte) (len uint64, err error) {
   723  	var (
   724  		capacity      uint64
   725  		chunkLen      uint64
   726  		chunkPos      uint64
   727  		connection    *connectionStruct
   728  		contentLength int
   729  		fsErr         blunder.FsError
   730  		headers       map[string][]string
   731  		httpPayload   string
   732  		httpStatus    int
   733  		isError       bool
   734  	)
   735  
   736  	capacity = uint64(cap(buf))
   737  
   738  	headers = make(map[string][]string)
   739  	headers["Range"] = []string{"bytes=" + strconv.FormatUint(offset, 10) + "-" + strconv.FormatUint((offset+capacity-1), 10)}
   740  
   741  	connection, err = acquireNonChunkedConnection()
   742  	if err != nil {
   743  		// acquireNonChunkedConnection()/openConnection() logged a warning
   744  		return
   745  	}
   746  
   747  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "GET", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), headers)
   748  	if nil != err {
   749  		releaseNonChunkedConnection(connection, false)
   750  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   751  		logger.WarnfWithError(err, "swiftclient.objectRead(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
   752  		return
   753  	}
   754  
   755  	httpStatus, headers, err = readHTTPStatusAndHeaders(connection.tcpConn)
   756  	if nil != err {
   757  		releaseNonChunkedConnection(connection, false)
   758  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   759  		logger.WarnfWithError(err, "swiftclient.objectRead(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
   760  		return
   761  	}
   762  	evtlog.Record(evtlog.FormatObjectRead, accountName, containerName, objectName, offset, capacity, uint32(httpStatus))
   763  	isError, fsErr = httpStatusIsError(httpStatus)
   764  	if isError {
   765  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, headers)
   766  		releaseNonChunkedConnection(connection, false)
   767  		err = blunder.NewError(fsErr, "GET %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
   768  		err = blunder.AddHTTPCode(err, httpStatus)
   769  		logger.WarnfWithError(err, "swiftclient.objectRead(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
   770  		return
   771  	}
   772  
   773  	if parseTransferEncoding(headers) {
   774  		chunkPos = 0
   775  		for {
   776  			chunkLen, err = readHTTPChunkIntoBuf(connection.tcpConn, buf[chunkPos:])
   777  			if nil != err {
   778  				releaseNonChunkedConnection(connection, false)
   779  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   780  				logger.WarnfWithError(err, "swiftclient.objectRead(\"%v/%v/%v\") got readHTTPChunk() error", accountName, containerName, objectName)
   781  				return
   782  			}
   783  
   784  			if 0 == chunkLen {
   785  				len = chunkPos
   786  				break
   787  			}
   788  		}
   789  	} else {
   790  		contentLength, err = parseContentLength(headers)
   791  		if nil != err {
   792  			releaseNonChunkedConnection(connection, false)
   793  			err = blunder.AddError(err, blunder.BadHTTPGetError)
   794  			logger.WarnfWithError(err, "swiftclient.objectRead(\"%v/%v/%v\") got parseContentLength() error", accountName, containerName, objectName)
   795  			return
   796  		}
   797  
   798  		if 0 == contentLength {
   799  			len = 0
   800  			err = nil
   801  		} else {
   802  			err = readBytesFromTCPConnIntoBuf(connection.tcpConn, buf)
   803  			if nil != err {
   804  				releaseNonChunkedConnection(connection, false)
   805  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   806  				logger.WarnfWithError(err, "swiftclient.objectRead(\"%v/%v/%v\") got readBytesFromTCPConn() error", accountName, containerName, objectName)
   807  				return
   808  			}
   809  			len = capacity
   810  		}
   811  	}
   812  
   813  	releaseNonChunkedConnection(connection, parseConnection(headers))
   814  
   815  	stats.IncrementOperationsAndBucketedBytes(stats.SwiftObjRead, len)
   816  	globals.ObjectReadBytes.Add(len)
   817  	return
   818  }
   819  
   820  func objectTailWithRetry(accountName string, containerName string, objectName string,
   821  	length uint64) ([]byte, error) {
   822  
   823  	// request is a function that, through the miracle of closure, calls
   824  	// objectTail() with the paramaters passed to this function, stashes the
   825  	// relevant return values into the local variables of this function, and
   826  	// then returns err and whether it is retriable to RequestWithRetry()
   827  	var (
   828  		buf []byte
   829  		err error
   830  	)
   831  	request := func() (bool, error) {
   832  		var err error
   833  		buf, err = objectTail(accountName, containerName, objectName, length)
   834  		return true, err
   835  	}
   836  
   837  	var (
   838  		retryObj *RetryCtrl = NewRetryCtrl(
   839  			globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
   840  		opname string = fmt.Sprintf(
   841  			"swiftclient.objectTail(\"%v/%v/%v\")", accountName, containerName, objectName)
   842  
   843  		statnm requestStatistics = requestStatistics{
   844  			retryCnt:          &stats.SwiftObjTailRetryOps,
   845  			retrySuccessCnt:   &stats.SwiftObjTailRetrySuccessOps,
   846  			clientRequestTime: &globals.ObjectTailUsec,
   847  			clientFailureCnt:  &globals.ObjectTailFailure,
   848  			swiftRequestTime:  &globals.SwiftObjectTailUsec,
   849  			swiftRetryOps:     &globals.SwiftObjectTailRetryOps,
   850  		}
   851  	)
   852  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
   853  	return buf, err
   854  }
   855  
   856  func objectTail(accountName string, containerName string, objectName string, length uint64) (buf []byte, err error) {
   857  	var (
   858  		chunk         []byte
   859  		connection    *connectionStruct
   860  		contentLength int
   861  		fsErr         blunder.FsError
   862  		headers       map[string][]string
   863  		httpPayload   string
   864  		httpStatus    int
   865  		isError       bool
   866  	)
   867  
   868  	headers = make(map[string][]string)
   869  	headers["Range"] = []string{"bytes=-" + strconv.FormatUint(length, 10)}
   870  
   871  	connection, err = acquireNonChunkedConnection()
   872  	if err != nil {
   873  		// acquireNonChunkedConnection()/openConnection() logged a warning
   874  		return
   875  	}
   876  
   877  	err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "GET", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), headers)
   878  	if nil != err {
   879  		releaseNonChunkedConnection(connection, false)
   880  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   881  		logger.WarnfWithError(err, "swiftclient.objectTail(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
   882  		return
   883  	}
   884  
   885  	httpStatus, headers, err = readHTTPStatusAndHeaders(connection.tcpConn)
   886  	if nil != err {
   887  		releaseNonChunkedConnection(connection, false)
   888  		err = blunder.AddError(err, blunder.BadHTTPGetError)
   889  		logger.WarnfWithError(err, "swiftclient.objectTail(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", accountName, containerName, objectName)
   890  		return
   891  	}
   892  	evtlog.Record(evtlog.FormatObjectTail, accountName, containerName, objectName, length, uint32(httpStatus))
   893  	isError, fsErr = httpStatusIsError(httpStatus)
   894  	if isError {
   895  		httpPayload, _ = readHTTPPayloadAsString(connection.tcpConn, headers)
   896  		releaseNonChunkedConnection(connection, false)
   897  		err = blunder.NewError(fsErr, "GET %s/%s/%s returned HTTP StatusCode %d Payload %s", accountName, containerName, objectName, httpStatus, httpPayload)
   898  		err = blunder.AddHTTPCode(err, httpStatus)
   899  		logger.WarnfWithError(err, "swiftclient.objectTail(\"%v/%v/%v\") got readHTTPStatusAndHeaders() bad status", accountName, containerName, objectName)
   900  		return
   901  	}
   902  
   903  	if parseTransferEncoding(headers) {
   904  		buf = make([]byte, 0)
   905  		for {
   906  			chunk, err = readHTTPChunk(connection.tcpConn)
   907  			if nil != err {
   908  				releaseNonChunkedConnection(connection, false)
   909  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   910  				logger.WarnfWithError(err, "swiftclient.objectTail(\"%v/%v/%v\") got readHTTPChunk() error", accountName, containerName, objectName)
   911  				return
   912  			}
   913  
   914  			if 0 == len(chunk) {
   915  				break
   916  			}
   917  
   918  			buf = append(buf, chunk...)
   919  		}
   920  	} else {
   921  		contentLength, err = parseContentLength(headers)
   922  		if nil != err {
   923  			releaseNonChunkedConnection(connection, false)
   924  			err = blunder.AddError(err, blunder.BadHTTPGetError)
   925  			logger.WarnfWithError(err, "swiftclient.objectTail(\"%v/%v/%v\") got parseContentLength() error", accountName, containerName, objectName)
   926  			return
   927  		}
   928  
   929  		if 0 == contentLength {
   930  			buf = make([]byte, 0)
   931  		} else {
   932  			buf, err = readBytesFromTCPConn(connection.tcpConn, contentLength)
   933  			if nil != err {
   934  				releaseNonChunkedConnection(connection, false)
   935  				err = blunder.AddError(err, blunder.BadHTTPGetError)
   936  				logger.WarnfWithError(err, "swiftclient.objectTail(\"%v/%v/%v\") got readBytesFromTCPConn() error", accountName, containerName, objectName)
   937  				return
   938  			}
   939  		}
   940  	}
   941  
   942  	releaseNonChunkedConnection(connection, parseConnection(headers))
   943  
   944  	stats.IncrementOperationsAndBytes(stats.SwiftObjTail, uint64(len(buf)))
   945  	globals.ObjectTailBytes.Add(uint64(len(buf)))
   946  	return
   947  }
   948  
   949  // Checksum and related info for one chunked put chunk
   950  type chunkedPutChunkInfo struct {
   951  	chunkBuf   []byte
   952  	chunkCksum uint64
   953  }
   954  
   955  type chunkedPutContextStruct struct {
   956  	trackedlock.Mutex
   957  	accountName             string
   958  	containerName           string
   959  	objectName              string
   960  	fetchTime               time.Time
   961  	sendTime                time.Time
   962  	active                  bool
   963  	err                     error
   964  	fatal                   bool
   965  	useReserveForVolumeName string
   966  	connection              *connectionStruct
   967  	stillOpen               bool
   968  	bytesPut                uint64
   969  	bytesPutTree            sortedmap.LLRBTree // Key   == objectOffset of start of chunk in object
   970  	//                                  Value == []byte       of bytes sent to SendChunk()
   971  	chunkInfoArray []chunkedPutChunkInfo
   972  }
   973  
   974  func (chunkedPutContext *chunkedPutContextStruct) DumpKey(key sortedmap.Key) (keyAsString string, err error) {
   975  	keyAsUint64, ok := key.(uint64)
   976  	if !ok {
   977  		err = fmt.Errorf("swiftclient.chunkedPutContext.DumpKey() could not parse key as a uint64")
   978  		return
   979  	}
   980  
   981  	keyAsString = fmt.Sprintf("0x%016X", keyAsUint64)
   982  
   983  	err = nil
   984  	return
   985  }
   986  
   987  func (chunkedPutContext *chunkedPutContextStruct) DumpValue(value sortedmap.Value) (valueAsString string, err error) {
   988  	valueAsByteSlice, ok := value.([]byte)
   989  	if !ok {
   990  		err = fmt.Errorf("swiftclient.chunkedPutContext.DumpValue() could not parse value as a []byte")
   991  		return
   992  	}
   993  
   994  	valueAsString = string(valueAsByteSlice[:])
   995  
   996  	err = nil
   997  	return
   998  }
   999  
  1000  func objectFetchChunkedPutContextWithRetry(accountName string, containerName string, objectName string, useReserveForVolumeName string) (*chunkedPutContextStruct, error) {
  1001  	// request is a function that, through the miracle of closure, calls
  1002  	// objectFetchChunkedPutContext() with the paramaters passed to this
  1003  	// function, stashes the relevant return values into the local variables of
  1004  	// this function, and then returns err and whether it is retriable to
  1005  	// RequestWithRetry()
  1006  	var (
  1007  		chunkedPutContext *chunkedPutContextStruct
  1008  	)
  1009  	request := func() (bool, error) {
  1010  		var err error
  1011  		chunkedPutContext, err = objectFetchChunkedPutContext(accountName, containerName, objectName, useReserveForVolumeName)
  1012  		return true, err
  1013  	}
  1014  
  1015  	var (
  1016  		retryObj *RetryCtrl = NewRetryCtrl(
  1017  			globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
  1018  		opname string = fmt.Sprintf(
  1019  			"swiftclient.objectFetchChunkedPutContext(\"%v/%v/%v\")", accountName, containerName, objectName)
  1020  
  1021  		statnm requestStatistics = requestStatistics{
  1022  			retryCnt:          &stats.SwiftObjFetchPutCtxtRetryOps,
  1023  			retrySuccessCnt:   &stats.SwiftObjFetchPutCtxtRetrySuccessOps,
  1024  			clientRequestTime: &globals.ObjectPutCtxtFetchUsec,
  1025  			clientFailureCnt:  &globals.ObjectPutCtxtFetchFailure,
  1026  			swiftRequestTime:  &globals.SwiftObjectPutCtxtFetchUsec,
  1027  			swiftRetryOps:     &globals.SwiftObjectPutCtxtFetchRetryOps,
  1028  		}
  1029  	)
  1030  	err := retryObj.RequestWithRetry(request, &opname, &statnm)
  1031  	return chunkedPutContext, err
  1032  }
  1033  
  1034  // used during testing for error injection
  1035  var objectFetchChunkedPutContextCnt uint64
  1036  
  1037  func objectFetchChunkedPutContext(accountName string, containerName string, objectName string, useReserveForVolumeName string) (chunkedPutContext *chunkedPutContextStruct, err error) {
  1038  	var (
  1039  		connection *connectionStruct
  1040  		headers    map[string][]string
  1041  	)
  1042  
  1043  	evtlog.Record(evtlog.FormatObjectPutChunkedStart, accountName, containerName, objectName)
  1044  
  1045  	connection, err = acquireChunkedConnection(useReserveForVolumeName)
  1046  	if err != nil {
  1047  		// acquireChunkedConnection()/openConnection() logged a warning
  1048  		return
  1049  	}
  1050  
  1051  	headers = make(map[string][]string)
  1052  	headers["Transfer-Encoding"] = []string{"chunked"}
  1053  
  1054  	// check for chaos error generation (testing only)
  1055  	if atomic.LoadUint64(&globals.chaosFetchChunkedPutFailureRate) > 0 &&
  1056  		(atomic.AddUint64(&objectFetchChunkedPutContextCnt, 1)%
  1057  			atomic.LoadUint64(&globals.chaosFetchChunkedPutFailureRate) == 0) {
  1058  		err = fmt.Errorf("swiftclient.objectFetchChunkedPutContext returning simulated error")
  1059  	} else {
  1060  		err = writeHTTPRequestLineAndHeaders(connection.tcpConn, "PUT", "/"+swiftVersion+"/"+pathEscape(accountName, containerName, objectName), headers)
  1061  	}
  1062  	if nil != err {
  1063  		releaseChunkedConnection(connection, false)
  1064  		err = blunder.AddError(err, blunder.BadHTTPPutError)
  1065  		logger.WarnfWithError(err, "swiftclient.objectFetchChunkedPutContext(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", accountName, containerName, objectName)
  1066  		return
  1067  	}
  1068  
  1069  	chunkedPutContext = &chunkedPutContextStruct{
  1070  		accountName:             accountName,
  1071  		containerName:           containerName,
  1072  		objectName:              objectName,
  1073  		err:                     nil,
  1074  		active:                  true,
  1075  		useReserveForVolumeName: useReserveForVolumeName,
  1076  		connection:              connection,
  1077  		bytesPut:                0,
  1078  	}
  1079  
  1080  	chunkedPutContext.bytesPutTree = sortedmap.NewLLRBTree(sortedmap.CompareUint64, chunkedPutContext)
  1081  	chunkedPutContext.chunkInfoArray = make([]chunkedPutChunkInfo, 0)
  1082  
  1083  	stats.IncrementOperations(&stats.SwiftObjPutCtxFetchOps)
  1084  
  1085  	// record the time that ObjectFetchChunkedPutContext() returns
  1086  	chunkedPutContext.fetchTime = time.Now()
  1087  	return
  1088  }
  1089  
  1090  func (chunkedPutContext *chunkedPutContextStruct) Active() (active bool) {
  1091  	active = chunkedPutContext.active
  1092  
  1093  	stats.IncrementOperations(&stats.SwiftObjPutCtxActiveOps)
  1094  
  1095  	return
  1096  }
  1097  
  1098  func (chunkedPutContext *chunkedPutContextStruct) BytesPut() (bytesPut uint64, err error) {
  1099  	chunkedPutContext.Lock()
  1100  	bytesPut = chunkedPutContext.bytesPut
  1101  	chunkedPutContext.Unlock()
  1102  
  1103  	stats.IncrementOperations(&stats.SwiftObjPutCtxBytesPutOps)
  1104  	globals.ObjectPutCtxtBytesPut.Add(1)
  1105  
  1106  	err = nil
  1107  	return
  1108  }
  1109  
  1110  func (chunkedPutContext *chunkedPutContextStruct) Close() (err error) {
  1111  
  1112  	// track the amount of time the client spent holding the connection --
  1113  	// this includes time spent in SendChunk(), but not time spent in
  1114  	// ObjectFetchChunkedPutContext() and not time that will be spent here
  1115  	// in Close()
  1116  	fetchToCloseUsec := time.Since(chunkedPutContext.fetchTime).Nanoseconds() /
  1117  		time.Microsecond.Nanoseconds()
  1118  	globals.ObjectPutCtxtFetchToCloseUsec.Add(uint64(fetchToCloseUsec))
  1119  
  1120  	chunkedPutContext.validateChunkChecksums()
  1121  
  1122  	// request is a function that, through the miracle of closure, calls
  1123  	// retry() and Close() with the paramaters passed to this function and
  1124  	// stashes the return values into the local variables of this function
  1125  	// and then returns the error and whether it is retriable to its caller,
  1126  	// RequestWithRetry()
  1127  	var firstAttempt bool = true
  1128  	request := func() (bool, error) {
  1129  		var err error
  1130  
  1131  		// if this not the first attempt, retry() will redo all of the
  1132  		// setup and SendChunk()s that were done before the first attempt
  1133  		if !firstAttempt {
  1134  			err = chunkedPutContext.retry()
  1135  			if err != nil {
  1136  				// closeHelper() will clean up the error, but
  1137  				// only if it needs to know there was one
  1138  				chunkedPutContext.err = err
  1139  			}
  1140  		}
  1141  		firstAttempt = false
  1142  
  1143  		err = chunkedPutContext.closeHelper()
  1144  
  1145  		// fatal errors cannot be retried because we don't have the data
  1146  		// that needs to be resent available (it could not be stored)
  1147  		retriable := !chunkedPutContext.fatal
  1148  		return retriable, err
  1149  	}
  1150  
  1151  	var (
  1152  		retryObj *RetryCtrl
  1153  		opname   string
  1154  		statnm   requestStatistics = requestStatistics{
  1155  			retryCnt:          &stats.SwiftObjPutCtxtCloseRetryOps,
  1156  			retrySuccessCnt:   &stats.SwiftObjPutCtxtCloseRetrySuccessOps,
  1157  			clientRequestTime: &globals.ObjectPutCtxtCloseUsec,
  1158  			clientFailureCnt:  &globals.ObjectPutCtxtCloseFailure,
  1159  			swiftRequestTime:  &globals.SwiftObjectPutCtxtCloseUsec,
  1160  			swiftRetryOps:     &globals.SwiftObjectPutCtxtCloseRetryOps,
  1161  		}
  1162  	)
  1163  	retryObj = NewRetryCtrl(globals.retryLimitObject, globals.retryDelayObject, globals.retryExpBackoffObject)
  1164  	opname = fmt.Sprintf("swiftclient.chunkedPutContext.Close(\"%v/%v/%v\")",
  1165  		chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1166  	err = retryObj.RequestWithRetry(request, &opname, &statnm)
  1167  
  1168  	// RequestWithRetry() tried the operation one or more times until it
  1169  	// either: succeeded; failed with a non-retriable (fatal) error; or hit
  1170  	// the retry limit. Regardless, its time to release the connection we
  1171  	// got at the very start or acquired during retry (if any).
  1172  	if err != nil {
  1173  		if nil != chunkedPutContext.connection {
  1174  			releaseChunkedConnection(chunkedPutContext.connection, false)
  1175  		}
  1176  	} else {
  1177  		releaseChunkedConnection(chunkedPutContext.connection, chunkedPutContext.stillOpen)
  1178  	}
  1179  	chunkedPutContext.connection = nil
  1180  
  1181  	return err
  1182  }
  1183  
  1184  func (chunkedPutContext *chunkedPutContextStruct) closeHelper() (err error) {
  1185  	var (
  1186  		fsErr       blunder.FsError
  1187  		headers     map[string][]string
  1188  		httpPayload string
  1189  		httpStatus  int
  1190  		isError     bool
  1191  	)
  1192  
  1193  	chunkedPutContext.Lock()
  1194  	defer chunkedPutContext.Unlock()
  1195  	defer chunkedPutContext.validateChunkChecksums()
  1196  
  1197  	if !chunkedPutContext.active {
  1198  		err = blunder.NewError(blunder.BadHTTPPutError, "called while inactive")
  1199  		logger.PanicfWithError(err, "swiftclient.chunkedPutContext.closeHelper(\"%v/%v/%v\") called while inactive",
  1200  			chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1201  		return
  1202  	}
  1203  
  1204  	chunkedPutContext.active = false
  1205  
  1206  	// if an error occurred earlier there's no point in trying to send the closing chunk
  1207  	if chunkedPutContext.err != nil {
  1208  		err = chunkedPutContext.err
  1209  		return
  1210  	}
  1211  
  1212  	err = writeHTTPPutChunk(chunkedPutContext.connection.tcpConn, []byte{})
  1213  	if nil != err {
  1214  		err = blunder.AddError(err, blunder.BadHTTPPutError)
  1215  		logger.WarnfWithError(err, "swiftclient.chunkedPutContext.closeHelper(\"%v/%v/%v\") got writeHTTPPutChunk() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1216  		return
  1217  	}
  1218  
  1219  	httpStatus, headers, err = readHTTPStatusAndHeaders(chunkedPutContext.connection.tcpConn)
  1220  
  1221  	atomic.AddUint64(&chunkedPutCloseCnt, 1)
  1222  	if atomic.LoadUint64(&globals.chaosCloseChunkFailureRate) > 0 &&
  1223  		atomic.LoadUint64(&chunkedPutCloseCnt)%atomic.LoadUint64(&globals.chaosCloseChunkFailureRate) == 0 {
  1224  		err = fmt.Errorf("chunkedPutContextStruct.closeHelper(): readHTTPStatusAndHeaders() simulated error")
  1225  	}
  1226  	if nil != err {
  1227  		err = blunder.AddError(err, blunder.BadHTTPPutError)
  1228  		logger.WarnfWithError(err, "swiftclient.chunkedPutContext.closeHelper(\"%v/%v/%v\") got readHTTPStatusAndHeaders() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1229  		return
  1230  	}
  1231  	evtlog.Record(evtlog.FormatObjectPutChunkedEnd, chunkedPutContext.accountName,
  1232  		chunkedPutContext.containerName, chunkedPutContext.objectName,
  1233  		chunkedPutContext.bytesPut, uint32(httpStatus))
  1234  
  1235  	isError, fsErr = httpStatusIsError(httpStatus)
  1236  	if isError {
  1237  		httpPayload, _ = readHTTPPayloadAsString(chunkedPutContext.connection.tcpConn, headers)
  1238  		err = blunder.NewError(fsErr,
  1239  			"chunkedPutContext.closeHelper(): PUT '%s/%s/%s' returned HTTP StatusCode %d Payload %s",
  1240  			chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName,
  1241  			httpStatus, httpPayload)
  1242  		err = blunder.AddHTTPCode(err, httpStatus)
  1243  		logger.WarnfWithError(err, "chunkedPutContext readHTTPStatusAndHeaders() returned error")
  1244  		return
  1245  	}
  1246  	// even though the PUT succeeded the server may have decided to close
  1247  	// the connection, something releaseChunkedConnection() needs to know
  1248  	chunkedPutContext.stillOpen = parseConnection(headers)
  1249  
  1250  	stats.IncrementOperations(&stats.SwiftObjPutCtxCloseOps)
  1251  	globals.ObjectPutCtxtBytes.Add(chunkedPutContext.bytesPut)
  1252  
  1253  	return
  1254  }
  1255  
  1256  func (chunkedPutContext *chunkedPutContextStruct) Read(offset uint64, length uint64) (buf []byte, err error) {
  1257  	var (
  1258  		chunkBufAsByteSlice []byte
  1259  		chunkBufAsValue     sortedmap.Value
  1260  		chunkOffsetAsKey    sortedmap.Key
  1261  		chunkOffsetAsUint64 uint64
  1262  		found               bool
  1263  		chunkIndex          int
  1264  		ok                  bool
  1265  		readLimitOffset     uint64
  1266  	)
  1267  
  1268  	readLimitOffset = offset + length
  1269  
  1270  	chunkedPutContext.Lock()
  1271  
  1272  	chunkedPutContext.validateChunkChecksums()
  1273  
  1274  	if readLimitOffset > chunkedPutContext.bytesPut {
  1275  		chunkedPutContext.Unlock()
  1276  		err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() called for invalid range")
  1277  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") called for invalid range", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1278  		return
  1279  	}
  1280  
  1281  	chunkIndex, found, err = chunkedPutContext.bytesPutTree.BisectLeft(offset)
  1282  	if nil != err {
  1283  		chunkedPutContext.Unlock()
  1284  		err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.BisectLeft() failed: %v", err)
  1285  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got bytesPutTree.BisectLeft() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1286  		return
  1287  	}
  1288  	if !found && (0 > chunkIndex) {
  1289  		chunkedPutContext.Unlock()
  1290  		err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.BisectLeft() returned unexpected index/found")
  1291  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") attempt to read past end", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1292  		return
  1293  	}
  1294  
  1295  	chunkOffsetAsKey, chunkBufAsValue, ok, err = chunkedPutContext.bytesPutTree.GetByIndex(chunkIndex)
  1296  	if nil != err {
  1297  		chunkedPutContext.Unlock()
  1298  		err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() failed: %v", err)
  1299  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got initial bytesPutTree.GetByIndex() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1300  		return
  1301  	}
  1302  	if !ok {
  1303  		chunkedPutContext.Unlock()
  1304  		err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() returned ok == false")
  1305  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got initial bytesPutTree.GetByIndex() !ok", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1306  		return
  1307  	}
  1308  
  1309  	chunkOffsetAsUint64, ok = chunkOffsetAsKey.(uint64)
  1310  	if !ok {
  1311  		chunkedPutContext.Unlock()
  1312  		err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() returned non-uint64 Key")
  1313  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got initial bytesPutTree.GetByIndex() malformed Key", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1314  		return
  1315  	}
  1316  
  1317  	chunkBufAsByteSlice, ok = chunkBufAsValue.([]byte)
  1318  	if !ok {
  1319  		chunkedPutContext.Unlock()
  1320  		err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() returned non-[]byte Value")
  1321  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got initial bytesPutTree.GetByIndex() malformed Value", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1322  		return
  1323  	}
  1324  
  1325  	if (readLimitOffset - chunkOffsetAsUint64) <= uint64(len(chunkBufAsByteSlice)) {
  1326  		// Trivial case: offset:readLimitOffset fits entirely within chunkBufAsByteSlice
  1327  
  1328  		buf = chunkBufAsByteSlice[(offset - chunkOffsetAsUint64):(readLimitOffset - chunkOffsetAsUint64)]
  1329  	} else {
  1330  		// Complex case: offset:readLimit extends beyond end of chunkBufAsByteSlice
  1331  
  1332  		buf = make([]byte, 0, length)
  1333  		buf = append(buf, chunkBufAsByteSlice[(offset-chunkOffsetAsUint64):]...)
  1334  
  1335  		for uint64(len(buf)) < length {
  1336  			chunkIndex++
  1337  
  1338  			chunkOffsetAsKey, chunkBufAsValue, ok, err = chunkedPutContext.bytesPutTree.GetByIndex(chunkIndex)
  1339  			if nil != err {
  1340  				chunkedPutContext.Unlock()
  1341  				err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() failed: %v", err)
  1342  				logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got next bytesPutTree.GetByIndex() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1343  				return
  1344  			}
  1345  			if !ok {
  1346  				chunkedPutContext.Unlock()
  1347  				err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() returned ok == false")
  1348  				logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got next bytesPutTree.GetByIndex() !ok", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1349  				return
  1350  			}
  1351  
  1352  			chunkOffsetAsUint64, ok = chunkOffsetAsKey.(uint64)
  1353  			if !ok {
  1354  				chunkedPutContext.Unlock()
  1355  				err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() returned non-uint64 Key")
  1356  				logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got next bytesPutTree.GetByIndex() malformed Key", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1357  				return
  1358  			}
  1359  
  1360  			chunkBufAsByteSlice, ok = chunkBufAsValue.([]byte)
  1361  			if !ok {
  1362  				chunkedPutContext.Unlock()
  1363  				err = blunder.NewError(blunder.BadHTTPGetError, "swiftclient.chunkedPutContext.Read() bytesPutTree.GetByIndex() returned non-[]byte Value")
  1364  				logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.Read(\"%v/%v/%v\") got next bytesPutTree.GetByIndex() malformed Key", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1365  				return
  1366  			}
  1367  
  1368  			if (readLimitOffset - chunkOffsetAsUint64) < uint64(len(chunkBufAsByteSlice)) {
  1369  				buf = append(buf, chunkBufAsByteSlice[:(readLimitOffset-chunkOffsetAsUint64)]...)
  1370  			} else {
  1371  				buf = append(buf, chunkBufAsByteSlice...)
  1372  			}
  1373  		}
  1374  	}
  1375  	chunkedPutContext.Unlock()
  1376  
  1377  	stats.IncrementOperationsAndBucketedBytes(stats.SwiftObjPutCtxRead, length)
  1378  	globals.ObjectPutCtxtReadBytes.Add(length)
  1379  
  1380  	err = nil
  1381  	return
  1382  }
  1383  
  1384  func (chunkedPutContext *chunkedPutContextStruct) retry() (err error) {
  1385  	var (
  1386  		chunkBufAsByteSlice []byte
  1387  		chunkBufAsValue     sortedmap.Value
  1388  		chunkIndex          int
  1389  		headers             map[string][]string
  1390  		ok                  bool
  1391  	)
  1392  
  1393  	chunkedPutContext.Lock()
  1394  
  1395  	if chunkedPutContext.active {
  1396  		chunkedPutContext.Unlock()
  1397  		err = blunder.NewError(blunder.BadHTTPPutError, "called while active")
  1398  		logger.PanicfWithError(err, "swiftclient.chunkedPutContext.retry(\"%v/%v/%v\") called while active",
  1399  			chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1400  		return
  1401  	}
  1402  
  1403  	// clear error from the previous attempt and make active
  1404  	chunkedPutContext.err = nil
  1405  	chunkedPutContext.active = true
  1406  
  1407  	// try to re-open chunked put connection
  1408  	if nil != chunkedPutContext.connection {
  1409  		releaseChunkedConnection(chunkedPutContext.connection, false)
  1410  	}
  1411  	chunkedPutContext.connection, err = acquireChunkedConnection(chunkedPutContext.useReserveForVolumeName)
  1412  	if nil != err {
  1413  		chunkedPutContext.Unlock()
  1414  		err = blunder.AddError(err, blunder.BadHTTPPutError)
  1415  		logger.WarnfWithError(err, "swiftclient.chunkedPutContext.retry(\"%v/%v/%v\") acquireChunkedConnection() failed",
  1416  			chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1417  		return
  1418  	}
  1419  
  1420  	headers = make(map[string][]string)
  1421  	headers["Transfer-Encoding"] = []string{"chunked"}
  1422  
  1423  	err = writeHTTPRequestLineAndHeaders(chunkedPutContext.connection.tcpConn, "PUT", "/"+swiftVersion+"/"+pathEscape(chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName), headers)
  1424  	if nil != err {
  1425  		chunkedPutContext.Unlock()
  1426  		err = blunder.AddError(err, blunder.BadHTTPPutError)
  1427  		logger.WarnfWithError(err, "swiftclient.chunkedPutContext.retry(\"%v/%v/%v\") got writeHTTPRequestLineAndHeaders() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1428  		return
  1429  	}
  1430  
  1431  	chunkIndex = 0
  1432  
  1433  	chunkedPutContext.validateChunkChecksums()
  1434  	for {
  1435  		_, chunkBufAsValue, ok, err = chunkedPutContext.bytesPutTree.GetByIndex(chunkIndex)
  1436  		if nil != err {
  1437  			chunkedPutContext.Unlock()
  1438  			err = blunder.NewError(blunder.BadHTTPPutError, "bytesPutTree.GetByIndex() failed: %v", err)
  1439  			logger.WarnfWithError(err, "swiftclient.chunkedPutContext.retry(\"%v/%v/%v\") got bytesPutTree.GetByIndex() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1440  			return
  1441  		}
  1442  
  1443  		if !ok {
  1444  			// We've reached the end of bytesPutTree... so we (presumably) have now sent bytesPut bytes in total
  1445  			break
  1446  		}
  1447  
  1448  		// Simply (re)send the chunk (assuming it is a []byte)
  1449  
  1450  		chunkBufAsByteSlice, ok = chunkBufAsValue.([]byte)
  1451  		if !ok {
  1452  			chunkedPutContext.Unlock()
  1453  			err = blunder.NewError(blunder.BadHTTPPutError, "bytesPutTree.GetByIndex() returned non-[]byte Value")
  1454  			logger.WarnfWithError(err, "swiftclient.chunkedPutContext.retry(\"%v/%v/%v\") got bytesPutTree.GetByIndex(() malformed Value", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1455  			return
  1456  		}
  1457  
  1458  		// check for chaos error generation (testing only)
  1459  		atomic.AddUint64(&chunkedPutSendRetryCnt, 1)
  1460  		if atomic.LoadUint64(&globals.chaosSendChunkFailureRate) > 0 &&
  1461  			atomic.LoadUint64(&chunkedPutSendRetryCnt)%atomic.LoadUint64(&globals.chaosSendChunkFailureRate) == 0 {
  1462  
  1463  			nChunk, _ := chunkedPutContext.bytesPutTree.Len()
  1464  			err = fmt.Errorf("chunkedPutContextStruct.retry(): "+
  1465  				"writeHTTPPutChunk() simulated error with %d chunks", nChunk)
  1466  		} else {
  1467  			err = writeHTTPPutChunk(chunkedPutContext.connection.tcpConn, chunkBufAsByteSlice)
  1468  		}
  1469  		if nil != err {
  1470  			chunkedPutContext.Unlock()
  1471  			err = blunder.NewError(blunder.BadHTTPPutError, "writeHTTPPutChunk() failed: %v", err)
  1472  			logger.WarnfWithError(err, "swiftclient.chunkedPutContext.retry(\"%v/%v/%v\") got writeHTTPPutChunk() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1473  			return
  1474  		}
  1475  
  1476  		// See if there is (was) another chunk
  1477  
  1478  		chunkIndex++
  1479  	}
  1480  	chunkedPutContext.Unlock()
  1481  
  1482  	stats.IncrementOperations(&stats.SwiftObjPutCtxRetryOps)
  1483  
  1484  	return
  1485  }
  1486  
  1487  // If checksums are enabled validate the checksums for each chunk.
  1488  //
  1489  // The chunked put context is assumed to be locked (not actively changing), and
  1490  // is returned still locked.
  1491  //
  1492  func (chunkedPutContext *chunkedPutContextStruct) validateChunkChecksums() {
  1493  	if !globals.checksumChunkedPutChunks {
  1494  		return
  1495  	}
  1496  	if chunkedPutContext.bytesPut == 0 {
  1497  		return
  1498  	}
  1499  
  1500  	var (
  1501  		chunkBufAsValue  sortedmap.Value
  1502  		chunkBuf         []byte
  1503  		chunkOffsetAsKey sortedmap.Key
  1504  		chunkOffset      uint64
  1505  		chunkIndex       int
  1506  		ok               bool
  1507  		panicErr         error
  1508  		offset           uint64
  1509  	)
  1510  
  1511  	// print as much potentially useful information as we can before panic'ing
  1512  	panicErr = nil
  1513  	offset = 0
  1514  	for chunkIndex = 0; true; chunkIndex++ {
  1515  		var err error
  1516  
  1517  		chunkOffsetAsKey, chunkBufAsValue, ok, err = chunkedPutContext.bytesPutTree.GetByIndex(chunkIndex)
  1518  		if err != nil {
  1519  			panicErr = fmt.Errorf("bytesPutTree.GetByIndex(%d) offset %d at failed: %v",
  1520  				chunkIndex, offset, err)
  1521  			break
  1522  		}
  1523  		if !ok {
  1524  			// We've reached the end of bytesPutTree... so we
  1525  			// (presumably) have now check all the chunks
  1526  			break
  1527  		}
  1528  
  1529  		chunkOffset, ok = chunkOffsetAsKey.(uint64)
  1530  		if !ok {
  1531  			panicErr = fmt.Errorf(
  1532  				"bytesPutTree.GetByIndex(%d) offset %d returned incorrect key type: %T",
  1533  				chunkIndex, offset, chunkOffsetAsKey)
  1534  			break
  1535  		}
  1536  
  1537  		chunkBuf, ok = chunkBufAsValue.([]byte)
  1538  		if !ok {
  1539  			panicErr = fmt.Errorf(
  1540  				"bytesPutTree.GetByIndex(%d) offset %d returned incorrect value type: %T",
  1541  				chunkIndex, offset, chunkOffsetAsKey)
  1542  			break
  1543  		}
  1544  
  1545  		// verify slice still points to the same memory and has correct checksum
  1546  		if &chunkBuf[0] != &chunkedPutContext.chunkInfoArray[chunkIndex].chunkBuf[0] {
  1547  			err = fmt.Errorf("chunkInfoArray(%d) offset %d returned buf %p != saved buf %p",
  1548  				chunkIndex, offset, &chunkBuf[0],
  1549  				&chunkedPutContext.chunkInfoArray[chunkIndex].chunkBuf[0])
  1550  
  1551  		} else if cityhash.Hash64(chunkBuf) != chunkedPutContext.chunkInfoArray[chunkIndex].chunkCksum {
  1552  			err = fmt.Errorf(
  1553  				"chunkInfoArray(%d) offset %d computed chunk checksum 0x%16x != saved checksum 0x%16x",
  1554  				chunkIndex, offset, cityhash.Hash64(chunkBuf),
  1555  				chunkedPutContext.chunkInfoArray[chunkIndex].chunkCksum)
  1556  		} else if offset != chunkOffset {
  1557  			err = fmt.Errorf("chunkInfoArray(%d) offset %d returned incorrect offset key %d",
  1558  				chunkIndex, offset, chunkOffset)
  1559  		}
  1560  
  1561  		if err != nil {
  1562  			logger.ErrorfWithError(err,
  1563  				"validateChunkChecksums('%v/%v/%v') %d chunks %d bytes invalid chunk",
  1564  				chunkedPutContext.accountName, chunkedPutContext.containerName,
  1565  				chunkedPutContext.objectName,
  1566  				len(chunkedPutContext.chunkInfoArray), chunkedPutContext.bytesPut)
  1567  			if panicErr == nil {
  1568  				panicErr = err
  1569  			}
  1570  		}
  1571  
  1572  		offset += uint64(len(chunkBuf))
  1573  	}
  1574  
  1575  	if panicErr == nil {
  1576  		return
  1577  	}
  1578  
  1579  	// log time stamps for chunked put (and last checksum validation)
  1580  	logger.Errorf(
  1581  		"validateChunkChecksums('%v/%v/%v') fetched %f sec ago last SendChunk() %f sec ago",
  1582  		chunkedPutContext.accountName, chunkedPutContext.containerName,
  1583  		chunkedPutContext.objectName,
  1584  		time.Since(chunkedPutContext.fetchTime).Seconds(),
  1585  		time.Since(chunkedPutContext.sendTime).Seconds())
  1586  
  1587  	// there was an error; dump stack, other relevant information, and panic
  1588  	stackBuf := make([]byte, 64*1024)
  1589  	stackBuf = stackBuf[:runtime.Stack(stackBuf, false)]
  1590  	logger.Errorf(
  1591  		"validateChunkChecksums('%v/%v/%v') chunked buffer error: stack trace:\n%s",
  1592  		chunkedPutContext.accountName, chunkedPutContext.containerName,
  1593  		chunkedPutContext.objectName, stackBuf)
  1594  
  1595  	logger.PanicfWithError(panicErr,
  1596  		"validateChunkChecksums('%v/%v/%v') %d chunks %d chunk bytes chunked buffer error",
  1597  		chunkedPutContext.accountName, chunkedPutContext.containerName,
  1598  		chunkedPutContext.objectName,
  1599  		len(chunkedPutContext.chunkInfoArray), chunkedPutContext.bytesPut)
  1600  	panic(panicErr)
  1601  }
  1602  
  1603  // used during testing for error injection
  1604  var (
  1605  	chunkedPutSendCnt      uint64
  1606  	chunkedPutSendRetryCnt uint64
  1607  	chunkedPutCloseCnt     uint64
  1608  )
  1609  
  1610  // SendChunk() tries to send the chunk of data to the Swift server and deal with
  1611  // any errors that occur.  There is a retry mechanism in Close() that will
  1612  // attempt to resend the data if the first attempt here was not successful.
  1613  //
  1614  // For "normal" error cases SendChunk() will returns nil (success) instead of
  1615  // the error and stash the data to be sent away so that retry can attempt to
  1616  // send it.  Whence Close() is called, it notices the pending error in
  1617  // ChunkedPutContext.err and retries the entire operation.  If the retry works,
  1618  // it returns success.  Otherwise, it returns the final error it encountered.
  1619  
  1620  // There are some cases where SendChunk() cannot stash away the data to be sent
  1621  // due to logic errors in the program or corruption of in memory data
  1622  // structures.  These should probably just be dealt with by panic'ing, but
  1623  // instead ChunkedPutContext treats this as a "fatal" error, which it returns to
  1624  // the caller of SendChunk().  If Close() is called later, it does not attempt
  1625  // to retry but instead returns the same error.  Just in case Close() is not
  1626  // called (current code does not call Close() after SendChunk() returns an
  1627  // error, SendChunk() also cleans up the TCP connection.
  1628  //
  1629  func (chunkedPutContext *chunkedPutContextStruct) SendChunk(buf []byte) (err error) {
  1630  
  1631  	clientReqTime := time.Now()
  1632  
  1633  	chunkedPutContext.Lock()
  1634  
  1635  	// record the start of the most recent (modulo lock scheduling) send request
  1636  	chunkedPutContext.sendTime = clientReqTime
  1637  
  1638  	if !chunkedPutContext.active {
  1639  		err = blunder.NewError(blunder.BadHTTPPutError, "called while inactive")
  1640  		logger.PanicfWithError(err, "swiftclient.chunkedPutContext.SendChunk(\"%v/%v/%v\") logic error",
  1641  			chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1642  		chunkedPutContext.err = err
  1643  		chunkedPutContext.fatal = true
  1644  
  1645  		// connection should already be nil
  1646  		chunkedPutContext.connection = nil
  1647  		chunkedPutContext.Unlock()
  1648  		return
  1649  	}
  1650  
  1651  	if 0 == len(buf) {
  1652  		err = blunder.NewError(blunder.BadHTTPPutError, "called with zero-length buf")
  1653  		logger.PanicfWithError(err, "swiftclient.chunkedPutContext.SendChunk(\"%v/%v/%v\") logic error",
  1654  			chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1655  		chunkedPutContext.err = err
  1656  		chunkedPutContext.fatal = true
  1657  		releaseChunkedConnection(chunkedPutContext.connection, false)
  1658  		chunkedPutContext.connection = nil
  1659  		chunkedPutContext.Unlock()
  1660  		return
  1661  	}
  1662  
  1663  	// validate current checksums
  1664  	chunkedPutContext.validateChunkChecksums()
  1665  
  1666  	// if checksum verification is enabled, compute the checksum
  1667  	if globals.checksumChunkedPutChunks {
  1668  		var chunkInfo chunkedPutChunkInfo
  1669  
  1670  		chunkInfo.chunkBuf = buf
  1671  		chunkInfo.chunkCksum = cityhash.Hash64(buf)
  1672  		chunkedPutContext.chunkInfoArray = append(chunkedPutContext.chunkInfoArray, chunkInfo)
  1673  	}
  1674  
  1675  	ok, err := chunkedPutContext.bytesPutTree.Put(chunkedPutContext.bytesPut, buf)
  1676  	if nil != err {
  1677  		err = blunder.NewError(blunder.BadHTTPPutError, "attempt to append chunk to LLRB Tree failed: %v", err)
  1678  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.SendChunk(\"%v/%v/%v\") got bytesPutTree.Put() error", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1679  		chunkedPutContext.err = err
  1680  		chunkedPutContext.fatal = true
  1681  		releaseChunkedConnection(chunkedPutContext.connection, false)
  1682  		chunkedPutContext.connection = nil
  1683  		chunkedPutContext.Unlock()
  1684  		return
  1685  	}
  1686  	if !ok {
  1687  		err = blunder.NewError(blunder.BadHTTPPutError, "attempt to append chunk to LLRB Tree returned ok == false")
  1688  		logger.ErrorfWithError(err, "swiftclient.chunkedPutContext.SendChunk(\"%v/%v/%v\") got bytesPutTree.Put() !ok", chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1689  		chunkedPutContext.err = err
  1690  		chunkedPutContext.fatal = true
  1691  		releaseChunkedConnection(chunkedPutContext.connection, false)
  1692  		chunkedPutContext.connection = nil
  1693  		chunkedPutContext.Unlock()
  1694  		return
  1695  	}
  1696  
  1697  	chunkedPutContext.bytesPut += uint64(len(buf))
  1698  
  1699  	// The prior errors are logical/programmatic errors that cannot be fixed
  1700  	// by a retry so let them return with the error and let the caller abort
  1701  	// (Close() will not be called, so retry() will not be called).
  1702  	//
  1703  	// However, if writeHTTPPutChunk() fails that is probably due to a
  1704  	// problem with the TCP connection or storage which may be cured by a
  1705  	// retry.  Therefore, if the call fails stash the error in
  1706  	// chunkedPutContext and return success to the caller so it will feed us
  1707  	// the rest of the chunks.  We need those chunks for the retry!
  1708  	//
  1709  	// If an error has already been seen, stash the data for use by retry
  1710  	// and return success.
  1711  	if chunkedPutContext.err != nil {
  1712  		chunkedPutContext.Unlock()
  1713  		return nil
  1714  	}
  1715  
  1716  	// check for chaos error generation (testing only)
  1717  	atomic.AddUint64(&chunkedPutSendCnt, 1)
  1718  	if atomic.LoadUint64(&globals.chaosSendChunkFailureRate) > 0 &&
  1719  		atomic.LoadUint64(&chunkedPutSendCnt)%atomic.LoadUint64(&globals.chaosSendChunkFailureRate) == 0 {
  1720  		err = fmt.Errorf("chunkedPutContextStruct.SendChunk(): writeHTTPPutChunk() simulated error")
  1721  	} else {
  1722  		err = writeHTTPPutChunk(chunkedPutContext.connection.tcpConn, buf)
  1723  	}
  1724  	if nil != err {
  1725  		err = blunder.AddError(err, blunder.BadHTTPPutError)
  1726  		logger.WarnfWithError(err,
  1727  			"swiftclient.chunkedPutContext.SendChunk(\"%v/%v/%v\") got writeHTTPPutChunk() error",
  1728  			chunkedPutContext.accountName, chunkedPutContext.containerName, chunkedPutContext.objectName)
  1729  		chunkedPutContext.err = err
  1730  		chunkedPutContext.Unlock()
  1731  		err = nil
  1732  		return
  1733  	}
  1734  
  1735  	chunkedPutContext.Unlock()
  1736  
  1737  	stats.IncrementOperationsAndBucketedBytes(stats.SwiftObjPutCtxSendChunk, uint64(len(buf)))
  1738  	elapsedUsec := time.Since(clientReqTime).Nanoseconds() / time.Microsecond.Nanoseconds()
  1739  	globals.ObjectPutCtxtSendChunkUsec.Add(uint64(elapsedUsec))
  1740  	globals.ObjectPutCtxtSendChunkBytes.Add(uint64(len(buf)))
  1741  
  1742  	return
  1743  }