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 }