mirror of
https://github.com/pgsty/minio.git
synced 2026-09-25 20:35:58 +03:00
fix(federation): bind copy timestamps to committed writes
Return the committed object or part time on federation write responses and capture it for CopyObject and UploadPartCopy without a follow-up read. Keep successful writes compatible with targets that do not supply a time. Reject authenticated raw SSE-C replica CopyObject across deployments before forwarding. Extend the existing SSE fixtures to verify empty objects, stored checksums, KMS contexts and multipart sources. Refs: #169, #168, #171 Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -36,6 +36,44 @@ import (
|
||||
|
||||
var etagRegex = regexp.MustCompile("\"*?([^\"]*?)\"*?$")
|
||||
|
||||
// federatedLastModified carries the committed object/part time on the write
|
||||
// response. HTTP Date is a response time and Last-Modified loses subsecond
|
||||
// precision, so neither can supply CopyObject/UploadPartCopy's timestamp.
|
||||
const federatedLastModified = "X-Minio-Last-Modified"
|
||||
|
||||
type federatedWriteTimeKey struct{}
|
||||
|
||||
// withFederatedWriteTime allocates one result per SDK operation. SDK retries
|
||||
// run sequentially with this context; concurrent copies never share the result.
|
||||
func withFederatedWriteTime(ctx context.Context) (context.Context, *time.Time) {
|
||||
modified := new(time.Time)
|
||||
return context.WithValue(ctx, federatedWriteTimeKey{}, modified), modified
|
||||
}
|
||||
|
||||
type federatedWriteTransport struct {
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
func (t federatedWriteTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
resp, err := t.RoundTripper.RoundTrip(r)
|
||||
if modified, ok := r.Context().Value(federatedWriteTimeKey{}).(*time.Time); ok && r.Method == http.MethodPut {
|
||||
// Replace on every attempt, including a success from an older target
|
||||
// without the header. Never retain a failed attempt's timestamp or fail
|
||||
// a committed write just because its timestamp is unavailable.
|
||||
*modified = time.Time{}
|
||||
if err == nil && resp != nil && resp.StatusCode == http.StatusOK {
|
||||
*modified, _ = time.Parse(time.RFC3339Nano, resp.Header.Get(federatedLastModified))
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func setFederatedWriteTime(w http.ResponseWriter, r *http.Request, modified time.Time) {
|
||||
if isFederatedInternalRequest(r.UserAgent()) && !modified.IsZero() {
|
||||
w.Header().Set(federatedLastModified, modified.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
}
|
||||
|
||||
// Validates the preconditions for CopyObjectPart, returns true if CopyObjectPart
|
||||
// operation should not proceed. Preconditions supported are:
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user