History

Add support for existing object replication. (#12109)

Also adding an API to allow resyncing replication when existing object replication is enabled and the remote target is entirely lost. With the `mc replicate reset` command, the objects that are eligible for replication as per the replication config will be resynced to target if existing object replication is enabled on the rule.

Poorna Krishnamoorthy committed 5 years ago

Commit dbea8d21 parent 1f262da19 files · +928 −334Browse files

Unified · Split

19 files
  1. cmd/api-errors.go +6 −0
  2. cmd/api-router.go +3 −0
  3. cmd/apierrorcode_string.go +224 −223
  4. cmd/bucket-handlers.go +89 −1
  5. cmd/bucket-replication.go +201 −43
  6. cmd/bucket-replication_test.go +209 −0
  7. cmd/data-scanner.go +50 −22
  8. cmd/data-usage-cache.go +2 −1
  9. cmd/object-api-datatypes.go +1 −0
  10. cmd/object-handlers.go +28 −15
  11. cmd/web-handlers.go +3 −3
  12. cmd/xl-storage.go +13 −1
  13. docs/bucket/replication/DESIGN.md +5 −1
  14. docs/bucket/replication/README.md +15 −0
  15. go.mod +1 −1
  16. go.sum +2 −0
  17. internal/bucket/replication/replication.go +20 −9
  18. internal/bucket/replication/rule.go +53 −14
  19. internal/http/headers.go +3 −0
cmd/api-errors.go+6−0
@@ -125,6 +125,7 @@ const ( · 124 unmodified lines
125125 ErrReplicationSourceNotVersionedError
126126 ErrReplicationNeedsVersioningError
127127 ErrReplicationBucketNeedsVersioningError
128+ ErrReplicationNoMatchingRuleError
128129 ErrObjectRestoreAlreadyInProgress
129130 ErrNoSuchKey
130131 ErrNoSuchUpload
@@ -859,6 +860,11 @@ var errorCodes = errorCodeMap{ · 728 unmodified lines
859860 Description: "Remote service connection error - please check remote service credentials and target bucket",
860861 HTTPStatusCode: http.StatusNotFound,
861862 },
863+ ErrReplicationNoMatchingRuleError: {
864+ Code: "XMinioReplicationNoMatchingRule",
865+ Description: "No matching replication rule found for this object prefix",
866+ HTTPStatusCode: http.StatusBadRequest,
867+ },
862868 ErrBucketRemoteIdenticalToSource: {
863869 Code: "XMinioAdminRemoteIdenticalToSource",
864870 Description: "The remote target cannot be identical to source",
cmd/api-router.go+3−0
@@ -394,6 +394,9 @@ func registerAPIRouter(router *mux.Router) { · 393 unmodified lines
394394 // PutBucketNotification
395395 router.Methods(http.MethodPut).HandlerFunc(
396396 collectAPIStats("putbucketnotification", maxClients(httpTraceAll(api.PutBucketNotificationHandler)))).Queries("notification", "")
397+ // ResetBucketReplicationState - MinIO extension API
398+ router.Methods(http.MethodPut).HandlerFunc(
399+ collectAPIStats("resetbucketreplicationstate", maxClients(httpTraceAll(api.ResetBucketReplicationStateHandler)))).Queries("replication-reset", "")
397400 // PutBucket
398401 router.Methods(http.MethodPut).HandlerFunc(
399402 collectAPIStats("putbucket", maxClients(httpTraceAll(api.PutBucketHandler))))
cmd/apierrorcode_string.go+224−223
@@ -62,232 +62,233 @@ func _() { · 61 unmodified lines
6262 _ = x[ErrReplicationSourceNotVersionedError-51]
6363 _ = x[ErrReplicationNeedsVersioningError-52]
6464 _ = x[ErrReplicationBucketNeedsVersioningError-53]
65− _ = x[ErrObjectRestoreAlreadyInProgress-54]
66− _ = x[ErrNoSuchKey-55]
67− _ = x[ErrNoSuchUpload-56]
68− _ = x[ErrInvalidVersionID-57]
69− _ = x[ErrNoSuchVersion-58]
70− _ = x[ErrNotImplemented-59]
71− _ = x[ErrPreconditionFailed-60]
72− _ = x[ErrRequestTimeTooSkewed-61]
73− _ = x[ErrSignatureDoesNotMatch-62]
74− _ = x[ErrMethodNotAllowed-63]
75− _ = x[ErrInvalidPart-64]
76− _ = x[ErrInvalidPartOrder-65]
77− _ = x[ErrAuthorizationHeaderMalformed-66]
78− _ = x[ErrMalformedPOSTRequest-67]
79− _ = x[ErrPOSTFileRequired-68]
80− _ = x[ErrSignatureVersionNotSupported-69]
81− _ = x[ErrBucketNotEmpty-70]
82− _ = x[ErrAllAccessDisabled-71]
83− _ = x[ErrMalformedPolicy-72]
84− _ = x[ErrMissingFields-73]
85− _ = x[ErrMissingCredTag-74]
86− _ = x[ErrCredMalformed-75]
87− _ = x[ErrInvalidRegion-76]
88− _ = x[ErrInvalidServiceS3-77]
89− _ = x[ErrInvalidServiceSTS-78]
90− _ = x[ErrInvalidRequestVersion-79]
91− _ = x[ErrMissingSignTag-80]
92− _ = x[ErrMissingSignHeadersTag-81]
93− _ = x[ErrMalformedDate-82]
94− _ = x[ErrMalformedPresignedDate-83]
95− _ = x[ErrMalformedCredentialDate-84]
96− _ = x[ErrMalformedCredentialRegion-85]
97− _ = x[ErrMalformedExpires-86]
98− _ = x[ErrNegativeExpires-87]
99− _ = x[ErrAuthHeaderEmpty-88]
100− _ = x[ErrExpiredPresignRequest-89]
101− _ = x[ErrRequestNotReadyYet-90]
102− _ = x[ErrUnsignedHeaders-91]
103− _ = x[ErrMissingDateHeader-92]
104− _ = x[ErrInvalidQuerySignatureAlgo-93]
105− _ = x[ErrInvalidQueryParams-94]
106− _ = x[ErrBucketAlreadyOwnedByYou-95]
107− _ = x[ErrInvalidDuration-96]
108− _ = x[ErrBucketAlreadyExists-97]
109− _ = x[ErrMetadataTooLarge-98]
110− _ = x[ErrUnsupportedMetadata-99]
111− _ = x[ErrMaximumExpires-100]
112− _ = x[ErrSlowDown-101]
113− _ = x[ErrInvalidPrefixMarker-102]
114− _ = x[ErrBadRequest-103]
115− _ = x[ErrKeyTooLongError-104]
116− _ = x[ErrInvalidBucketObjectLockConfiguration-105]
117− _ = x[ErrObjectLockConfigurationNotFound-106]
118− _ = x[ErrObjectLockConfigurationNotAllowed-107]
119− _ = x[ErrNoSuchObjectLockConfiguration-108]
120− _ = x[ErrObjectLocked-109]
121− _ = x[ErrInvalidRetentionDate-110]
122− _ = x[ErrPastObjectLockRetainDate-111]
123− _ = x[ErrUnknownWORMModeDirective-112]
124− _ = x[ErrBucketTaggingNotFound-113]
125− _ = x[ErrObjectLockInvalidHeaders-114]
126− _ = x[ErrInvalidTagDirective-115]
127− _ = x[ErrInvalidEncryptionMethod-116]
128− _ = x[ErrInsecureSSECustomerRequest-117]
129− _ = x[ErrSSEMultipartEncrypted-118]
130− _ = x[ErrSSEEncryptedObject-119]
131− _ = x[ErrInvalidEncryptionParameters-120]
132− _ = x[ErrInvalidSSECustomerAlgorithm-121]
133− _ = x[ErrInvalidSSECustomerKey-122]
134− _ = x[ErrMissingSSECustomerKey-123]
135− _ = x[ErrMissingSSECustomerKeyMD5-124]
136− _ = x[ErrSSECustomerKeyMD5Mismatch-125]
137− _ = x[ErrInvalidSSECustomerParameters-126]
138− _ = x[ErrIncompatibleEncryptionMethod-127]
139− _ = x[ErrKMSNotConfigured-128]
140− _ = x[ErrNoAccessKey-129]
141− _ = x[ErrInvalidToken-130]
142− _ = x[ErrEventNotification-131]
143− _ = x[ErrARNNotification-132]
144− _ = x[ErrRegionNotification-133]
145− _ = x[ErrOverlappingFilterNotification-134]
146− _ = x[ErrFilterNameInvalid-135]
147− _ = x[ErrFilterNamePrefix-136]
148− _ = x[ErrFilterNameSuffix-137]
149− _ = x[ErrFilterValueInvalid-138]
150− _ = x[ErrOverlappingConfigs-139]
151− _ = x[ErrUnsupportedNotification-140]
152− _ = x[ErrContentSHA256Mismatch-141]
153− _ = x[ErrReadQuorum-142]
154− _ = x[ErrWriteQuorum-143]
155− _ = x[ErrParentIsObject-144]
156− _ = x[ErrStorageFull-145]
157− _ = x[ErrRequestBodyParse-146]
158− _ = x[ErrObjectExistsAsDirectory-147]
159− _ = x[ErrInvalidObjectName-148]
160− _ = x[ErrInvalidObjectNamePrefixSlash-149]
161− _ = x[ErrInvalidResourceName-150]
162− _ = x[ErrServerNotInitialized-151]
163− _ = x[ErrOperationTimedOut-152]
164− _ = x[ErrClientDisconnected-153]
165− _ = x[ErrOperationMaxedOut-154]
166− _ = x[ErrInvalidRequest-155]
167− _ = x[ErrTransitionStorageClassNotFoundError-156]
168− _ = x[ErrInvalidStorageClass-157]
169− _ = x[ErrBackendDown-158]
170− _ = x[ErrMalformedJSON-159]
171− _ = x[ErrAdminNoSuchUser-160]
172− _ = x[ErrAdminNoSuchGroup-161]
173− _ = x[ErrAdminGroupNotEmpty-162]
174− _ = x[ErrAdminNoSuchPolicy-163]
175− _ = x[ErrAdminInvalidArgument-164]
176− _ = x[ErrAdminInvalidAccessKey-165]
177− _ = x[ErrAdminInvalidSecretKey-166]
178− _ = x[ErrAdminConfigNoQuorum-167]
179− _ = x[ErrAdminConfigTooLarge-168]
180− _ = x[ErrAdminConfigBadJSON-169]
181− _ = x[ErrAdminConfigDuplicateKeys-170]
182− _ = x[ErrAdminCredentialsMismatch-171]
183− _ = x[ErrInsecureClientRequest-172]
184− _ = x[ErrObjectTampered-173]
185− _ = x[ErrAdminBucketQuotaExceeded-174]
186− _ = x[ErrAdminNoSuchQuotaConfiguration-175]
187− _ = x[ErrHealNotImplemented-176]
188− _ = x[ErrHealNoSuchProcess-177]
189− _ = x[ErrHealInvalidClientToken-178]
190− _ = x[ErrHealMissingBucket-179]
191− _ = x[ErrHealAlreadyRunning-180]
192− _ = x[ErrHealOverlappingPaths-181]
193− _ = x[ErrIncorrectContinuationToken-182]
194− _ = x[ErrEmptyRequestBody-183]
195− _ = x[ErrUnsupportedFunction-184]
196− _ = x[ErrInvalidExpressionType-185]
197− _ = x[ErrBusy-186]
198− _ = x[ErrUnauthorizedAccess-187]
199− _ = x[ErrExpressionTooLong-188]
200− _ = x[ErrIllegalSQLFunctionArgument-189]
201− _ = x[ErrInvalidKeyPath-190]
202− _ = x[ErrInvalidCompressionFormat-191]
203− _ = x[ErrInvalidFileHeaderInfo-192]
204− _ = x[ErrInvalidJSONType-193]
205− _ = x[ErrInvalidQuoteFields-194]
206− _ = x[ErrInvalidRequestParameter-195]
207− _ = x[ErrInvalidDataType-196]
208− _ = x[ErrInvalidTextEncoding-197]
209− _ = x[ErrInvalidDataSource-198]
210− _ = x[ErrInvalidTableAlias-199]
211− _ = x[ErrMissingRequiredParameter-200]
212− _ = x[ErrObjectSerializationConflict-201]
213− _ = x[ErrUnsupportedSQLOperation-202]
214− _ = x[ErrUnsupportedSQLStructure-203]
215− _ = x[ErrUnsupportedSyntax-204]
216− _ = x[ErrUnsupportedRangeHeader-205]
217− _ = x[ErrLexerInvalidChar-206]
218− _ = x[ErrLexerInvalidOperator-207]
219− _ = x[ErrLexerInvalidLiteral-208]
220− _ = x[ErrLexerInvalidIONLiteral-209]
221− _ = x[ErrParseExpectedDatePart-210]
222− _ = x[ErrParseExpectedKeyword-211]
223− _ = x[ErrParseExpectedTokenType-212]
224− _ = x[ErrParseExpected2TokenTypes-213]
225− _ = x[ErrParseExpectedNumber-214]
226− _ = x[ErrParseExpectedRightParenBuiltinFunctionCall-215]
227− _ = x[ErrParseExpectedTypeName-216]
228− _ = x[ErrParseExpectedWhenClause-217]
229− _ = x[ErrParseUnsupportedToken-218]
230− _ = x[ErrParseUnsupportedLiteralsGroupBy-219]
231− _ = x[ErrParseExpectedMember-220]
232− _ = x[ErrParseUnsupportedSelect-221]
233− _ = x[ErrParseUnsupportedCase-222]
234− _ = x[ErrParseUnsupportedCaseClause-223]
235− _ = x[ErrParseUnsupportedAlias-224]
236− _ = x[ErrParseUnsupportedSyntax-225]
237− _ = x[ErrParseUnknownOperator-226]
238− _ = x[ErrParseMissingIdentAfterAt-227]
239− _ = x[ErrParseUnexpectedOperator-228]
240− _ = x[ErrParseUnexpectedTerm-229]
241− _ = x[ErrParseUnexpectedToken-230]
242− _ = x[ErrParseUnexpectedKeyword-231]
243− _ = x[ErrParseExpectedExpression-232]
244− _ = x[ErrParseExpectedLeftParenAfterCast-233]
245− _ = x[ErrParseExpectedLeftParenValueConstructor-234]
246− _ = x[ErrParseExpectedLeftParenBuiltinFunctionCall-235]
247− _ = x[ErrParseExpectedArgumentDelimiter-236]
248− _ = x[ErrParseCastArity-237]
249− _ = x[ErrParseInvalidTypeParam-238]
250− _ = x[ErrParseEmptySelect-239]
251− _ = x[ErrParseSelectMissingFrom-240]
252− _ = x[ErrParseExpectedIdentForGroupName-241]
253− _ = x[ErrParseExpectedIdentForAlias-242]
254− _ = x[ErrParseUnsupportedCallWithStar-243]
255− _ = x[ErrParseNonUnaryAgregateFunctionCall-244]
256− _ = x[ErrParseMalformedJoin-245]
257− _ = x[ErrParseExpectedIdentForAt-246]
258− _ = x[ErrParseAsteriskIsNotAloneInSelectList-247]
259− _ = x[ErrParseCannotMixSqbAndWildcardInSelectList-248]
260− _ = x[ErrParseInvalidContextForWildcardInSelectList-249]
261− _ = x[ErrIncorrectSQLFunctionArgumentType-250]
262− _ = x[ErrValueParseFailure-251]
263− _ = x[ErrEvaluatorInvalidArguments-252]
264− _ = x[ErrIntegerOverflow-253]
265− _ = x[ErrLikeInvalidInputs-254]
266− _ = x[ErrCastFailed-255]
267− _ = x[ErrInvalidCast-256]
268− _ = x[ErrEvaluatorInvalidTimestampFormatPattern-257]
269− _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing-258]
270− _ = x[ErrEvaluatorTimestampFormatPatternDuplicateFields-259]
271− _ = x[ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch-260]
272− _ = x[ErrEvaluatorUnterminatedTimestampFormatPatternToken-261]
273− _ = x[ErrEvaluatorInvalidTimestampFormatPatternToken-262]
274− _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbol-263]
275− _ = x[ErrEvaluatorBindingDoesNotExist-264]
276− _ = x[ErrMissingHeaders-265]
277− _ = x[ErrInvalidColumnIndex-266]
278− _ = x[ErrAdminConfigNotificationTargetsFailed-267]
279− _ = x[ErrAdminProfilerNotEnabled-268]
280− _ = x[ErrInvalidDecompressedSize-269]
281− _ = x[ErrAddUserInvalidArgument-270]
282− _ = x[ErrAdminAccountNotEligible-271]
283− _ = x[ErrAccountNotEligible-272]
284− _ = x[ErrAdminServiceAccountNotFound-273]
285− _ = x[ErrPostPolicyConditionInvalidFormat-274]
65+ _ = x[ErrReplicationNoMatchingRuleError-54]
66+ _ = x[ErrObjectRestoreAlreadyInProgress-55]
67+ _ = x[ErrNoSuchKey-56]
68+ _ = x[ErrNoSuchUpload-57]
69+ _ = x[ErrInvalidVersionID-58]
70+ _ = x[ErrNoSuchVersion-59]
71+ _ = x[ErrNotImplemented-60]
72+ _ = x[ErrPreconditionFailed-61]
73+ _ = x[ErrRequestTimeTooSkewed-62]
74+ _ = x[ErrSignatureDoesNotMatch-63]
75+ _ = x[ErrMethodNotAllowed-64]
76+ _ = x[ErrInvalidPart-65]
77+ _ = x[ErrInvalidPartOrder-66]
78+ _ = x[ErrAuthorizationHeaderMalformed-67]
79+ _ = x[ErrMalformedPOSTRequest-68]
80+ _ = x[ErrPOSTFileRequired-69]
81+ _ = x[ErrSignatureVersionNotSupported-70]
82+ _ = x[ErrBucketNotEmpty-71]
83+ _ = x[ErrAllAccessDisabled-72]
84+ _ = x[ErrMalformedPolicy-73]
85+ _ = x[ErrMissingFields-74]
86+ _ = x[ErrMissingCredTag-75]
87+ _ = x[ErrCredMalformed-76]
88+ _ = x[ErrInvalidRegion-77]
89+ _ = x[ErrInvalidServiceS3-78]
90+ _ = x[ErrInvalidServiceSTS-79]
91+ _ = x[ErrInvalidRequestVersion-80]
92+ _ = x[ErrMissingSignTag-81]
93+ _ = x[ErrMissingSignHeadersTag-82]
94+ _ = x[ErrMalformedDate-83]
95+ _ = x[ErrMalformedPresignedDate-84]
96+ _ = x[ErrMalformedCredentialDate-85]
97+ _ = x[ErrMalformedCredentialRegion-86]
98+ _ = x[ErrMalformedExpires-87]
99+ _ = x[ErrNegativeExpires-88]
100+ _ = x[ErrAuthHeaderEmpty-89]
101+ _ = x[ErrExpiredPresignRequest-90]
102+ _ = x[ErrRequestNotReadyYet-91]
103+ _ = x[ErrUnsignedHeaders-92]
104+ _ = x[ErrMissingDateHeader-93]
105+ _ = x[ErrInvalidQuerySignatureAlgo-94]
106+ _ = x[ErrInvalidQueryParams-95]
107+ _ = x[ErrBucketAlreadyOwnedByYou-96]
108+ _ = x[ErrInvalidDuration-97]
109+ _ = x[ErrBucketAlreadyExists-98]
110+ _ = x[ErrMetadataTooLarge-99]
111+ _ = x[ErrUnsupportedMetadata-100]
112+ _ = x[ErrMaximumExpires-101]
113+ _ = x[ErrSlowDown-102]
114+ _ = x[ErrInvalidPrefixMarker-103]
115+ _ = x[ErrBadRequest-104]
116+ _ = x[ErrKeyTooLongError-105]
117+ _ = x[ErrInvalidBucketObjectLockConfiguration-106]
118+ _ = x[ErrObjectLockConfigurationNotFound-107]
119+ _ = x[ErrObjectLockConfigurationNotAllowed-108]
120+ _ = x[ErrNoSuchObjectLockConfiguration-109]
121+ _ = x[ErrObjectLocked-110]
122+ _ = x[ErrInvalidRetentionDate-111]
123+ _ = x[ErrPastObjectLockRetainDate-112]
124+ _ = x[ErrUnknownWORMModeDirective-113]
125+ _ = x[ErrBucketTaggingNotFound-114]
126+ _ = x[ErrObjectLockInvalidHeaders-115]
127+ _ = x[ErrInvalidTagDirective-116]
128+ _ = x[ErrInvalidEncryptionMethod-117]
129+ _ = x[ErrInsecureSSECustomerRequest-118]
130+ _ = x[ErrSSEMultipartEncrypted-119]
131+ _ = x[ErrSSEEncryptedObject-120]
132+ _ = x[ErrInvalidEncryptionParameters-121]
133+ _ = x[ErrInvalidSSECustomerAlgorithm-122]
134+ _ = x[ErrInvalidSSECustomerKey-123]
135+ _ = x[ErrMissingSSECustomerKey-124]
136+ _ = x[ErrMissingSSECustomerKeyMD5-125]
137+ _ = x[ErrSSECustomerKeyMD5Mismatch-126]
138+ _ = x[ErrInvalidSSECustomerParameters-127]
139+ _ = x[ErrIncompatibleEncryptionMethod-128]
140+ _ = x[ErrKMSNotConfigured-129]
141+ _ = x[ErrNoAccessKey-130]
142+ _ = x[ErrInvalidToken-131]
143+ _ = x[ErrEventNotification-132]
144+ _ = x[ErrARNNotification-133]
145+ _ = x[ErrRegionNotification-134]
146+ _ = x[ErrOverlappingFilterNotification-135]
147+ _ = x[ErrFilterNameInvalid-136]
148+ _ = x[ErrFilterNamePrefix-137]
149+ _ = x[ErrFilterNameSuffix-138]
150+ _ = x[ErrFilterValueInvalid-139]
151+ _ = x[ErrOverlappingConfigs-140]
152+ _ = x[ErrUnsupportedNotification-141]
153+ _ = x[ErrContentSHA256Mismatch-142]
154+ _ = x[ErrReadQuorum-143]
155+ _ = x[ErrWriteQuorum-144]
156+ _ = x[ErrParentIsObject-145]
157+ _ = x[ErrStorageFull-146]
158+ _ = x[ErrRequestBodyParse-147]
159+ _ = x[ErrObjectExistsAsDirectory-148]
160+ _ = x[ErrInvalidObjectName-149]
161+ _ = x[ErrInvalidObjectNamePrefixSlash-150]
162+ _ = x[ErrInvalidResourceName-151]
163+ _ = x[ErrServerNotInitialized-152]
164+ _ = x[ErrOperationTimedOut-153]
165+ _ = x[ErrClientDisconnected-154]
166+ _ = x[ErrOperationMaxedOut-155]
167+ _ = x[ErrInvalidRequest-156]
168+ _ = x[ErrTransitionStorageClassNotFoundError-157]
169+ _ = x[ErrInvalidStorageClass-158]
170+ _ = x[ErrBackendDown-159]
171+ _ = x[ErrMalformedJSON-160]
172+ _ = x[ErrAdminNoSuchUser-161]
173+ _ = x[ErrAdminNoSuchGroup-162]
174+ _ = x[ErrAdminGroupNotEmpty-163]
175+ _ = x[ErrAdminNoSuchPolicy-164]
176+ _ = x[ErrAdminInvalidArgument-165]
177+ _ = x[ErrAdminInvalidAccessKey-166]
178+ _ = x[ErrAdminInvalidSecretKey-167]
179+ _ = x[ErrAdminConfigNoQuorum-168]
180+ _ = x[ErrAdminConfigTooLarge-169]
181+ _ = x[ErrAdminConfigBadJSON-170]
182+ _ = x[ErrAdminConfigDuplicateKeys-171]
183+ _ = x[ErrAdminCredentialsMismatch-172]
184+ _ = x[ErrInsecureClientRequest-173]
185+ _ = x[ErrObjectTampered-174]
186+ _ = x[ErrAdminBucketQuotaExceeded-175]
187+ _ = x[ErrAdminNoSuchQuotaConfiguration-176]
188+ _ = x[ErrHealNotImplemented-177]
189+ _ = x[ErrHealNoSuchProcess-178]
190+ _ = x[ErrHealInvalidClientToken-179]
191+ _ = x[ErrHealMissingBucket-180]
192+ _ = x[ErrHealAlreadyRunning-181]
193+ _ = x[ErrHealOverlappingPaths-182]
194+ _ = x[ErrIncorrectContinuationToken-183]
195+ _ = x[ErrEmptyRequestBody-184]
196+ _ = x[ErrUnsupportedFunction-185]
197+ _ = x[ErrInvalidExpressionType-186]
198+ _ = x[ErrBusy-187]
199+ _ = x[ErrUnauthorizedAccess-188]
200+ _ = x[ErrExpressionTooLong-189]
201+ _ = x[ErrIllegalSQLFunctionArgument-190]
202+ _ = x[ErrInvalidKeyPath-191]
203+ _ = x[ErrInvalidCompressionFormat-192]
204+ _ = x[ErrInvalidFileHeaderInfo-193]
205+ _ = x[ErrInvalidJSONType-194]
206+ _ = x[ErrInvalidQuoteFields-195]
207+ _ = x[ErrInvalidRequestParameter-196]
208+ _ = x[ErrInvalidDataType-197]
209+ _ = x[ErrInvalidTextEncoding-198]
210+ _ = x[ErrInvalidDataSource-199]
211+ _ = x[ErrInvalidTableAlias-200]
212+ _ = x[ErrMissingRequiredParameter-201]
213+ _ = x[ErrObjectSerializationConflict-202]
214+ _ = x[ErrUnsupportedSQLOperation-203]
215+ _ = x[ErrUnsupportedSQLStructure-204]
216+ _ = x[ErrUnsupportedSyntax-205]
217+ _ = x[ErrUnsupportedRangeHeader-206]
218+ _ = x[ErrLexerInvalidChar-207]
219+ _ = x[ErrLexerInvalidOperator-208]
220+ _ = x[ErrLexerInvalidLiteral-209]
221+ _ = x[ErrLexerInvalidIONLiteral-210]
222+ _ = x[ErrParseExpectedDatePart-211]
223+ _ = x[ErrParseExpectedKeyword-212]
224+ _ = x[ErrParseExpectedTokenType-213]
225+ _ = x[ErrParseExpected2TokenTypes-214]
226+ _ = x[ErrParseExpectedNumber-215]
227+ _ = x[ErrParseExpectedRightParenBuiltinFunctionCall-216]
228+ _ = x[ErrParseExpectedTypeName-217]
229+ _ = x[ErrParseExpectedWhenClause-218]
230+ _ = x[ErrParseUnsupportedToken-219]
231+ _ = x[ErrParseUnsupportedLiteralsGroupBy-220]
232+ _ = x[ErrParseExpectedMember-221]
233+ _ = x[ErrParseUnsupportedSelect-222]
234+ _ = x[ErrParseUnsupportedCase-223]
235+ _ = x[ErrParseUnsupportedCaseClause-224]
236+ _ = x[ErrParseUnsupportedAlias-225]
237+ _ = x[ErrParseUnsupportedSyntax-226]
238+ _ = x[ErrParseUnknownOperator-227]
239+ _ = x[ErrParseMissingIdentAfterAt-228]
240+ _ = x[ErrParseUnexpectedOperator-229]
241+ _ = x[ErrParseUnexpectedTerm-230]
242+ _ = x[ErrParseUnexpectedToken-231]
243+ _ = x[ErrParseUnexpectedKeyword-232]
244+ _ = x[ErrParseExpectedExpression-233]
245+ _ = x[ErrParseExpectedLeftParenAfterCast-234]
246+ _ = x[ErrParseExpectedLeftParenValueConstructor-235]
247+ _ = x[ErrParseExpectedLeftParenBuiltinFunctionCall-236]
248+ _ = x[ErrParseExpectedArgumentDelimiter-237]
249+ _ = x[ErrParseCastArity-238]
250+ _ = x[ErrParseInvalidTypeParam-239]
251+ _ = x[ErrParseEmptySelect-240]
252+ _ = x[ErrParseSelectMissingFrom-241]
253+ _ = x[ErrParseExpectedIdentForGroupName-242]
254+ _ = x[ErrParseExpectedIdentForAlias-243]
255+ _ = x[ErrParseUnsupportedCallWithStar-244]
256+ _ = x[ErrParseNonUnaryAgregateFunctionCall-245]
257+ _ = x[ErrParseMalformedJoin-246]
258+ _ = x[ErrParseExpectedIdentForAt-247]
259+ _ = x[ErrParseAsteriskIsNotAloneInSelectList-248]
260+ _ = x[ErrParseCannotMixSqbAndWildcardInSelectList-249]
261+ _ = x[ErrParseInvalidContextForWildcardInSelectList-250]
262+ _ = x[ErrIncorrectSQLFunctionArgumentType-251]
263+ _ = x[ErrValueParseFailure-252]
264+ _ = x[ErrEvaluatorInvalidArguments-253]
265+ _ = x[ErrIntegerOverflow-254]
266+ _ = x[ErrLikeInvalidInputs-255]
267+ _ = x[ErrCastFailed-256]
268+ _ = x[ErrInvalidCast-257]
269+ _ = x[ErrEvaluatorInvalidTimestampFormatPattern-258]
270+ _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing-259]
271+ _ = x[ErrEvaluatorTimestampFormatPatternDuplicateFields-260]
272+ _ = x[ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch-261]
273+ _ = x[ErrEvaluatorUnterminatedTimestampFormatPatternToken-262]
274+ _ = x[ErrEvaluatorInvalidTimestampFormatPatternToken-263]
275+ _ = x[ErrEvaluatorInvalidTimestampFormatPatternSymbol-264]
276+ _ = x[ErrEvaluatorBindingDoesNotExist-265]
277+ _ = x[ErrMissingHeaders-266]
278+ _ = x[ErrInvalidColumnIndex-267]
279+ _ = x[ErrAdminConfigNotificationTargetsFailed-268]
280+ _ = x[ErrAdminProfilerNotEnabled-269]
281+ _ = x[ErrInvalidDecompressedSize-270]
282+ _ = x[ErrAddUserInvalidArgument-271]
283+ _ = x[ErrAdminAccountNotEligible-272]
284+ _ = x[ErrAccountNotEligible-273]
285+ _ = x[ErrAdminServiceAccountNotFound-274]
286+ _ = x[ErrPostPolicyConditionInvalidFormat-275]
286287 }
287288
288−const _APIErrorCode_name = "NoneAccessDeniedBadDigestEntityTooSmallEntityTooLargePolicyTooLargeIncompleteBodyInternalErrorInvalidAccessKeyIDInvalidBucketNameInvalidDigestInvalidRangeInvalidRangePartNumberInvalidCopyPartRangeInvalidCopyPartRangeSourceInvalidMaxKeysInvalidEncodingMethodInvalidMaxUploadsInvalidMaxPartsInvalidPartNumberMarkerInvalidPartNumberInvalidRequestBodyInvalidCopySourceInvalidMetadataDirectiveInvalidCopyDestInvalidPolicyDocumentInvalidObjectStateMalformedXMLMissingContentLengthMissingContentMD5MissingRequestBodyErrorMissingSecurityHeaderNoSuchBucketNoSuchBucketPolicyNoSuchBucketLifecycleNoSuchLifecycleConfigurationNoSuchBucketSSEConfigNoSuchCORSConfigurationNoSuchWebsiteConfigurationReplicationConfigurationNotFoundErrorRemoteDestinationNotFoundErrorReplicationDestinationMissingLockRemoteTargetNotFoundErrorReplicationRemoteConnectionErrorBucketRemoteIdenticalToSourceBucketRemoteAlreadyExistsBucketRemoteLabelInUseBucketRemoteArnTypeInvalidBucketRemoteArnInvalidBucketRemoteRemoveDisallowedRemoteTargetNotVersionedErrorReplicationSourceNotVersionedErrorReplicationNeedsVersioningErrorReplicationBucketNeedsVersioningErrorObjectRestoreAlreadyInProgressNoSuchKeyNoSuchUploadInvalidVersionIDNoSuchVersionNotImplementedPreconditionFailedRequestTimeTooSkewedSignatureDoesNotMatchMethodNotAllowedInvalidPartInvalidPartOrderAuthorizationHeaderMalformedMalformedPOSTRequestPOSTFileRequiredSignatureVersionNotSupportedBucketNotEmptyAllAccessDisabledMalformedPolicyMissingFieldsMissingCredTagCredMalformedInvalidRegionInvalidServiceS3InvalidServiceSTSInvalidRequestVersionMissingSignTagMissingSignHeadersTagMalformedDateMalformedPresignedDateMalformedCredentialDateMalformedCredentialRegionMalformedExpiresNegativeExpiresAuthHeaderEmptyExpiredPresignRequestRequestNotReadyYetUnsignedHeadersMissingDateHeaderInvalidQuerySignatureAlgoInvalidQueryParamsBucketAlreadyOwnedByYouInvalidDurationBucketAlreadyExistsMetadataTooLargeUnsupportedMetadataMaximumExpiresSlowDownInvalidPrefixMarkerBadRequestKeyTooLongErrorInvalidBucketObjectLockConfigurationObjectLockConfigurationNotFoundObjectLockConfigurationNotAllowedNoSuchObjectLockConfigurationObjectLockedInvalidRetentionDatePastObjectLockRetainDateUnknownWORMModeDirectiveBucketTaggingNotFoundObjectLockInvalidHeadersInvalidTagDirectiveInvalidEncryptionMethodInsecureSSECustomerRequestSSEMultipartEncryptedSSEEncryptedObjectInvalidEncryptionParametersInvalidSSECustomerAlgorithmInvalidSSECustomerKeyMissingSSECustomerKeyMissingSSECustomerKeyMD5SSECustomerKeyMD5MismatchInvalidSSECustomerParametersIncompatibleEncryptionMethodKMSNotConfiguredNoAccessKeyInvalidTokenEventNotificationARNNotificationRegionNotificationOverlappingFilterNotificationFilterNameInvalidFilterNamePrefixFilterNameSuffixFilterValueInvalidOverlappingConfigsUnsupportedNotificationContentSHA256MismatchReadQuorumWriteQuorumParentIsObjectStorageFullRequestBodyParseObjectExistsAsDirectoryInvalidObjectNameInvalidObjectNamePrefixSlashInvalidResourceNameServerNotInitializedOperationTimedOutClientDisconnectedOperationMaxedOutInvalidRequestTransitionStorageClassNotFoundErrorInvalidStorageClassBackendDownMalformedJSONAdminNoSuchUserAdminNoSuchGroupAdminGroupNotEmptyAdminNoSuchPolicyAdminInvalidArgumentAdminInvalidAccessKeyAdminInvalidSecretKeyAdminConfigNoQuorumAdminConfigTooLargeAdminConfigBadJSONAdminConfigDuplicateKeysAdminCredentialsMismatchInsecureClientRequestObjectTamperedAdminBucketQuotaExceededAdminNoSuchQuotaConfigurationHealNotImplementedHealNoSuchProcessHealInvalidClientTokenHealMissingBucketHealAlreadyRunningHealOverlappingPathsIncorrectContinuationTokenEmptyRequestBodyUnsupportedFunctionInvalidExpressionTypeBusyUnauthorizedAccessExpressionTooLongIllegalSQLFunctionArgumentInvalidKeyPathInvalidCompressionFormatInvalidFileHeaderInfoInvalidJSONTypeInvalidQuoteFieldsInvalidRequestParameterInvalidDataTypeInvalidTextEncodingInvalidDataSourceInvalidTableAliasMissingRequiredParameterObjectSerializationConflictUnsupportedSQLOperationUnsupportedSQLStructureUnsupportedSyntaxUnsupportedRangeHeaderLexerInvalidCharLexerInvalidOperatorLexerInvalidLiteralLexerInvalidIONLiteralParseExpectedDatePartParseExpectedKeywordParseExpectedTokenTypeParseExpected2TokenTypesParseExpectedNumberParseExpectedRightParenBuiltinFunctionCallParseExpectedTypeNameParseExpectedWhenClauseParseUnsupportedTokenParseUnsupportedLiteralsGroupByParseExpectedMemberParseUnsupportedSelectParseUnsupportedCaseParseUnsupportedCaseClauseParseUnsupportedAliasParseUnsupportedSyntaxParseUnknownOperatorParseMissingIdentAfterAtParseUnexpectedOperatorParseUnexpectedTermParseUnexpectedTokenParseUnexpectedKeywordParseExpectedExpressionParseExpectedLeftParenAfterCastParseExpectedLeftParenValueConstructorParseExpectedLeftParenBuiltinFunctionCallParseExpectedArgumentDelimiterParseCastArityParseInvalidTypeParamParseEmptySelectParseSelectMissingFromParseExpectedIdentForGroupNameParseExpectedIdentForAliasParseUnsupportedCallWithStarParseNonUnaryAgregateFunctionCallParseMalformedJoinParseExpectedIdentForAtParseAsteriskIsNotAloneInSelectListParseCannotMixSqbAndWildcardInSelectListParseInvalidContextForWildcardInSelectListIncorrectSQLFunctionArgumentTypeValueParseFailureEvaluatorInvalidArgumentsIntegerOverflowLikeInvalidInputsCastFailedInvalidCastEvaluatorInvalidTimestampFormatPatternEvaluatorInvalidTimestampFormatPatternSymbolForParsingEvaluatorTimestampFormatPatternDuplicateFieldsEvaluatorTimestampFormatPatternHourClockAmPmMismatchEvaluatorUnterminatedTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternSymbolEvaluatorBindingDoesNotExistMissingHeadersInvalidColumnIndexAdminConfigNotificationTargetsFailedAdminProfilerNotEnabledInvalidDecompressedSizeAddUserInvalidArgumentAdminAccountNotEligibleAccountNotEligibleAdminServiceAccountNotFoundPostPolicyConditionInvalidFormat"
289+const _APIErrorCode_name = "NoneAccessDeniedBadDigestEntityTooSmallEntityTooLargePolicyTooLargeIncompleteBodyInternalErrorInvalidAccessKeyIDInvalidBucketNameInvalidDigestInvalidRangeInvalidRangePartNumberInvalidCopyPartRangeInvalidCopyPartRangeSourceInvalidMaxKeysInvalidEncodingMethodInvalidMaxUploadsInvalidMaxPartsInvalidPartNumberMarkerInvalidPartNumberInvalidRequestBodyInvalidCopySourceInvalidMetadataDirectiveInvalidCopyDestInvalidPolicyDocumentInvalidObjectStateMalformedXMLMissingContentLengthMissingContentMD5MissingRequestBodyErrorMissingSecurityHeaderNoSuchBucketNoSuchBucketPolicyNoSuchBucketLifecycleNoSuchLifecycleConfigurationNoSuchBucketSSEConfigNoSuchCORSConfigurationNoSuchWebsiteConfigurationReplicationConfigurationNotFoundErrorRemoteDestinationNotFoundErrorReplicationDestinationMissingLockRemoteTargetNotFoundErrorReplicationRemoteConnectionErrorBucketRemoteIdenticalToSourceBucketRemoteAlreadyExistsBucketRemoteLabelInUseBucketRemoteArnTypeInvalidBucketRemoteArnInvalidBucketRemoteRemoveDisallowedRemoteTargetNotVersionedErrorReplicationSourceNotVersionedErrorReplicationNeedsVersioningErrorReplicationBucketNeedsVersioningErrorReplicationNoMatchingRuleErrorObjectRestoreAlreadyInProgressNoSuchKeyNoSuchUploadInvalidVersionIDNoSuchVersionNotImplementedPreconditionFailedRequestTimeTooSkewedSignatureDoesNotMatchMethodNotAllowedInvalidPartInvalidPartOrderAuthorizationHeaderMalformedMalformedPOSTRequestPOSTFileRequiredSignatureVersionNotSupportedBucketNotEmptyAllAccessDisabledMalformedPolicyMissingFieldsMissingCredTagCredMalformedInvalidRegionInvalidServiceS3InvalidServiceSTSInvalidRequestVersionMissingSignTagMissingSignHeadersTagMalformedDateMalformedPresignedDateMalformedCredentialDateMalformedCredentialRegionMalformedExpiresNegativeExpiresAuthHeaderEmptyExpiredPresignRequestRequestNotReadyYetUnsignedHeadersMissingDateHeaderInvalidQuerySignatureAlgoInvalidQueryParamsBucketAlreadyOwnedByYouInvalidDurationBucketAlreadyExistsMetadataTooLargeUnsupportedMetadataMaximumExpiresSlowDownInvalidPrefixMarkerBadRequestKeyTooLongErrorInvalidBucketObjectLockConfigurationObjectLockConfigurationNotFoundObjectLockConfigurationNotAllowedNoSuchObjectLockConfigurationObjectLockedInvalidRetentionDatePastObjectLockRetainDateUnknownWORMModeDirectiveBucketTaggingNotFoundObjectLockInvalidHeadersInvalidTagDirectiveInvalidEncryptionMethodInsecureSSECustomerRequestSSEMultipartEncryptedSSEEncryptedObjectInvalidEncryptionParametersInvalidSSECustomerAlgorithmInvalidSSECustomerKeyMissingSSECustomerKeyMissingSSECustomerKeyMD5SSECustomerKeyMD5MismatchInvalidSSECustomerParametersIncompatibleEncryptionMethodKMSNotConfiguredNoAccessKeyInvalidTokenEventNotificationARNNotificationRegionNotificationOverlappingFilterNotificationFilterNameInvalidFilterNamePrefixFilterNameSuffixFilterValueInvalidOverlappingConfigsUnsupportedNotificationContentSHA256MismatchReadQuorumWriteQuorumParentIsObjectStorageFullRequestBodyParseObjectExistsAsDirectoryInvalidObjectNameInvalidObjectNamePrefixSlashInvalidResourceNameServerNotInitializedOperationTimedOutClientDisconnectedOperationMaxedOutInvalidRequestTransitionStorageClassNotFoundErrorInvalidStorageClassBackendDownMalformedJSONAdminNoSuchUserAdminNoSuchGroupAdminGroupNotEmptyAdminNoSuchPolicyAdminInvalidArgumentAdminInvalidAccessKeyAdminInvalidSecretKeyAdminConfigNoQuorumAdminConfigTooLargeAdminConfigBadJSONAdminConfigDuplicateKeysAdminCredentialsMismatchInsecureClientRequestObjectTamperedAdminBucketQuotaExceededAdminNoSuchQuotaConfigurationHealNotImplementedHealNoSuchProcessHealInvalidClientTokenHealMissingBucketHealAlreadyRunningHealOverlappingPathsIncorrectContinuationTokenEmptyRequestBodyUnsupportedFunctionInvalidExpressionTypeBusyUnauthorizedAccessExpressionTooLongIllegalSQLFunctionArgumentInvalidKeyPathInvalidCompressionFormatInvalidFileHeaderInfoInvalidJSONTypeInvalidQuoteFieldsInvalidRequestParameterInvalidDataTypeInvalidTextEncodingInvalidDataSourceInvalidTableAliasMissingRequiredParameterObjectSerializationConflictUnsupportedSQLOperationUnsupportedSQLStructureUnsupportedSyntaxUnsupportedRangeHeaderLexerInvalidCharLexerInvalidOperatorLexerInvalidLiteralLexerInvalidIONLiteralParseExpectedDatePartParseExpectedKeywordParseExpectedTokenTypeParseExpected2TokenTypesParseExpectedNumberParseExpectedRightParenBuiltinFunctionCallParseExpectedTypeNameParseExpectedWhenClauseParseUnsupportedTokenParseUnsupportedLiteralsGroupByParseExpectedMemberParseUnsupportedSelectParseUnsupportedCaseParseUnsupportedCaseClauseParseUnsupportedAliasParseUnsupportedSyntaxParseUnknownOperatorParseMissingIdentAfterAtParseUnexpectedOperatorParseUnexpectedTermParseUnexpectedTokenParseUnexpectedKeywordParseExpectedExpressionParseExpectedLeftParenAfterCastParseExpectedLeftParenValueConstructorParseExpectedLeftParenBuiltinFunctionCallParseExpectedArgumentDelimiterParseCastArityParseInvalidTypeParamParseEmptySelectParseSelectMissingFromParseExpectedIdentForGroupNameParseExpectedIdentForAliasParseUnsupportedCallWithStarParseNonUnaryAgregateFunctionCallParseMalformedJoinParseExpectedIdentForAtParseAsteriskIsNotAloneInSelectListParseCannotMixSqbAndWildcardInSelectListParseInvalidContextForWildcardInSelectListIncorrectSQLFunctionArgumentTypeValueParseFailureEvaluatorInvalidArgumentsIntegerOverflowLikeInvalidInputsCastFailedInvalidCastEvaluatorInvalidTimestampFormatPatternEvaluatorInvalidTimestampFormatPatternSymbolForParsingEvaluatorTimestampFormatPatternDuplicateFieldsEvaluatorTimestampFormatPatternHourClockAmPmMismatchEvaluatorUnterminatedTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternSymbolEvaluatorBindingDoesNotExistMissingHeadersInvalidColumnIndexAdminConfigNotificationTargetsFailedAdminProfilerNotEnabledInvalidDecompressedSizeAddUserInvalidArgumentAdminAccountNotEligibleAccountNotEligibleAdminServiceAccountNotFoundPostPolicyConditionInvalidFormat"
289290
290−var _APIErrorCode_index = [...]uint16{0, 4, 16, 25, 39, 53, 67, 81, 94, 112, 129, 142, 154, 176, 196, 222, 236, 257, 274, 289, 312, 329, 347, 364, 388, 403, 424, 442, 454, 474, 491, 514, 535, 547, 565, 586, 614, 635, 658, 684, 721, 751, 784, 809, 841, 870, 895, 917, 943, 965, 993, 1022, 1056, 1087, 1124, 1154, 1163, 1175, 1191, 1204, 1218, 1236, 1256, 1277, 1293, 1304, 1320, 1348, 1368, 1384, 1412, 1426, 1443, 1458, 1471, 1485, 1498, 1511, 1527, 1544, 1565, 1579, 1600, 1613, 1635, 1658, 1683, 1699, 1714, 1729, 1750, 1768, 1783, 1800, 1825, 1843, 1866, 1881, 1900, 1916, 1935, 1949, 1957, 1976, 1986, 2001, 2037, 2068, 2101, 2130, 2142, 2162, 2186, 2210, 2231, 2255, 2274, 2297, 2323, 2344, 2362, 2389, 2416, 2437, 2458, 2482, 2507, 2535, 2563, 2579, 2590, 2602, 2619, 2634, 2652, 2681, 2698, 2714, 2730, 2748, 2766, 2789, 2810, 2820, 2831, 2845, 2856, 2872, 2895, 2912, 2940, 2959, 2979, 2996, 3014, 3031, 3045, 3080, 3099, 3110, 3123, 3138, 3154, 3172, 3189, 3209, 3230, 3251, 3270, 3289, 3307, 3331, 3355, 3376, 3390, 3414, 3443, 3461, 3478, 3500, 3517, 3535, 3555, 3581, 3597, 3616, 3637, 3641, 3659, 3676, 3702, 3716, 3740, 3761, 3776, 3794, 3817, 3832, 3851, 3868, 3885, 3909, 3936, 3959, 3982, 3999, 4021, 4037, 4057, 4076, 4098, 4119, 4139, 4161, 4185, 4204, 4246, 4267, 4290, 4311, 4342, 4361, 4383, 4403, 4429, 4450, 4472, 4492, 4516, 4539, 4558, 4578, 4600, 4623, 4654, 4692, 4733, 4763, 4777, 4798, 4814, 4836, 4866, 4892, 4920, 4953, 4971, 4994, 5029, 5069, 5111, 5143, 5160, 5185, 5200, 5217, 5227, 5238, 5276, 5330, 5376, 5428, 5476, 5519, 5563, 5591, 5605, 5623, 5659, 5682, 5705, 5727, 5750, 5768, 5795, 5827}
291+var _APIErrorCode_index = [...]uint16{0, 4, 16, 25, 39, 53, 67, 81, 94, 112, 129, 142, 154, 176, 196, 222, 236, 257, 274, 289, 312, 329, 347, 364, 388, 403, 424, 442, 454, 474, 491, 514, 535, 547, 565, 586, 614, 635, 658, 684, 721, 751, 784, 809, 841, 870, 895, 917, 943, 965, 993, 1022, 1056, 1087, 1124, 1154, 1184, 1193, 1205, 1221, 1234, 1248, 1266, 1286, 1307, 1323, 1334, 1350, 1378, 1398, 1414, 1442, 1456, 1473, 1488, 1501, 1515, 1528, 1541, 1557, 1574, 1595, 1609, 1630, 1643, 1665, 1688, 1713, 1729, 1744, 1759, 1780, 1798, 1813, 1830, 1855, 1873, 1896, 1911, 1930, 1946, 1965, 1979, 1987, 2006, 2016, 2031, 2067, 2098, 2131, 2160, 2172, 2192, 2216, 2240, 2261, 2285, 2304, 2327, 2353, 2374, 2392, 2419, 2446, 2467, 2488, 2512, 2537, 2565, 2593, 2609, 2620, 2632, 2649, 2664, 2682, 2711, 2728, 2744, 2760, 2778, 2796, 2819, 2840, 2850, 2861, 2875, 2886, 2902, 2925, 2942, 2970, 2989, 3009, 3026, 3044, 3061, 3075, 3110, 3129, 3140, 3153, 3168, 3184, 3202, 3219, 3239, 3260, 3281, 3300, 3319, 3337, 3361, 3385, 3406, 3420, 3444, 3473, 3491, 3508, 3530, 3547, 3565, 3585, 3611, 3627, 3646, 3667, 3671, 3689, 3706, 3732, 3746, 3770, 3791, 3806, 3824, 3847, 3862, 3881, 3898, 3915, 3939, 3966, 3989, 4012, 4029, 4051, 4067, 4087, 4106, 4128, 4149, 4169, 4191, 4215, 4234, 4276, 4297, 4320, 4341, 4372, 4391, 4413, 4433, 4459, 4480, 4502, 4522, 4546, 4569, 4588, 4608, 4630, 4653, 4684, 4722, 4763, 4793, 4807, 4828, 4844, 4866, 4896, 4922, 4950, 4983, 5001, 5024, 5059, 5099, 5141, 5173, 5190, 5215, 5230, 5247, 5257, 5268, 5306, 5360, 5406, 5458, 5506, 5549, 5593, 5621, 5635, 5653, 5689, 5712, 5735, 5757, 5780, 5798, 5825, 5857}
291292
292293 func (i APIErrorCode) String() string {
293294 if i < 0 || i >= APIErrorCode(len(_APIErrorCode_index)-1) {
cmd/bucket-handlers.go+89−1
@@ -33,6 +33,7 @@ import ( · 32 unmodified lines
3333 "strconv"
3434 "strings"
3535 "sync"
36+ "time"
3637
3738 "github.com/google/uuid"
3839 "github.com/gorilla/mux"
@@ -611,7 +612,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, · 572 unmodified lines
611612
612613 if replicateDeletes {
613614 if dobj.DeleteMarkerReplicationStatus == string(replication.Pending) || dobj.VersionPurgeStatus == Pending {
614− dv := DeletedObjectVersionInfo{
615+ dv := DeletedObjectReplicationInfo{
615616 DeletedObject: dobj,
616617 Bucket: bucket,
617618 }
@@ -1686,3 +1687,90 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseW · 1068 unmodified lines
16861687 }
16871688 w.(http.Flusher).Flush()
16881689 }
1690+
1691+// ResetBucketReplicationStateHandler - starts a replication reset for all objects in a bucket which
1692+// qualify for replication and re-sync the object(s) to target, provided ExistingObjectReplication is
1693+// enabled for the qualifying rule. This API is a MinIO only extension provided for situations where
1694+// remote target is entirely lost,and previously replicated objects need to be re-synced.
1695+func (api objectAPIHandlers) ResetBucketReplicationStateHandler(w http.ResponseWriter, r *http.Request) {
1696+ ctx := newContext(r, w, "ResetBucketReplicationState")
1697+
1698+ defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
1699+
1700+ vars := mux.Vars(r)
1701+ bucket := vars["bucket"]
1702+ durationStr := r.URL.Query().Get("older-than")
1703+ var (
1704+ days time.Duration
1705+ err error
1706+ )
1707+ if durationStr != "" {
1708+ days, err = time.ParseDuration(durationStr)
1709+ if err != nil {
1710+ writeErrorResponse(ctx, w, toAPIError(ctx, InvalidArgument{
1711+ Bucket: bucket,
1712+ Err: fmt.Errorf("invalid query parameter older-than %s for %s : %w", durationStr, bucket, err),
1713+ }), r.URL, guessIsBrowserReq(r))
1714+ }
1715+ }
1716+
1717+ objectAPI := api.ObjectAPI()
1718+ if objectAPI == nil {
1719+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
1720+ return
1721+ }
1722+
1723+ if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone {
1724+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
1725+ return
1726+ }
1727+
1728+ // Check if bucket exists.
1729+ if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil {
1730+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
1731+ return
1732+ }
1733+
1734+ config, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
1735+ if err != nil {
1736+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
1737+ return
1738+ }
1739+ if !config.HasActiveRules("", true) {
1740+ writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoMatchingRuleError), r.URL, guessIsBrowserReq(r))
1741+ return
1742+ }
1743+ target := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, config.RoleArn)
1744+ target.ResetBeforeDate = UTCNow().AddDate(0, 0, -1*int(days/24))
1745+ target.ResetID = mustGetUUID()
1746+ if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, true); err != nil {
1747+ switch err.(type) {
1748+ case BucketRemoteConnectionErr:
1749+ writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationRemoteConnectionError, err), r.URL)
1750+ default:
1751+ writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
1752+ }
1753+ return
1754+ }
1755+ targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
1756+ if err != nil {
1757+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
1758+ return
1759+ }
1760+ tgtBytes, err := json.Marshal(&targets)
1761+ if err != nil {
1762+ writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
1763+ return
1764+ }
1765+ if err = globalBucketMetadataSys.Update(bucket, bucketTargetsFile, tgtBytes); err != nil {
1766+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
1767+ return
1768+ }
1769+ data, err := json.Marshal(target.ResetID)
1770+ if err != nil {
1771+ writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
1772+ return
1773+ }
1774+ // Write success response.
1775+ writeSuccessResponseJSON(w, data)
1776+}
cmd/bucket-replication.go+201−43
@@ -91,32 +91,70 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re · 90 unmodified lines
9191 return false, BucketRemoteTargetNotFound{Bucket: bucket}
9292 }
9393
94−func mustReplicateWeb(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string, permErr APIErrorCode) (replicate bool, sync bool) {
94+type mustReplicateOptions struct {
95+ meta map[string]string
96+ status replication.StatusType
97+ opType replication.Type
98+}
99+
100+func (o mustReplicateOptions) ReplicationStatus() (s replication.StatusType) {
101+ if rs, ok := o.meta[xhttp.AmzBucketReplicationStatus]; ok {
102+ return replication.StatusType(rs)
103+ }
104+ return s
105+}
106+func (o mustReplicateOptions) isExistingObjectReplication() bool {
107+ return o.opType == replication.ExistingObjectReplicationType
108+}
109+
110+func (o mustReplicateOptions) isMetadataReplication() bool {
111+ return o.opType == replication.MetadataReplicationType
112+}
113+func getMustReplicateOptions(o ObjectInfo, op replication.Type) mustReplicateOptions {
114+ if !op.Valid() {
115+ op = replication.ObjectReplicationType
116+ if o.metadataOnly {
117+ op = replication.MetadataReplicationType
118+ }
119+ }
120+ meta := cloneMSS(o.UserDefined)
121+ if o.UserTags != "" {
122+ meta[xhttp.AmzObjectTagging] = o.UserTags
123+ }
124+ return mustReplicateOptions{
125+ meta: meta,
126+ status: o.ReplicationStatus,
127+ opType: op,
128+ }
129+}
130+func mustReplicateWeb(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus replication.StatusType, permErr APIErrorCode) (replicate bool, sync bool) {
95131 if permErr != ErrNone {
96132 return
97133 }
98− return mustReplicater(ctx, bucket, object, meta, replStatus, false)
134+ return mustReplicater(ctx, bucket, object, mustReplicateOptions{
135+ meta: meta,
136+ status: replStatus,
137+ opType: replication.ObjectReplicationType,
138+ })
99139 }
100140
101141 // mustReplicate returns 2 booleans - true if object meets replication criteria and true if replication is to be done in
102142 // a synchronous manner.
103−func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string, metadataOnly bool) (replicate bool, sync bool) {
143+func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, opts mustReplicateOptions) (replicate bool, sync bool) {
104144 if s3Err := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, "", r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone {
105145 return
106146 }
107− return mustReplicater(ctx, bucket, object, meta, replStatus, metadataOnly)
147+ return mustReplicater(ctx, bucket, object, opts)
108148 }
109149
110150 // mustReplicater returns 2 booleans - true if object meets replication criteria and true if replication is to be done in
111151 // a synchronous manner.
112−func mustReplicater(ctx context.Context, bucket, object string, meta map[string]string, replStatus string, metadataOnly bool) (replicate bool, sync bool) {
152+func mustReplicater(ctx context.Context, bucket, object string, mopts mustReplicateOptions) (replicate bool, sync bool) {
113153 if globalIsGateway {
114154 return replicate, sync
115155 }
116− if rs, ok := meta[xhttp.AmzBucketReplicationStatus]; ok {
117− replStatus = rs
118− }
119− if replication.StatusType(replStatus) == replication.Replica && !metadataOnly {
156+ replStatus := mopts.ReplicationStatus()
157+ if replStatus == replication.Replica && !mopts.isMetadataReplication() {
120158 return replicate, sync
121159 }
122160 cfg, err := getReplicationConfig(ctx, bucket)
@@ -124,11 +162,12 @@ func mustReplicater(ctx context.Context, bucket, object string, meta map[string] · 1 unmodified line
124162 return replicate, sync
125163 }
126164 opts := replication.ObjectOpts{
127− Name: object,
128− SSEC: crypto.SSEC.IsEncrypted(meta),
129− Replica: replication.StatusType(replStatus) == replication.Replica,
165+ Name: object,
166+ SSEC: crypto.SSEC.IsEncrypted(mopts.meta),
167+ Replica: replStatus == replication.Replica,
168+ ExistingObject: mopts.isExistingObjectReplication(),
130169 }
131− tagStr, ok := meta[xhttp.AmzObjectTagging]
170+ tagStr, ok := mopts.meta[xhttp.AmzObjectTagging]
132171 if ok {
133172 opts.UserTags = tagStr
134173 }
@@ -226,7 +265,7 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet · 91 unmodified lines
226265 // target cluster, the object version is marked deleted on the source and hidden from listing. It is permanently
227266 // deleted from the source when the VersionPurgeStatus changes to "Complete", i.e after replication succeeds
228267 // on target.
229−func replicateDelete(ctx context.Context, dobj DeletedObjectVersionInfo, objectAPI ObjectLayer) {
268+func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, objectAPI ObjectLayer) {
230269 bucket := dobj.Bucket
231270 versionID := dobj.DeleteMarkerVersionID
232271 if versionID == "" {
@@ -760,7 +799,9 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje · 527 unmodified lines
760799 if objInfo.UserTags != "" {
761800 objInfo.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
762801 }
763−
802+ if ri.OpType == replication.ExistingObjectReplicationType {
803+ objInfo.UserDefined[xhttp.MinIOReplicationResetStatus] = fmt.Sprintf("%s;%s", UTCNow().Format(http.TimeFormat), ri.ResetID)
804+ }
764805 // FIXME: add support for missing replication events
765806 // - event.ObjectReplicationMissedThreshold
766807 // - event.ObjectReplicationReplicatedAfterThreshold
@@ -774,7 +815,8 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje · 7 unmodified lines
774815 return
775816 }
776817 // Leave metadata in `PENDING` state if inline replication fails to save iops
777− if ri.OpType == replication.HealReplicationType || replicationStatus == replication.Completed {
818+ if ri.OpType == replication.HealReplicationType ||
819+ replicationStatus == replication.Completed {
778820 // This lower level implementation is necessary to avoid write locks from CopyObject.
779821 poolIdx, err := z.getPoolIdx(ctx, bucket, object, objInfo.Size)
780822 if err != nil {
@@ -834,10 +876,12 @@ func filterReplicationStatusMetadata(metadata map[string]string) map[string]stri · 53 unmodified lines
834876 return dst
835877 }
836878
837−// DeletedObjectVersionInfo has info on deleted object
838−type DeletedObjectVersionInfo struct {
879+// DeletedObjectReplicationInfo has info on deleted object
880+type DeletedObjectReplicationInfo struct {
839881 DeletedObject
840− Bucket string
882+ Bucket string
883+ OpType replication.Type
884+ ResetID string
841885 }
842886
843887 var (
@@ -847,35 +891,40 @@ var ( · 3 unmodified lines
847891
848892 // ReplicationPool describes replication pool
849893 type ReplicationPool struct {
850− objLayer ObjectLayer
851− ctx context.Context
852− mrfWorkerKillCh chan struct{}
853− workerKillCh chan struct{}
854− replicaCh chan ReplicateObjectInfo
855− replicaDeleteCh chan DeletedObjectVersionInfo
856− mrfReplicaCh chan ReplicateObjectInfo
857− workerSize int
858− mrfWorkerSize int
859− workerWg sync.WaitGroup
860− mrfWorkerWg sync.WaitGroup
861− once sync.Once
862− mu sync.Mutex
894+ objLayer ObjectLayer
895+ ctx context.Context
896+ mrfWorkerKillCh chan struct{}
897+ workerKillCh chan struct{}
898+ replicaCh chan ReplicateObjectInfo
899+ replicaDeleteCh chan DeletedObjectReplicationInfo
900+ mrfReplicaCh chan ReplicateObjectInfo
901+ existingReplicaCh chan ReplicateObjectInfo
902+ existingReplicaDeleteCh chan DeletedObjectReplicationInfo
903+ workerSize int
904+ mrfWorkerSize int
905+ workerWg sync.WaitGroup
906+ mrfWorkerWg sync.WaitGroup
907+ once sync.Once
908+ mu sync.Mutex
863909 }
864910
865911 // NewReplicationPool creates a pool of replication workers of specified size
866912 func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPoolOpts) *ReplicationPool {
867913 pool := &ReplicationPool{
868− replicaCh: make(chan ReplicateObjectInfo, 100000),
869− replicaDeleteCh: make(chan DeletedObjectVersionInfo, 100000),
870− mrfReplicaCh: make(chan ReplicateObjectInfo, 100000),
871− workerKillCh: make(chan struct{}, opts.Workers),
872− mrfWorkerKillCh: make(chan struct{}, opts.FailedWorkers),
873− ctx: ctx,
874− objLayer: o,
914+ replicaCh: make(chan ReplicateObjectInfo, 100000),
915+ replicaDeleteCh: make(chan DeletedObjectReplicationInfo, 100000),
916+ mrfReplicaCh: make(chan ReplicateObjectInfo, 100000),
917+ workerKillCh: make(chan struct{}, opts.Workers),
918+ mrfWorkerKillCh: make(chan struct{}, opts.FailedWorkers),
919+ existingReplicaCh: make(chan ReplicateObjectInfo, 100000),
920+ existingReplicaDeleteCh: make(chan DeletedObjectReplicationInfo, 100000),
921+ ctx: ctx,
922+ objLayer: o,
875923 }
876924
877925 pool.ResizeWorkers(opts.Workers)
878926 pool.ResizeFailedWorkers(opts.FailedWorkers)
927+ go pool.AddExistingObjectReplicateWorker()
879928 return pool
880929 }
881930
@@ -921,6 +970,26 @@ func (p *ReplicationPool) AddWorker() { · 39 unmodified lines
921970
922971 }
923972
973+// AddExistingObjectReplicateWorker adds a worker to queue existing objects that need to be sync'd
974+func (p *ReplicationPool) AddExistingObjectReplicateWorker() {
975+ for {
976+ select {
977+ case <-p.ctx.Done():
978+ return
979+ case oi, ok := <-p.existingReplicaCh:
980+ if !ok {
981+ return
982+ }
983+ replicateObject(p.ctx, oi, p.objLayer)
984+ case doi, ok := <-p.existingReplicaDeleteCh:
985+ if !ok {
986+ return
987+ }
988+ replicateDelete(p.ctx, doi, p.objLayer)
989+ }
990+ }
991+}
992+
924993 // ResizeWorkers sets replication workers pool to new size
925994 func (p *ReplicationPool) ResizeWorkers(n int) {
926995 p.mu.Lock()
@@ -962,6 +1031,7 @@ func (p *ReplicationPool) queueReplicaFailedTask(ri ReplicateObjectInfo) { · 35 unmodified lines
9621031 p.once.Do(func() {
9631032 close(p.replicaCh)
9641033 close(p.mrfReplicaCh)
1034+ close(p.existingReplicaCh)
9651035 })
9661036 case p.mrfReplicaCh <- ri:
9671037 default:
@@ -972,27 +1042,44 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) { · 4 unmodified lines
9721042 if p == nil {
9731043 return
9741044 }
1045+ var ch chan ReplicateObjectInfo
1046+ switch ri.OpType {
1047+ case replication.ExistingObjectReplicationType:
1048+ ch = p.existingReplicaCh
1049+ default:
1050+ ch = p.replicaCh
1051+ }
9751052 select {
9761053 case <-GlobalContext.Done():
9771054 p.once.Do(func() {
9781055 close(p.replicaCh)
9791056 close(p.mrfReplicaCh)
1057+ close(p.existingReplicaCh)
9801058 })
981− case p.replicaCh <- ri:
1059+ case ch <- ri:
9821060 default:
9831061 }
9841062 }
9851063
986−func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectVersionInfo) {
1064+func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInfo) {
9871065 if p == nil {
9881066 return
9891067 }
1068+ var ch chan DeletedObjectReplicationInfo
1069+ switch doi.OpType {
1070+ case replication.ExistingObjectReplicationType:
1071+ ch = p.existingReplicaDeleteCh
1072+ default:
1073+ ch = p.replicaDeleteCh
1074+ }
1075+
9901076 select {
9911077 case <-GlobalContext.Done():
9921078 p.once.Do(func() {
9931079 close(p.replicaDeleteCh)
1080+ close(p.existingReplicaDeleteCh)
9941081 })
995− case p.replicaDeleteCh <- doi:
1082+ case ch <- doi:
9961083 default:
9971084 }
9981085 }
@@ -1157,7 +1244,78 @@ func scheduleReplication(ctx context.Context, objInfo ObjectInfo, o ObjectLayer, · 158 unmodified lines
11571244 }
11581245 }
11591246
1160−func scheduleReplicationDelete(ctx context.Context, dv DeletedObjectVersionInfo, o ObjectLayer, sync bool) {
1247+func scheduleReplicationDelete(ctx context.Context, dv DeletedObjectReplicationInfo, o ObjectLayer, sync bool) {
11611248 globalReplicationPool.queueReplicaDeleteTask(dv)
11621249 globalReplicationStats.Update(dv.Bucket, 0, replication.Pending, replication.StatusType(""), replication.DeleteReplicationType)
11631250 }
1251+
1252+type replicationConfig struct {
1253+ Config *replication.Config
1254+ ResetID string
1255+ ResetBeforeDate time.Time
1256+}
1257+
1258+func (c replicationConfig) Empty() bool {
1259+ return c.Config == nil
1260+}
1261+func (c replicationConfig) Replicate(opts replication.ObjectOpts) bool {
1262+ return c.Config.Replicate(opts)
1263+}
1264+
1265+// Resync returns true if replication reset is requested
1266+func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo) bool {
1267+ if c.Empty() {
1268+ return false
1269+ }
1270+ // existing object replication does not apply to un-versioned objects
1271+ if oi.VersionID == "" || oi.VersionID == nullVersionID {
1272+ return false
1273+ }
1274+
1275+ var replicate bool
1276+ if oi.DeleteMarker {
1277+ if c.Replicate(replication.ObjectOpts{
1278+ Name: oi.Name,
1279+ SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
1280+ UserTags: oi.UserTags,
1281+ DeleteMarker: oi.DeleteMarker,
1282+ VersionID: oi.VersionID,
1283+ OpType: replication.DeleteReplicationType,
1284+ ExistingObject: true}) {
1285+ replicate = true
1286+ }
1287+ } else {
1288+ // Ignore previous replication status when deciding if object can be re-replicated
1289+ objInfo := oi.Clone()
1290+ objInfo.ReplicationStatus = replication.StatusType("")
1291+ replicate, _ = mustReplicater(ctx, oi.Bucket, oi.Name, getMustReplicateOptions(objInfo, replication.ExistingObjectReplicationType))
1292+ }
1293+ return c.resync(oi, replicate)
1294+}
1295+
1296+// wrapper function for testability. Returns true if a new reset is requested on
1297+// already replicated objects OR object qualifies for existing object replication
1298+// and no reset requested.
1299+func (c replicationConfig) resync(oi ObjectInfo, replicate bool) bool {
1300+ if !replicate {
1301+ return false
1302+ }
1303+ rs, ok := oi.UserDefined[xhttp.MinIOReplicationResetStatus]
1304+ if !ok { // existing object replication is enabled and object version is unreplicated so far.
1305+ if c.ResetID != "" && oi.ModTime.Before(c.ResetBeforeDate) { // trigger replication if `mc replicate reset` requested
1306+ return true
1307+ }
1308+ return oi.ReplicationStatus != replication.Completed
1309+ }
1310+ if c.ResetID == "" || c.ResetBeforeDate.Equal(timeSentinel) { // no reset in progress
1311+ return false
1312+ }
1313+ // if already replicated, return true if a new reset was requested.
1314+ splits := strings.SplitN(rs, ";", 2)
1315+ newReset := splits[1] != c.ResetID
1316+ if !newReset && oi.ReplicationStatus == replication.Completed {
1317+ // already replicated and no reset requested
1318+ return false
1319+ }
1320+ return newReset && oi.ModTime.Before(c.ResetBeforeDate)
1321+}
cmd/bucket-replication_test.go+209−0
@@ -0,0 +1,209 @@
1+// Copyright (c) 2015-2021 MinIO, Inc.
2+//
3+// This file is part of MinIO Object Storage stack
4+//
5+// This program is free software: you can redistribute it and/or modify
6+// it under the terms of the GNU Affero General Public License as published by
7+// the Free Software Foundation, either version 3 of the License, or
8+// (at your option) any later version.
9+//
10+// This program is distributed in the hope that it will be useful
11+// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+// GNU Affero General Public License for more details.
14+//
15+// You should have received a copy of the GNU Affero General Public License
16+// along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+package cmd
19+
20+import (
21+ "context"
22+ "fmt"
23+ "net/http"
24+ "testing"
25+ "time"
26+
27+ "github.com/minio/minio/internal/bucket/replication"
28+ xhttp "github.com/minio/minio/internal/http"
29+)
30+
31+var configs = []replication.Config{
32+ { // Config0 - Replication config has no filters, existing object replication enabled
33+ Rules: []replication.Rule{
34+ {
35+ Status: replication.Enabled,
36+ Priority: 1,
37+ DeleteMarkerReplication: replication.DeleteMarkerReplication{Status: replication.Enabled},
38+ DeleteReplication: replication.DeleteReplication{Status: replication.Enabled},
39+ Filter: replication.Filter{},
40+ ExistingObjectReplication: replication.ExistingObjectReplication{Status: replication.Enabled},
41+ SourceSelectionCriteria: replication.SourceSelectionCriteria{
42+ ReplicaModifications: replication.ReplicaModifications{Status: replication.Enabled},
43+ },
44+ },
45+ },
46+ },
47+}
48+
49+var replicationConfigTests = []struct {
50+ info ObjectInfo
51+ name string
52+ rcfg replicationConfig
53+ expectedSync bool
54+}{
55+ { //1. no replication config
56+ name: "no replication config",
57+ info: ObjectInfo{Size: 100},
58+ rcfg: replicationConfig{Config: nil},
59+ expectedSync: false,
60+ },
61+ { //2. existing object replication config enabled, no versioning
62+ name: "existing object replication config enabled, no versioning",
63+ info: ObjectInfo{Size: 100},
64+ rcfg: replicationConfig{Config: &configs[0]},
65+ expectedSync: false,
66+ },
67+ { //3. existing object replication config enabled, versioning suspended
68+ name: "existing object replication config enabled, versioning suspended",
69+ info: ObjectInfo{Size: 100, VersionID: nullVersionID},
70+ rcfg: replicationConfig{Config: &configs[0]},
71+ expectedSync: false,
72+ },
73+ { //4. existing object replication enabled, versioning enabled; no reset in progress
74+ name: "existing object replication enabled, versioning enabled; no reset in progress",
75+ info: ObjectInfo{Size: 100,
76+ ReplicationStatus: replication.Completed,
77+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
78+ },
79+ rcfg: replicationConfig{Config: &configs[0]},
80+ expectedSync: false,
81+ },
82+}
83+
84+func TestReplicationResync(t *testing.T) {
85+ ctx := context.Background()
86+ for i, test := range replicationConfigTests {
87+ if sync := test.rcfg.Resync(ctx, test.info); sync != test.expectedSync {
88+ t.Errorf("Test%d (%s): Resync got %t , want %t", i+1, test.name, sync, test.expectedSync)
89+ }
90+ }
91+}
92+
93+var start = UTCNow().AddDate(0, 0, -1)
94+var replicationConfigTests2 = []struct {
95+ info ObjectInfo
96+ name string
97+ replicate bool
98+ rcfg replicationConfig
99+ expectedSync bool
100+}{
101+ { // Cases 1-4: existing object replication enabled, versioning enabled, no reset - replication status varies
102+ // 1: Pending replication
103+ name: "existing object replication on object in Pending replication status",
104+ info: ObjectInfo{Size: 100,
105+ ReplicationStatus: replication.Pending,
106+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
107+ },
108+ replicate: true,
109+ expectedSync: true,
110+ },
111+ { // 2. replication status Failed
112+ name: "existing object replication on object in Failed replication status",
113+ info: ObjectInfo{Size: 100,
114+ ReplicationStatus: replication.Failed,
115+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
116+ },
117+ replicate: true,
118+ expectedSync: true,
119+ },
120+ { //3. replication status unset
121+ name: "existing object replication on pre-existing unreplicated object",
122+ info: ObjectInfo{Size: 100,
123+ ReplicationStatus: replication.StatusType(""),
124+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
125+ },
126+ replicate: true,
127+ expectedSync: true,
128+ },
129+ { //4. replication status Complete
130+ name: "existing object replication on object in Completed replication status",
131+ info: ObjectInfo{Size: 100,
132+ ReplicationStatus: replication.Completed,
133+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
134+ },
135+ replicate: true,
136+ expectedSync: false,
137+ },
138+ { //5. existing object replication enabled, versioning enabled, replication status Pending & reset ID present
139+ name: "existing object replication with reset in progress and object in Pending status",
140+ info: ObjectInfo{Size: 100,
141+ ReplicationStatus: replication.Pending,
142+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
143+ },
144+ replicate: true,
145+ expectedSync: true,
146+ rcfg: replicationConfig{ResetID: "xyz", ResetBeforeDate: UTCNow()},
147+ },
148+ { //6. existing object replication enabled, versioning enabled, replication status Failed & reset ID present
149+ name: "existing object replication with reset in progress and object in Failed status",
150+ info: ObjectInfo{Size: 100,
151+ ReplicationStatus: replication.Failed,
152+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
153+ },
154+ replicate: true,
155+ expectedSync: true,
156+ rcfg: replicationConfig{ResetID: "xyz", ResetBeforeDate: UTCNow()},
157+ },
158+ { //7. existing object replication enabled, versioning enabled, replication status unset & reset ID present
159+ name: "existing object replication with reset in progress and object never replicated before",
160+ info: ObjectInfo{Size: 100,
161+ ReplicationStatus: replication.StatusType(""),
162+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
163+ },
164+ replicate: true,
165+ expectedSync: true,
166+ rcfg: replicationConfig{ResetID: "xyz", ResetBeforeDate: UTCNow()},
167+ },
168+ { //8. existing object replication enabled, versioning enabled, replication status Complete & reset ID present
169+ name: "existing object replication enabled - reset in progress for an object in Completed status",
170+ info: ObjectInfo{Size: 100,
171+ ReplicationStatus: replication.Completed,
172+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df8",
173+ },
174+ replicate: true,
175+ expectedSync: true,
176+ rcfg: replicationConfig{ResetID: "xyz", ResetBeforeDate: UTCNow()},
177+ },
178+ { //9. existing object replication enabled, versioning enabled, replication status Pending & reset ID different
179+ name: "existing object replication enabled, newer reset in progress on object in Pending replication status",
180+ info: ObjectInfo{Size: 100,
181+ ReplicationStatus: replication.Pending,
182+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
183+ UserDefined: map[string]string{xhttp.MinIOReplicationResetStatus: fmt.Sprintf("%s;%s", UTCNow().Format(http.TimeFormat), "xyz")},
184+ ModTime: UTCNow().AddDate(0, 0, -1),
185+ },
186+ replicate: true,
187+ expectedSync: true,
188+ rcfg: replicationConfig{ResetID: "abc", ResetBeforeDate: UTCNow()},
189+ },
190+ { //10. existing object replication enabled, versioning enabled, replication status Complete & reset done
191+ name: "reset done on object in Completed Status - ineligbile for re-replication",
192+ info: ObjectInfo{Size: 100,
193+ ReplicationStatus: replication.Completed,
194+ VersionID: "a3348c34-c352-4498-82f0-1098e8b34df9",
195+ UserDefined: map[string]string{xhttp.MinIOReplicationResetStatus: fmt.Sprintf("%s;%s", start.Format(http.TimeFormat), "xyz")},
196+ },
197+ replicate: true,
198+ expectedSync: false,
199+ rcfg: replicationConfig{ResetID: "xyz", ResetBeforeDate: UTCNow()},
200+ },
201+}
202+
203+func TestReplicationResyncwrapper(t *testing.T) {
204+ for i, test := range replicationConfigTests2 {
205+ if sync := test.rcfg.resync(test.info, test.replicate); sync != test.expectedSync {
206+ t.Errorf("%s (%s): Replicationresync got %t , want %t", fmt.Sprintf("Test%d - %s", i+1, time.Now().Format(http.TimeFormat)), test.name, sync, test.expectedSync)
207+ }
208+ }
209+}
cmd/data-scanner.go+50−22
@@ -375,7 +375,12 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int · 374 unmodified lines
375375 activeLifeCycle = f.oldCache.Info.lifeCycle
376376 filter = nil
377377 }
378−
378+ // If there are replication rules for the prefix, remove the filter.
379+ var replicationCfg replicationConfig
380+ if !f.oldCache.Info.replication.Empty() && f.oldCache.Info.replication.Config.HasActiveRules(prefix, true) {
381+ replicationCfg = f.oldCache.Info.replication
382+ filter = nil
383+ }
379384 // Check if we can skip it due to bloom filter...
380385 if filter != nil && ok && existing.Compacted {
381386 // If folder isn't in filter and we have data, skip it completely.
@@ -449,16 +454,16 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int · 67 unmodified lines
449454
450455 // Get file size, ignore errors.
451456 item := scannerItem{
452− Path: path.Join(f.root, entName),
453− Typ: typ,
454− bucket: bucket,
455− prefix: path.Dir(prefix),
456− objectName: path.Base(entName),
457− debug: f.dataUsageScannerDebug,
458− lifeCycle: activeLifeCycle,
459− heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
457+ Path: path.Join(f.root, entName),
458+ Typ: typ,
459+ bucket: bucket,
460+ prefix: path.Dir(prefix),
461+ objectName: path.Base(entName),
462+ debug: f.dataUsageScannerDebug,
463+ lifeCycle: activeLifeCycle,
464+ replication: replicationCfg,
465+ heal: thisHash.mod(f.oldCache.Info.NextCycle, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure,
460466 }
461−
462467 // if the drive belongs to an erasure set
463468 // that is already being healed, skip the
464469 // healing attempt on this drive.
@@ -808,12 +813,13 @@ type scannerItem struct { · 343 unmodified lines
808813 Path string
809814 Typ os.FileMode
810815
811− bucket string // Bucket.
812− prefix string // Only the prefix if any, does not have final object name.
813− objectName string // Only the object name without prefixes.
814− lifeCycle *lifecycle.Lifecycle
815− heal bool // Has the object been selected for heal check?
816− debug bool
816+ bucket string // Bucket.
817+ prefix string // Only the prefix if any, does not have final object name.
818+ objectName string // Only the object name without prefixes.
819+ lifeCycle *lifecycle.Lifecycle
820+ replication replicationConfig
821+ heal bool // Has the object been selected for heal check?
822+ debug bool
817823 }
818824
819825 type sizeSummary struct {
@@ -1140,33 +1146,50 @@ func (i *scannerItem) objectPath() string { · 320 unmodified lines
11401146
11411147 // healReplication will heal a scanned item that has failed replication.
11421148 func (i *scannerItem) healReplication(ctx context.Context, o ObjectLayer, oi ObjectInfo, sizeS *sizeSummary) {
1149+ existingObjResync := i.replication.Resync(ctx, oi)
11431150 if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
11441151 // heal delete marker replication failure or versioned delete replication failure
11451152 if oi.ReplicationStatus == replication.Pending ||
11461153 oi.ReplicationStatus == replication.Failed ||
11471154 oi.VersionPurgeStatus == Failed || oi.VersionPurgeStatus == Pending {
1148− i.healReplicationDeletes(ctx, o, oi)
1155+ i.healReplicationDeletes(ctx, o, oi, existingObjResync)
11491156 return
11501157 }
1158+ // if replication status is Complete on DeleteMarker and existing object resync required
1159+ if existingObjResync && oi.ReplicationStatus == replication.Completed {
1160+ i.healReplicationDeletes(ctx, o, oi, existingObjResync)
1161+ return
1162+ }
1163+ return
1164+ }
1165+ roi := ReplicateObjectInfo{ObjectInfo: oi, OpType: replication.HealReplicationType}
1166+ if existingObjResync {
1167+ roi.OpType = replication.ExistingObjectReplicationType
1168+ roi.ResetID = i.replication.ResetID
11511169 }
11521170 switch oi.ReplicationStatus {
11531171 case replication.Pending:
11541172 sizeS.pendingCount++
11551173 sizeS.pendingSize += oi.Size
1156− globalReplicationPool.queueReplicaTask(ReplicateObjectInfo{ObjectInfo: oi, OpType: replication.HealReplicationType})
1174+ globalReplicationPool.queueReplicaTask(roi)
1175+ return
11571176 case replication.Failed:
11581177 sizeS.failedSize += oi.Size
11591178 sizeS.failedCount++
1160− globalReplicationPool.queueReplicaTask(ReplicateObjectInfo{ObjectInfo: oi, OpType: replication.HealReplicationType})
1179+ globalReplicationPool.queueReplicaTask(roi)
1180+ return
11611181 case replication.Completed, "COMPLETE":
11621182 sizeS.replicatedSize += oi.Size
11631183 case replication.Replica:
11641184 sizeS.replicaSize += oi.Size
11651185 }
1186+ if existingObjResync {
1187+ globalReplicationPool.queueReplicaTask(roi)
1188+ }
11661189 }
11671190
11681191 // healReplicationDeletes will heal a scanned deleted item that failed to replicate deletes.
1169−func (i *scannerItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, oi ObjectInfo) {
1192+func (i *scannerItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, oi ObjectInfo, existingObject bool) {
11701193 // handle soft delete and permanent delete failures here.
11711194 if oi.DeleteMarker || !oi.VersionPurgeStatus.Empty() {
11721195 versionID := ""
@@ -1176,7 +1199,7 @@ func (i *scannerItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, · 3 unmodified lines
11761199 } else {
11771200 versionID = oi.VersionID
11781201 }
1179− globalReplicationPool.queueReplicaDeleteTask(DeletedObjectVersionInfo{
1202+ doi := DeletedObjectReplicationInfo{
11801203 DeletedObject: DeletedObject{
11811204 ObjectName: oi.Name,
11821205 DeleteMarkerVersionID: dmVersionID,
@@ -1187,7 +1210,12 @@ func (i *scannerItem) healReplicationDeletes(ctx context.Context, o ObjectLayer, · 4 unmodified lines
11871210 VersionPurgeStatus: oi.VersionPurgeStatus,
11881211 },
11891212 Bucket: oi.Bucket,
1190− })
1213+ }
1214+ if existingObject {
1215+ doi.OpType = replication.ExistingObjectReplicationType
1216+ doi.ResetID = i.replication.ResetID
1217+ }
1218+ globalReplicationPool.queueReplicaDeleteTask(doi)
11911219 }
11921220 }
11931221
cmd/data-usage-cache.go+2−1
@@ -161,7 +161,8 @@ type dataUsageCacheInfo struct { · 160 unmodified lines
161161 // optional updates channel.
162162 // If set updates will be sent regularly to this channel.
163163 // Will not be closed when returned.
164− updates chan<- dataUsageEntry `msg:"-"`
164+ updates chan<- dataUsageEntry `msg:"-"`
165+ replication replicationConfig `msg:"-"`
165166 }
166167
167168 func (e *dataUsageEntry) addSizes(summary sizeSummary) {
cmd/object-api-datatypes.go+1−0
@@ -230,6 +230,7 @@ type ReplicateObjectInfo struct { · 229 unmodified lines
230230 ObjectInfo
231231 OpType replication.Type
232232 RetryCount uint32
233+ ResetID string
233234 }
234235
235236 // MultipartInfo captures metadata information about the uploadId
cmd/object-handlers.go+28−15
@@ -1291,7 +1291,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re · 1290 unmodified lines
12911291 if rs := r.Header.Get(xhttp.AmzBucketReplicationStatus); rs != "" {
12921292 srcInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = rs
12931293 }
1294− if ok, _ := mustReplicate(ctx, r, dstBucket, dstObject, srcInfo.UserDefined, srcInfo.ReplicationStatus.String(), srcInfo.metadataOnly); ok {
1294+ if ok, _ := mustReplicate(ctx, r, dstBucket, dstObject, getMustReplicateOptions(srcInfo, replication.UnsetReplicationType)); ok {
12951295 srcInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
12961296 }
12971297 // Store the preserved compression metadata.
@@ -1393,7 +1393,8 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re · 95 unmodified lines
13931393 objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
13941394 response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime)
13951395 encodedSuccessResponse := encodeResponse(response)
1396− if replicate, sync := mustReplicate(ctx, r, dstBucket, dstObject, objInfo.UserDefined, objInfo.ReplicationStatus.String(), objInfo.metadataOnly); replicate {
1396+
1397+ if replicate, sync := mustReplicate(ctx, r, dstBucket, dstObject, getMustReplicateOptions(objInfo, replication.UnsetReplicationType)); replicate {
13971398 scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
13981399 }
13991400
@@ -1637,7 +1638,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req · 237 unmodified lines
16371638 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
16381639 return
16391640 }
1640− if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, "", false); ok {
1641+ if ok, _ := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(ObjectInfo{
1642+ UserDefined: metadata,
1643+ }, replication.ObjectReplicationType)); ok {
16411644 metadata[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
16421645 }
16431646 if r.Header.Get(xhttp.AmzBucketReplicationStatus) == replication.Replica.String() {
@@ -1720,7 +1723,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req · 76 unmodified lines
17201723 }
17211724 }
17221725 }
1723− if replicate, sync := mustReplicate(ctx, r, bucket, object, metadata, "", false); replicate {
1726+ if replicate, sync := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(ObjectInfo{
1727+ UserDefined: metadata,
1728+ }, replication.ObjectReplicationType)); replicate {
17241729 scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
17251730 }
17261731
@@ -1956,7 +1961,9 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h · 229 unmodified lines
19561961 return
19571962 }
19581963
1959− if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, "", false); ok {
1964+ if ok, _ := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(ObjectInfo{
1965+ UserDefined: metadata,
1966+ }, replication.ObjectReplicationType)); ok {
19601967 metadata[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
19611968 }
19621969
@@ -2012,7 +2019,9 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h · 49 unmodified lines
20122019 return
20132020 }
20142021
2015− if replicate, sync := mustReplicate(ctx, r, bucket, object, metadata, "", false); replicate {
2022+ if replicate, sync := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(ObjectInfo{
2023+ UserDefined: metadata,
2024+ }, replication.ObjectReplicationType)); replicate {
20162025 scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
20172026
20182027 }
@@ -2124,7 +2133,9 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r · 105 unmodified lines
21242133 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
21252134 return
21262135 }
2127− if ok, _ := mustReplicate(ctx, r, bucket, object, metadata, "", false); ok {
2136+ if ok, _ := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(ObjectInfo{
2137+ UserDefined: metadata,
2138+ }, replication.ObjectReplicationType)); ok {
21282139 metadata[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
21292140 }
21302141 // We need to preserve the encryption headers set in EncryptRequest,
@@ -3114,7 +3125,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite · 983 unmodified lines
31143125 }
31153126
31163127 setPutObjHeaders(w, objInfo, false)
3117− if replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String(), false); replicate {
3128+ if replicate, sync := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(objInfo, replication.ObjectReplicationType)); replicate {
31183129 scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType)
31193130 }
31203131
@@ -3288,7 +3299,7 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http. · 167 unmodified lines
32883299 } else {
32893300 versionID = objInfo.VersionID
32903301 }
3291− dobj := DeletedObjectVersionInfo{
3302+ dobj := DeletedObjectReplicationInfo{
32923303 DeletedObject: DeletedObject{
32933304 ObjectName: object,
32943305 VersionID: versionID,
@@ -3375,7 +3386,7 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r · 80 unmodified lines
33753386 return
33763387 }
33773388 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = strings.ToUpper(string(legalHold.Status))
3378− replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String(), true)
3389+ replicate, sync := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType))
33793390 if replicate {
33803391 objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
33813392 }
@@ -3554,7 +3565,7 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r · 172 unmodified lines
35543565 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = ""
35553566 objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = ""
35563567 }
3557− replicate, sync := mustReplicate(ctx, r, bucket, object, objInfo.UserDefined, objInfo.ReplicationStatus.String(), true)
3568+ replicate, sync := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType))
35583569 if replicate {
35593570 objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
35603571 }
@@ -3751,14 +3762,16 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h · 190 unmodified lines
37513762 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
37523763 return
37533764 }
3754− replicate, sync := mustReplicate(ctx, r, bucket, object, map[string]string{xhttp.AmzObjectTagging: tags.String()}, objInfo.ReplicationStatus.String(), true)
3765+ tagsStr := tags.String()
3766+
3767+ oi := objInfo.Clone()
3768+ oi.UserTags = tagsStr
3769+ replicate, sync := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(oi, replication.MetadataReplicationType))
37553770 if replicate {
37563771 opts.UserDefined = make(map[string]string)
37573772 opts.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
37583773 }
37593774
3760− tagsStr := tags.String()
3761−
37623775 // Put object tags
37633776 objInfo, err = objAPI.PutObjectTags(ctx, bucket, object, tagsStr, opts)
37643777 if err != nil {
@@ -3828,7 +3841,7 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r · 63 unmodified lines
38283841 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
38293842 return
38303843 }
3831− replicate, sync := mustReplicate(ctx, r, bucket, object, map[string]string{xhttp.AmzObjectTagging: oi.UserTags}, oi.ReplicationStatus.String(), true)
3844+ replicate, sync := mustReplicate(ctx, r, bucket, object, getMustReplicateOptions(oi, replication.MetadataReplicationType))
38323845 if replicate {
38333846 opts.UserDefined = make(map[string]string)
38343847 opts.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Pending.String()
cmd/web-handlers.go+3−3
@@ -814,7 +814,7 @@ next: · 813 unmodified lines
814814 })
815815
816816 if replicateDel {
817− dobj := DeletedObjectVersionInfo{
817+ dobj := DeletedObjectReplicationInfo{
818818 DeletedObject: DeletedObject{
819819 ObjectName: objectName,
820820 DeleteMarkerVersionID: oi.VersionID,
@@ -948,7 +948,7 @@ next: · 127 unmodified lines
948948 Host: sourceIP,
949949 })
950950 if dobj.DeleteMarkerReplicationStatus == string(replication.Pending) || dobj.VersionPurgeStatus == Pending {
951− dv := DeletedObjectVersionInfo{
951+ dv := DeletedObjectReplicationInfo{
952952 DeletedObject: dobj,
953953 Bucket: args.BucketName,
954954 }
@@ -1256,7 +1256,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { · 301 unmodified lines
12561256 }
12571257 }
12581258
1259− mustReplicate, sync := mustReplicateWeb(ctx, r, bucket, object, metadata, "", replPerms)
1259+ mustReplicate, sync := mustReplicateWeb(ctx, r, bucket, object, metadata, replication.StatusType(""), replPerms)
12601260 if mustReplicate {
12611261 metadata[xhttp.AmzBucketReplicationStatus] = string(replication.Pending)
12621262 }
cmd/xl-storage.go+13−1
@@ -414,6 +414,19 @@ func (s *xlStorage) NSScanner(ctx context.Context, cache dataUsageCache, updates · 413 unmodified lines
414414 }
415415 }
416416
417+ // Check if the current bucket has replication configuration
418+ if rcfg, err := globalBucketMetadataSys.GetReplicationConfig(ctx, cache.Info.Name); err == nil {
419+ if rcfg.HasActiveRules("", true) {
420+ tgt := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, cache.Info.Name, rcfg.RoleArn)
421+ cache.Info.replication = replicationConfig{
422+ Config: rcfg,
423+ ResetID: tgt.ResetID,
424+ ResetBeforeDate: tgt.ResetBeforeDate}
425+ if intDataUpdateTracker.debug {
426+ console.Debugln(color.Green("scannerDisk:") + " replication: Active rules found")
427+ }
428+ }
429+ }
417430 // return initialized object layer
418431 objAPI := newObjectLayerFn()
419432 // object layer not initialized, return.
@@ -452,7 +465,6 @@ func (s *xlStorage) NSScanner(ctx context.Context, cache dataUsageCache, updates · 32 unmodified lines
452465 }
453466 return sizeSummary{}, errSkipFile
454467 }
455−
456468 sizeS := sizeSummary{}
457469 for _, version := range fivs.Versions {
458470 oi := version.ToObjectInfo(item.bucket, item.objectPath())
docs/bucket/replication/DESIGN.md+5−1
@@ -33,7 +33,11 @@ An additional header `X-Minio-Replication-Delete-Status` is returned which would · 32 unmodified lines
3333
3434 Note that synchronous replication, i.e. when remote target is configured with --sync mode in `mc admin bucket remote add` does not apply to `DELETE` operations. The version being deleted on the source cluster needs to maintain state and ensure that the operation is mirrored to the target cluster prior to completing on the source object version. Since this needs to account for the target cluster availability and the need to serialize concurrent DELETE operations on different versions of the same object during multi DELETE operations, the current implementation queues the `DELETE` operations in both sync and async modes.
3535
36−Existing object replication, replica modification sync for 2-way replication and multi site replication are currently not supported.
36+### Existing object replication
37+Existing object replication works similar to regular replication. Objects qualifying for existing object replication are detected when scanner runs, and will be replicated if existing object replication is enabled and applicable replication rules are satisfied. Because replication depends on the immutability of versions, only pre-existing objects created while versioning was enabled can be replicated. Even if replication rules are disabled and re-enabled later, the objects created during the interim will be synced as the scanner queues them. For saving iops, objects qualifying for
38+existing object replication are not marked as `PENDING` prior to replication.
39+
40+If the remote site is fully lost and objects previously replicated need to be re-synced, the `mc replicate reset` command with optional flag of `--older-than` needs to be used to trigger re-syncing of previously replicated objects. This command generates a ResetID which is a unique UUID saved to the remote target config along with the applicable date(defaults to time of initiating the reset). All objects created prior to this date are eligible for re-replication if existing object replication is enabled for the replication rule the object satisifes. At the time of completion of replication, `X-Minio-Replication-Reset-Status` is set in the metadata with the timestamp of replication and ResetID. For saving iops, the objects which are re-replicated are not first set to `PENDING` state.
3741
3842 ### Internal metadata for replication
3943
docs/bucket/replication/README.md+15−0
@@ -202,6 +202,21 @@ remote replication target using the `mc admin bucket remote add` command · 201 unmodified lines
202202 mc admin bucket remote add myminio/srcbucket https://accessKey:secretKey@replica-endpoint:9000/destbucket --service replication --region us-east-1 --sync --healthcheck-seconds 100
203203 ```
204204
205+### Existing object replication
206+Existing object replication as detailed [here](https://aws.amazon.com/blogs/storage/replicating-existing-objects-between-s3-buckets/) can be enabled by passing `existing-objects` as a value to `--replicate` flag while adding or editing a replication rule.
207+
208+Once existing object replication is enabled, all objects or object prefixes that satisfy the replication rules and were created prior to adding replication configuration OR while replication rules were disabled will be synced to the target cluster. Depending on the number of previously existing objects, the existing objects that are now eligible to be replicated will eventually be synced to the target cluster as the scanner schedules them. This may be slower depending on the load on the cluster, latency and size of the namespace.
209+
210+Note that existing object replication requires that the objects being replicated were created while versioning was enabled. Objects with null version (i.e. those created when versioning is not enabled or versioning is suspended) will not be replicated.
211+
212+In the rare event that target DR site is entirely lost and previously replicated objects to the DR cluster need to be re-replicated, `mc replicate reset alias/bucket` can be used to initiate a reset. This would initiate a re-sync between the two clusters on a lower priority as the scanner picks up these objects to re-sync.
213+
214+This is an expensive operation and should be initiated only once - progress of the syncing can be monitored by looking at Prometheus metrics. If object version has been re-replicated, `mc stat --vid --debug` on this version shows an additional header `X-Minio-Replication-Reset-Status` with the replication timestamp and ResetID generated at the time of issuing the `mc replicate reset` command.
215+
216+Note that ExistingObjectReplication needs to be enabled in the config via `mc replicate [add|edit]` by passing `existing-objects` as one of the values to `--replicate` flag. Only those objects meeting replication rules and having existing object replication enabled will be re-synced.
217+
218+Multi site replication is currently not supported.
219+
205220 ## Explore Further
206221 - [MinIO Bucket Replication Design](https://github.com/minio/minio/blob/master/docs/bucket/replication/DESIGN.md)
207222 - [MinIO Bucket Versioning Implementation](https://docs.minio.io/docs/minio-bucket-versioning-guide.html)
go.mod+1−1
@@ -47,7 +47,7 @@ require ( · 46 unmodified lines
4747 github.com/minio/madmin-go v1.0.9
4848 github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78
4949 github.com/minio/parquet-go v1.0.0
50− github.com/minio/pkg v1.0.3
50+ github.com/minio/pkg v1.0.4
5151 github.com/minio/rpc v1.0.0
5252 github.com/minio/selfupdate v0.3.1
5353 github.com/minio/sha256-simd v1.0.0
go.sum+2−0
@@ -494,6 +494,8 @@ github.com/minio/parquet-go v1.0.0 h1:fcWsEvub04Nsl/4hiRBDWlbqd6jhacQieV07a+nhiI · 493 unmodified lines
494494 github.com/minio/parquet-go v1.0.0/go.mod h1:aQlkSOfOq2AtQKkuou3mosNVMwNokd+faTacxxk/oHA=
495495 github.com/minio/pkg v1.0.3 h1:tUhM6lG/BdNB0+5f2RbE4ifCAYwMs6cRJnZ/AY0WIeQ=
496496 github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
497+github.com/minio/pkg v1.0.4 h1:+BmaCENP6BaMm9PsGK6L1L5MKulWDxl4qobvJYf6m/E=
498+github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
497499 github.com/minio/rpc v1.0.0 h1:tJCHyLfQF6k6HlMQFpKy2FO/7lc2WP8gLDGMZp18E70=
498500 github.com/minio/rpc v1.0.0/go.mod h1:b9xqF7J0xeMXr0cM4pnBlP7Te7PDsG5JrRxl5dG6Ldk=
499501 github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
internal/bucket/replication/replication.go+20−9
@@ -131,23 +131,31 @@ type Type int · 130 unmodified lines
131131
132132 // Types of replication
133133 const (
134− ObjectReplicationType Type = 1 + iota
134+ UnsetReplicationType Type = 0 + iota
135+ ObjectReplicationType
135136 DeleteReplicationType
136137 MetadataReplicationType
137138 HealReplicationType
139+ ExistingObjectReplicationType
138140 )
139141
142+// Valid returns true if replication type is set
143+func (t Type) Valid() bool {
144+ return t > 0
145+}
146+
140147 // ObjectOpts provides information to deduce whether replication
141148 // can be triggered on the resultant object.
142149 type ObjectOpts struct {
143− Name string
144− UserTags string
145− VersionID string
146− IsLatest bool
147− DeleteMarker bool
148− SSEC bool
149− OpType Type
150− Replica bool
150+ Name string
151+ UserTags string
152+ VersionID string
153+ IsLatest bool
154+ DeleteMarker bool
155+ SSEC bool
156+ OpType Type
157+ Replica bool
158+ ExistingObject bool
151159 }
152160
153161 // FilterActionableRules returns the rules actions that need to be executed
@@ -191,6 +199,9 @@ func (c Config) Replicate(obj ObjectOpts) bool { · 37 unmodified lines
191199 if rule.Status == Disabled {
192200 continue
193201 }
202+ if obj.ExistingObject && rule.ExistingObjectReplication.Status == Disabled {
203+ return false
204+ }
194205 if obj.OpType == DeleteReplicationType {
195206 switch {
196207 case obj.VersionID != "":
internal/bucket/replication/rule.go+53−14
@@ -90,6 +90,43 @@ func (d *DeleteReplication) UnmarshalXML(dec *xml.Decoder, start xml.StartElemen · 89 unmodified lines
9090 return nil
9191 }
9292
93+// ExistingObjectReplication - whether existing object replication is enabled
94+type ExistingObjectReplication struct {
95+ Status Status `xml:"Status"` // should be set to "Disabled" by default
96+}
97+
98+// IsEmpty returns true if ExistingObjectReplication is not set
99+func (e ExistingObjectReplication) IsEmpty() bool {
100+ return len(e.Status) == 0
101+}
102+
103+// Validate validates whether the status is disabled.
104+func (e ExistingObjectReplication) Validate() error {
105+ if e.IsEmpty() {
106+ return nil
107+ }
108+ if e.Status != Disabled && e.Status != Enabled {
109+ return errInvalidExistingObjectReplicationStatus
110+ }
111+ return nil
112+}
113+
114+// UnmarshalXML - decodes XML data. Default to Disabled unless specified
115+func (e *ExistingObjectReplication) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) (err error) {
116+ // Make subtype to avoid recursive UnmarshalXML().
117+ type existingObjectReplication ExistingObjectReplication
118+ erep := existingObjectReplication{}
119+
120+ if err := dec.DecodeElement(&erep, &start); err != nil {
121+ return err
122+ }
123+ if len(erep.Status) == 0 {
124+ erep.Status = Disabled
125+ }
126+ e.Status = erep.Status
127+ return nil
128+}
129+
93130 // Rule - a rule for replication configuration.
94131 type Rule struct {
95132 XMLName xml.Name `xml:"Rule" json:"Rule"`
@@ -98,22 +135,24 @@ type Rule struct { · 2 unmodified lines
98135 Priority int `xml:"Priority" json:"Priority"`
99136 DeleteMarkerReplication DeleteMarkerReplication `xml:"DeleteMarkerReplication" json:"DeleteMarkerReplication"`
100137 // MinIO extension to replicate versioned deletes
101− DeleteReplication DeleteReplication `xml:"DeleteReplication" json:"DeleteReplication"`
102− Destination Destination `xml:"Destination" json:"Destination"`
103− SourceSelectionCriteria SourceSelectionCriteria `xml:"SourceSelectionCriteria" json:"SourceSelectionCriteria"`
104− Filter Filter `xml:"Filter" json:"Filter"`
138+ DeleteReplication DeleteReplication `xml:"DeleteReplication" json:"DeleteReplication"`
139+ Destination Destination `xml:"Destination" json:"Destination"`
140+ SourceSelectionCriteria SourceSelectionCriteria `xml:"SourceSelectionCriteria" json:"SourceSelectionCriteria"`
141+ Filter Filter `xml:"Filter" json:"Filter"`
142+ ExistingObjectReplication ExistingObjectReplication `xml:"ExistingObjectReplication,omitempty" json:"ExistingObjectReplication,omitempty"`
105143 }
106144
107145 var (
108− errInvalidRuleID = Errorf("ID must be less than 255 characters")
109− errEmptyRuleStatus = Errorf("Status should not be empty")
110− errInvalidRuleStatus = Errorf("Status must be set to either Enabled or Disabled")
111− errDeleteMarkerReplicationMissing = Errorf("DeleteMarkerReplication must be specified")
112− errPriorityMissing = Errorf("Priority must be specified")
113− errInvalidDeleteMarkerReplicationStatus = Errorf("Delete marker replication status is invalid")
114− errDestinationSourceIdentical = Errorf("Destination bucket cannot be the same as the source bucket.")
115− errDeleteReplicationMissing = Errorf("Delete replication must be specified")
116− errInvalidDeleteReplicationStatus = Errorf("Delete replication is either enable|disable")
146+ errInvalidRuleID = Errorf("ID must be less than 255 characters")
147+ errEmptyRuleStatus = Errorf("Status should not be empty")
148+ errInvalidRuleStatus = Errorf("Status must be set to either Enabled or Disabled")
149+ errDeleteMarkerReplicationMissing = Errorf("DeleteMarkerReplication must be specified")
150+ errPriorityMissing = Errorf("Priority must be specified")
151+ errInvalidDeleteMarkerReplicationStatus = Errorf("Delete marker replication status is invalid")
152+ errDestinationSourceIdentical = Errorf("Destination bucket cannot be the same as the source bucket.")
153+ errDeleteReplicationMissing = Errorf("Delete replication must be specified")
154+ errInvalidDeleteReplicationStatus = Errorf("Delete replication is either enable|disable")
155+ errInvalidExistingObjectReplicationStatus = Errorf("Existing object replication status is invalid")
117156 )
118157
119158 // validateID - checks if ID is valid or not.
@@ -200,7 +239,7 @@ func (r Rule) Validate(bucket string, sameTarget bool) error { · 80 unmodified lines
200239 if r.Destination.Bucket == bucket && sameTarget {
201240 return errDestinationSourceIdentical
202241 }
203− return nil
242+ return r.ExistingObjectReplication.Validate()
204243 }
205244
206245 // MetadataReplicate returns true if object is not a replica or in the case of replicas,
internal/http/headers.go+3−0
@@ -170,6 +170,9 @@ const ( · 169 unmodified lines
170170 MinIOSourceProxyRequest = "X-Minio-Source-Proxy-Request"
171171 // Header indicates that this request is a replication request to create a REPLICA
172172 MinIOSourceReplicationRequest = "X-Minio-Source-Replication-Request"
173+ // Header indicates replication reset status.
174+ MinIOReplicationResetStatus = "X-Minio-Replication-Reset-Status"
175+
173176 // predicted date/time of transition
174177 MinIOTransition = "X-Minio-Transition"
175178 )

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t