diff --git a/Makefile b/Makefile index df8e24b69..efc5dbbec 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,7 @@ test-iam: build ## verify IAM (external IDP, etcd backends) test-replication: install ## verify multi site replication @echo "Running tests for replicating three sites" @(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh) + @(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh) test-site-replication-ldap: install ## verify automatic site replication @echo "Running tests for automatic site replication of IAM (with LDAP)" diff --git a/cmd/api-errors.go b/cmd/api-errors.go index e01333207..0104256fd 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -131,7 +131,7 @@ const ( ErrReplicationNeedsVersioningError ErrReplicationBucketNeedsVersioningError ErrReplicationDenyEditError - ErrReplicationNoMatchingRuleError + ErrReplicationNoExistingObjects ErrObjectRestoreAlreadyInProgress ErrNoSuchKey ErrNoSuchUpload @@ -893,9 +893,9 @@ var errorCodes = errorCodeMap{ Description: "Bandwidth limit for remote target must be atleast 100MBps", HTTPStatusCode: http.StatusBadRequest, }, - ErrReplicationNoMatchingRuleError: { - Code: "XMinioReplicationNoMatchingRule", - Description: "No matching replication rule found for this object prefix", + ErrReplicationNoExistingObjects: { + Code: "XMinioReplicationNoExistingObjects", + Description: "No matching ExistingsObjects rule enabled", HTTPStatusCode: http.StatusBadRequest, }, ErrReplicationDenyEditError: { diff --git a/cmd/apierrorcode_string.go b/cmd/apierrorcode_string.go index 47b1c4189..f5cfefd2c 100644 --- a/cmd/apierrorcode_string.go +++ b/cmd/apierrorcode_string.go @@ -66,7 +66,7 @@ func _() { _ = x[ErrReplicationNeedsVersioningError-55] _ = x[ErrReplicationBucketNeedsVersioningError-56] _ = x[ErrReplicationDenyEditError-57] - _ = x[ErrReplicationNoMatchingRuleError-58] + _ = x[ErrReplicationNoExistingObjects-58] _ = x[ErrObjectRestoreAlreadyInProgress-59] _ = x[ErrNoSuchKey-60] _ = x[ErrNoSuchUpload-61] @@ -299,9 +299,9 @@ func _() { _ = x[ErrPostPolicyConditionInvalidFormat-288] } -const _APIErrorCode_name = "NoneAccessDeniedBadDigestEntityTooSmallEntityTooLargePolicyTooLargeIncompleteBodyInternalErrorInvalidAccessKeyIDAccessKeyDisabledInvalidBucketNameInvalidDigestInvalidRangeInvalidRangePartNumberInvalidCopyPartRangeInvalidCopyPartRangeSourceInvalidMaxKeysInvalidEncodingMethodInvalidMaxUploadsInvalidMaxPartsInvalidPartNumberMarkerInvalidPartNumberInvalidRequestBodyInvalidCopySourceInvalidMetadataDirectiveInvalidCopyDestInvalidPolicyDocumentInvalidObjectStateMalformedXMLMissingContentLengthMissingContentMD5MissingRequestBodyErrorMissingSecurityHeaderNoSuchBucketNoSuchBucketPolicyNoSuchBucketLifecycleNoSuchLifecycleConfigurationInvalidLifecycleWithObjectLockNoSuchBucketSSEConfigNoSuchCORSConfigurationNoSuchWebsiteConfigurationReplicationConfigurationNotFoundErrorRemoteDestinationNotFoundErrorReplicationDestinationMissingLockRemoteTargetNotFoundErrorReplicationRemoteConnectionErrorReplicationBandwidthLimitErrorBucketRemoteIdenticalToSourceBucketRemoteAlreadyExistsBucketRemoteLabelInUseBucketRemoteArnTypeInvalidBucketRemoteArnInvalidBucketRemoteRemoveDisallowedRemoteTargetNotVersionedErrorReplicationSourceNotVersionedErrorReplicationNeedsVersioningErrorReplicationBucketNeedsVersioningErrorReplicationDenyEditErrorReplicationNoMatchingRuleErrorObjectRestoreAlreadyInProgressNoSuchKeyNoSuchUploadInvalidVersionIDNoSuchVersionNotImplementedPreconditionFailedRequestTimeTooSkewedSignatureDoesNotMatchMethodNotAllowedInvalidPartInvalidPartOrderAuthorizationHeaderMalformedMalformedPOSTRequestPOSTFileRequiredSignatureVersionNotSupportedBucketNotEmptyAllAccessDisabledMalformedPolicyMissingFieldsMissingCredTagCredMalformedInvalidRegionInvalidServiceS3InvalidServiceSTSInvalidRequestVersionMissingSignTagMissingSignHeadersTagMalformedDateMalformedPresignedDateMalformedCredentialDateMalformedCredentialRegionMalformedExpiresNegativeExpiresAuthHeaderEmptyExpiredPresignRequestRequestNotReadyYetUnsignedHeadersMissingDateHeaderInvalidQuerySignatureAlgoInvalidQueryParamsBucketAlreadyOwnedByYouInvalidDurationBucketAlreadyExistsMetadataTooLargeUnsupportedMetadataMaximumExpiresSlowDownInvalidPrefixMarkerBadRequestKeyTooLongErrorInvalidBucketObjectLockConfigurationObjectLockConfigurationNotFoundObjectLockConfigurationNotAllowedNoSuchObjectLockConfigurationObjectLockedInvalidRetentionDatePastObjectLockRetainDateUnknownWORMModeDirectiveBucketTaggingNotFoundObjectLockInvalidHeadersInvalidTagDirectiveInvalidEncryptionMethodInsecureSSECustomerRequestSSEMultipartEncryptedSSEEncryptedObjectInvalidEncryptionParametersInvalidSSECustomerAlgorithmInvalidSSECustomerKeyMissingSSECustomerKeyMissingSSECustomerKeyMD5SSECustomerKeyMD5MismatchInvalidSSECustomerParametersIncompatibleEncryptionMethodKMSNotConfiguredKMSKeyNotFoundExceptionNoAccessKeyInvalidTokenEventNotificationARNNotificationRegionNotificationOverlappingFilterNotificationFilterNameInvalidFilterNamePrefixFilterNameSuffixFilterValueInvalidOverlappingConfigsUnsupportedNotificationContentSHA256MismatchReadQuorumWriteQuorumStorageFullRequestBodyParseObjectExistsAsDirectoryInvalidObjectNameInvalidObjectNamePrefixSlashInvalidResourceNameServerNotInitializedOperationTimedOutClientDisconnectedOperationMaxedOutInvalidRequestTransitionStorageClassNotFoundErrorInvalidStorageClassBackendDownMalformedJSONAdminNoSuchUserAdminNoSuchGroupAdminGroupNotEmptyAdminNoSuchPolicyAdminInvalidArgumentAdminInvalidAccessKeyAdminInvalidSecretKeyAdminConfigNoQuorumAdminConfigTooLargeAdminConfigBadJSONAdminConfigDuplicateKeysAdminCredentialsMismatchInsecureClientRequestObjectTamperedSiteReplicationInvalidRequestSiteReplicationPeerRespSiteReplicationBackendIssueSiteReplicationServiceAccountErrorSiteReplicationBucketConfigErrorSiteReplicationBucketMetaErrorSiteReplicationIAMErrorSiteReplicationConfigMissingAdminBucketQuotaExceededAdminNoSuchQuotaConfigurationHealNotImplementedHealNoSuchProcessHealInvalidClientTokenHealMissingBucketHealAlreadyRunningHealOverlappingPathsIncorrectContinuationTokenEmptyRequestBodyUnsupportedFunctionInvalidExpressionTypeBusyUnauthorizedAccessExpressionTooLongIllegalSQLFunctionArgumentInvalidKeyPathInvalidCompressionFormatInvalidFileHeaderInfoInvalidJSONTypeInvalidQuoteFieldsInvalidRequestParameterInvalidDataTypeInvalidTextEncodingInvalidDataSourceInvalidTableAliasMissingRequiredParameterObjectSerializationConflictUnsupportedSQLOperationUnsupportedSQLStructureUnsupportedSyntaxUnsupportedRangeHeaderLexerInvalidCharLexerInvalidOperatorLexerInvalidLiteralLexerInvalidIONLiteralParseExpectedDatePartParseExpectedKeywordParseExpectedTokenTypeParseExpected2TokenTypesParseExpectedNumberParseExpectedRightParenBuiltinFunctionCallParseExpectedTypeNameParseExpectedWhenClauseParseUnsupportedTokenParseUnsupportedLiteralsGroupByParseExpectedMemberParseUnsupportedSelectParseUnsupportedCaseParseUnsupportedCaseClauseParseUnsupportedAliasParseUnsupportedSyntaxParseUnknownOperatorParseMissingIdentAfterAtParseUnexpectedOperatorParseUnexpectedTermParseUnexpectedTokenParseUnexpectedKeywordParseExpectedExpressionParseExpectedLeftParenAfterCastParseExpectedLeftParenValueConstructorParseExpectedLeftParenBuiltinFunctionCallParseExpectedArgumentDelimiterParseCastArityParseInvalidTypeParamParseEmptySelectParseSelectMissingFromParseExpectedIdentForGroupNameParseExpectedIdentForAliasParseUnsupportedCallWithStarParseNonUnaryAgregateFunctionCallParseMalformedJoinParseExpectedIdentForAtParseAsteriskIsNotAloneInSelectListParseCannotMixSqbAndWildcardInSelectListParseInvalidContextForWildcardInSelectListIncorrectSQLFunctionArgumentTypeValueParseFailureEvaluatorInvalidArgumentsIntegerOverflowLikeInvalidInputsCastFailedInvalidCastEvaluatorInvalidTimestampFormatPatternEvaluatorInvalidTimestampFormatPatternSymbolForParsingEvaluatorTimestampFormatPatternDuplicateFieldsEvaluatorTimestampFormatPatternHourClockAmPmMismatchEvaluatorUnterminatedTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternSymbolEvaluatorBindingDoesNotExistMissingHeadersInvalidColumnIndexAdminConfigNotificationTargetsFailedAdminProfilerNotEnabledInvalidDecompressedSizeAddUserInvalidArgumentAdminResourceInvalidArgumentAdminAccountNotEligibleAccountNotEligibleAdminServiceAccountNotFoundPostPolicyConditionInvalidFormat" +const _APIErrorCode_name = "NoneAccessDeniedBadDigestEntityTooSmallEntityTooLargePolicyTooLargeIncompleteBodyInternalErrorInvalidAccessKeyIDAccessKeyDisabledInvalidBucketNameInvalidDigestInvalidRangeInvalidRangePartNumberInvalidCopyPartRangeInvalidCopyPartRangeSourceInvalidMaxKeysInvalidEncodingMethodInvalidMaxUploadsInvalidMaxPartsInvalidPartNumberMarkerInvalidPartNumberInvalidRequestBodyInvalidCopySourceInvalidMetadataDirectiveInvalidCopyDestInvalidPolicyDocumentInvalidObjectStateMalformedXMLMissingContentLengthMissingContentMD5MissingRequestBodyErrorMissingSecurityHeaderNoSuchBucketNoSuchBucketPolicyNoSuchBucketLifecycleNoSuchLifecycleConfigurationInvalidLifecycleWithObjectLockNoSuchBucketSSEConfigNoSuchCORSConfigurationNoSuchWebsiteConfigurationReplicationConfigurationNotFoundErrorRemoteDestinationNotFoundErrorReplicationDestinationMissingLockRemoteTargetNotFoundErrorReplicationRemoteConnectionErrorReplicationBandwidthLimitErrorBucketRemoteIdenticalToSourceBucketRemoteAlreadyExistsBucketRemoteLabelInUseBucketRemoteArnTypeInvalidBucketRemoteArnInvalidBucketRemoteRemoveDisallowedRemoteTargetNotVersionedErrorReplicationSourceNotVersionedErrorReplicationNeedsVersioningErrorReplicationBucketNeedsVersioningErrorReplicationDenyEditErrorReplicationNoExistingObjectsObjectRestoreAlreadyInProgressNoSuchKeyNoSuchUploadInvalidVersionIDNoSuchVersionNotImplementedPreconditionFailedRequestTimeTooSkewedSignatureDoesNotMatchMethodNotAllowedInvalidPartInvalidPartOrderAuthorizationHeaderMalformedMalformedPOSTRequestPOSTFileRequiredSignatureVersionNotSupportedBucketNotEmptyAllAccessDisabledMalformedPolicyMissingFieldsMissingCredTagCredMalformedInvalidRegionInvalidServiceS3InvalidServiceSTSInvalidRequestVersionMissingSignTagMissingSignHeadersTagMalformedDateMalformedPresignedDateMalformedCredentialDateMalformedCredentialRegionMalformedExpiresNegativeExpiresAuthHeaderEmptyExpiredPresignRequestRequestNotReadyYetUnsignedHeadersMissingDateHeaderInvalidQuerySignatureAlgoInvalidQueryParamsBucketAlreadyOwnedByYouInvalidDurationBucketAlreadyExistsMetadataTooLargeUnsupportedMetadataMaximumExpiresSlowDownInvalidPrefixMarkerBadRequestKeyTooLongErrorInvalidBucketObjectLockConfigurationObjectLockConfigurationNotFoundObjectLockConfigurationNotAllowedNoSuchObjectLockConfigurationObjectLockedInvalidRetentionDatePastObjectLockRetainDateUnknownWORMModeDirectiveBucketTaggingNotFoundObjectLockInvalidHeadersInvalidTagDirectiveInvalidEncryptionMethodInsecureSSECustomerRequestSSEMultipartEncryptedSSEEncryptedObjectInvalidEncryptionParametersInvalidSSECustomerAlgorithmInvalidSSECustomerKeyMissingSSECustomerKeyMissingSSECustomerKeyMD5SSECustomerKeyMD5MismatchInvalidSSECustomerParametersIncompatibleEncryptionMethodKMSNotConfiguredKMSKeyNotFoundExceptionNoAccessKeyInvalidTokenEventNotificationARNNotificationRegionNotificationOverlappingFilterNotificationFilterNameInvalidFilterNamePrefixFilterNameSuffixFilterValueInvalidOverlappingConfigsUnsupportedNotificationContentSHA256MismatchReadQuorumWriteQuorumStorageFullRequestBodyParseObjectExistsAsDirectoryInvalidObjectNameInvalidObjectNamePrefixSlashInvalidResourceNameServerNotInitializedOperationTimedOutClientDisconnectedOperationMaxedOutInvalidRequestTransitionStorageClassNotFoundErrorInvalidStorageClassBackendDownMalformedJSONAdminNoSuchUserAdminNoSuchGroupAdminGroupNotEmptyAdminNoSuchPolicyAdminInvalidArgumentAdminInvalidAccessKeyAdminInvalidSecretKeyAdminConfigNoQuorumAdminConfigTooLargeAdminConfigBadJSONAdminConfigDuplicateKeysAdminCredentialsMismatchInsecureClientRequestObjectTamperedSiteReplicationInvalidRequestSiteReplicationPeerRespSiteReplicationBackendIssueSiteReplicationServiceAccountErrorSiteReplicationBucketConfigErrorSiteReplicationBucketMetaErrorSiteReplicationIAMErrorSiteReplicationConfigMissingAdminBucketQuotaExceededAdminNoSuchQuotaConfigurationHealNotImplementedHealNoSuchProcessHealInvalidClientTokenHealMissingBucketHealAlreadyRunningHealOverlappingPathsIncorrectContinuationTokenEmptyRequestBodyUnsupportedFunctionInvalidExpressionTypeBusyUnauthorizedAccessExpressionTooLongIllegalSQLFunctionArgumentInvalidKeyPathInvalidCompressionFormatInvalidFileHeaderInfoInvalidJSONTypeInvalidQuoteFieldsInvalidRequestParameterInvalidDataTypeInvalidTextEncodingInvalidDataSourceInvalidTableAliasMissingRequiredParameterObjectSerializationConflictUnsupportedSQLOperationUnsupportedSQLStructureUnsupportedSyntaxUnsupportedRangeHeaderLexerInvalidCharLexerInvalidOperatorLexerInvalidLiteralLexerInvalidIONLiteralParseExpectedDatePartParseExpectedKeywordParseExpectedTokenTypeParseExpected2TokenTypesParseExpectedNumberParseExpectedRightParenBuiltinFunctionCallParseExpectedTypeNameParseExpectedWhenClauseParseUnsupportedTokenParseUnsupportedLiteralsGroupByParseExpectedMemberParseUnsupportedSelectParseUnsupportedCaseParseUnsupportedCaseClauseParseUnsupportedAliasParseUnsupportedSyntaxParseUnknownOperatorParseMissingIdentAfterAtParseUnexpectedOperatorParseUnexpectedTermParseUnexpectedTokenParseUnexpectedKeywordParseExpectedExpressionParseExpectedLeftParenAfterCastParseExpectedLeftParenValueConstructorParseExpectedLeftParenBuiltinFunctionCallParseExpectedArgumentDelimiterParseCastArityParseInvalidTypeParamParseEmptySelectParseSelectMissingFromParseExpectedIdentForGroupNameParseExpectedIdentForAliasParseUnsupportedCallWithStarParseNonUnaryAgregateFunctionCallParseMalformedJoinParseExpectedIdentForAtParseAsteriskIsNotAloneInSelectListParseCannotMixSqbAndWildcardInSelectListParseInvalidContextForWildcardInSelectListIncorrectSQLFunctionArgumentTypeValueParseFailureEvaluatorInvalidArgumentsIntegerOverflowLikeInvalidInputsCastFailedInvalidCastEvaluatorInvalidTimestampFormatPatternEvaluatorInvalidTimestampFormatPatternSymbolForParsingEvaluatorTimestampFormatPatternDuplicateFieldsEvaluatorTimestampFormatPatternHourClockAmPmMismatchEvaluatorUnterminatedTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternTokenEvaluatorInvalidTimestampFormatPatternSymbolEvaluatorBindingDoesNotExistMissingHeadersInvalidColumnIndexAdminConfigNotificationTargetsFailedAdminProfilerNotEnabledInvalidDecompressedSizeAddUserInvalidArgumentAdminResourceInvalidArgumentAdminAccountNotEligibleAccountNotEligibleAdminServiceAccountNotFoundPostPolicyConditionInvalidFormat" -var _APIErrorCode_index = [...]uint16{0, 4, 16, 25, 39, 53, 67, 81, 94, 112, 129, 146, 159, 171, 193, 213, 239, 253, 274, 291, 306, 329, 346, 364, 381, 405, 420, 441, 459, 471, 491, 508, 531, 552, 564, 582, 603, 631, 661, 682, 705, 731, 768, 798, 831, 856, 888, 918, 947, 972, 994, 1020, 1042, 1070, 1099, 1133, 1164, 1201, 1225, 1255, 1285, 1294, 1306, 1322, 1335, 1349, 1367, 1387, 1408, 1424, 1435, 1451, 1479, 1499, 1515, 1543, 1557, 1574, 1589, 1602, 1616, 1629, 1642, 1658, 1675, 1696, 1710, 1731, 1744, 1766, 1789, 1814, 1830, 1845, 1860, 1881, 1899, 1914, 1931, 1956, 1974, 1997, 2012, 2031, 2047, 2066, 2080, 2088, 2107, 2117, 2132, 2168, 2199, 2232, 2261, 2273, 2293, 2317, 2341, 2362, 2386, 2405, 2428, 2454, 2475, 2493, 2520, 2547, 2568, 2589, 2613, 2638, 2666, 2694, 2710, 2733, 2744, 2756, 2773, 2788, 2806, 2835, 2852, 2868, 2884, 2902, 2920, 2943, 2964, 2974, 2985, 2996, 3012, 3035, 3052, 3080, 3099, 3119, 3136, 3154, 3171, 3185, 3220, 3239, 3250, 3263, 3278, 3294, 3312, 3329, 3349, 3370, 3391, 3410, 3429, 3447, 3471, 3495, 3516, 3530, 3559, 3582, 3609, 3643, 3675, 3705, 3728, 3756, 3780, 3809, 3827, 3844, 3866, 3883, 3901, 3921, 3947, 3963, 3982, 4003, 4007, 4025, 4042, 4068, 4082, 4106, 4127, 4142, 4160, 4183, 4198, 4217, 4234, 4251, 4275, 4302, 4325, 4348, 4365, 4387, 4403, 4423, 4442, 4464, 4485, 4505, 4527, 4551, 4570, 4612, 4633, 4656, 4677, 4708, 4727, 4749, 4769, 4795, 4816, 4838, 4858, 4882, 4905, 4924, 4944, 4966, 4989, 5020, 5058, 5099, 5129, 5143, 5164, 5180, 5202, 5232, 5258, 5286, 5319, 5337, 5360, 5395, 5435, 5477, 5509, 5526, 5551, 5566, 5583, 5593, 5604, 5642, 5696, 5742, 5794, 5842, 5885, 5929, 5957, 5971, 5989, 6025, 6048, 6071, 6093, 6121, 6144, 6162, 6189, 6221} +var _APIErrorCode_index = [...]uint16{0, 4, 16, 25, 39, 53, 67, 81, 94, 112, 129, 146, 159, 171, 193, 213, 239, 253, 274, 291, 306, 329, 346, 364, 381, 405, 420, 441, 459, 471, 491, 508, 531, 552, 564, 582, 603, 631, 661, 682, 705, 731, 768, 798, 831, 856, 888, 918, 947, 972, 994, 1020, 1042, 1070, 1099, 1133, 1164, 1201, 1225, 1253, 1283, 1292, 1304, 1320, 1333, 1347, 1365, 1385, 1406, 1422, 1433, 1449, 1477, 1497, 1513, 1541, 1555, 1572, 1587, 1600, 1614, 1627, 1640, 1656, 1673, 1694, 1708, 1729, 1742, 1764, 1787, 1812, 1828, 1843, 1858, 1879, 1897, 1912, 1929, 1954, 1972, 1995, 2010, 2029, 2045, 2064, 2078, 2086, 2105, 2115, 2130, 2166, 2197, 2230, 2259, 2271, 2291, 2315, 2339, 2360, 2384, 2403, 2426, 2452, 2473, 2491, 2518, 2545, 2566, 2587, 2611, 2636, 2664, 2692, 2708, 2731, 2742, 2754, 2771, 2786, 2804, 2833, 2850, 2866, 2882, 2900, 2918, 2941, 2962, 2972, 2983, 2994, 3010, 3033, 3050, 3078, 3097, 3117, 3134, 3152, 3169, 3183, 3218, 3237, 3248, 3261, 3276, 3292, 3310, 3327, 3347, 3368, 3389, 3408, 3427, 3445, 3469, 3493, 3514, 3528, 3557, 3580, 3607, 3641, 3673, 3703, 3726, 3754, 3778, 3807, 3825, 3842, 3864, 3881, 3899, 3919, 3945, 3961, 3980, 4001, 4005, 4023, 4040, 4066, 4080, 4104, 4125, 4140, 4158, 4181, 4196, 4215, 4232, 4249, 4273, 4300, 4323, 4346, 4363, 4385, 4401, 4421, 4440, 4462, 4483, 4503, 4525, 4549, 4568, 4610, 4631, 4654, 4675, 4706, 4725, 4747, 4767, 4793, 4814, 4836, 4856, 4880, 4903, 4922, 4942, 4964, 4987, 5018, 5056, 5097, 5127, 5141, 5162, 5178, 5200, 5230, 5256, 5284, 5317, 5335, 5358, 5393, 5433, 5475, 5507, 5524, 5549, 5564, 5581, 5591, 5602, 5640, 5694, 5740, 5792, 5840, 5883, 5927, 5955, 5969, 5987, 6023, 6046, 6069, 6091, 6119, 6142, 6160, 6187, 6219} func (i APIErrorCode) String() string { if i < 0 || i >= APIErrorCode(len(_APIErrorCode_index)-1) { diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 3475dacad..1ad4ffeb9 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -21,7 +21,6 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" "encoding/xml" "fmt" "io" @@ -33,7 +32,6 @@ import ( "strconv" "strings" "sync" - "time" "github.com/google/uuid" "github.com/gorilla/mux" @@ -1596,372 +1594,3 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r // Write success response. writeSuccessResponseHeadersOnly(w) } - -// PutBucketReplicationConfigHandler - PUT Bucket replication configuration. -// ---------- -// Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html -func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "PutBucketReplicationConfig") - defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) - - vars := mux.Vars(r) - bucket := vars["bucket"] - objectAPI := api.ObjectAPI() - if objectAPI == nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) - return - } - if globalIsGateway { - writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) - return - } - if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) - return - } - // Check if bucket exists. - if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL) - return - } - replicationConfig, err := replication.ParseConfig(io.LimitReader(r.Body, r.ContentLength)) - if err != nil { - apiErr := errorCodes.ToAPIErr(ErrMalformedXML) - apiErr.Description = err.Error() - writeErrorResponse(ctx, w, apiErr, r.URL) - return - } - sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, true) - if apiErr != noError { - writeErrorResponse(ctx, w, apiErr, r.URL) - return - } - // Validate the received bucket replication config - if err = replicationConfig.Validate(bucket, sameTarget); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - configData, err := xml.Marshal(replicationConfig) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - if err = globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, configData); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - // Write success response. - writeSuccessResponseHeadersOnly(w) -} - -// GetBucketReplicationConfigHandler - GET Bucket replication configuration. -// ---------- -// Gets the replication configuration for a bucket. -func (api objectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "GetBucketReplicationConfig") - - defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) - - vars := mux.Vars(r) - bucket := vars["bucket"] - - objectAPI := api.ObjectAPI() - if objectAPI == nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) - return - } - - // check if user has permissions to perform this operation - if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) - return - } - // Check if bucket exists. - if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - configData, err := xml.Marshal(config) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - // Write success response. - writeSuccessResponseXML(w, configData) -} - -// DeleteBucketReplicationConfigHandler - DELETE Bucket replication config. -// ---------- -func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "DeleteBucketReplicationConfig") - defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) - vars := mux.Vars(r) - bucket := vars["bucket"] - - objectAPI := api.ObjectAPI() - if objectAPI == nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) - return - } - - if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) - return - } - // Check if bucket exists. - if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - if globalSiteReplicationSys.isEnabled() { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL) - return - } - if err := globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, nil); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - // Write success response. - writeSuccessResponseHeadersOnly(w) -} - -// GetBucketReplicationMetricsHandler - GET Bucket replication metrics. -// ---------- -// Gets the replication metrics for a bucket. -func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "GetBucketReplicationMetrics") - - defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) - - vars := mux.Vars(r) - bucket := vars["bucket"] - - objectAPI := api.ObjectAPI() - if objectAPI == nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) - return - } - - // check if user has permissions to perform this operation - if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) - return - } - - // Check if bucket exists. - if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - var usageInfo BucketUsageInfo - dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI) - if err == nil && !dataUsageInfo.LastUpdate.IsZero() { - usageInfo = dataUsageInfo.BucketsUsage[bucket] - } - - w.Header().Set(xhttp.ContentType, string(mimeJSON)) - - enc := json.NewEncoder(w) - if err = enc.Encode(getLatestReplicationStats(bucket, usageInfo)); err != nil { - writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) - return - } -} - -// ResetBucketReplicationStartHandler - starts a replication reset for all objects in a bucket which -// qualify for replication and re-sync the object(s) to target, provided ExistingObjectReplication is -// enabled for the qualifying rule. This API is a MinIO only extension provided for situations where -// remote target is entirely lost,and previously replicated objects need to be re-synced. If resync is -// already in progress it returns an error -func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "ResetBucketReplicationStart") - defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) - - vars := mux.Vars(r) - bucket := vars["bucket"] - durationStr := r.URL.Query().Get("older-than") - arn := r.URL.Query().Get("arn") - resetID := r.URL.Query().Get("reset-id") - if resetID == "" { - resetID = mustGetUUID() - } - var ( - days time.Duration - err error - ) - if durationStr != "" { - days, err = time.ParseDuration(durationStr) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, InvalidArgument{ - Bucket: bucket, - Err: fmt.Errorf("invalid query parameter older-than %s for %s : %w", durationStr, bucket, err), - }), r.URL) - } - } - resetBeforeDate := UTCNow().AddDate(0, 0, -1*int(days/24)) - - objectAPI := api.ObjectAPI() - if objectAPI == nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) - return - } - - if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) - return - } - - // Check if bucket exists. - if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - if !config.HasActiveRules("", true) { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoMatchingRuleError), r.URL) - return - } - tgtArns := config.FilterTargetArns( - replication.ObjectOpts{ - OpType: replication.ResyncReplicationType, - TargetArn: arn, - }) - - if len(tgtArns) == 0 { - writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ - Bucket: bucket, - Err: fmt.Errorf("Remote target ARN %s missing or ineligible for replication resync", arn), - }), r.URL) - return - } - - if len(tgtArns) > 1 && arn == "" { - writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ - Bucket: bucket, - Err: fmt.Errorf("ARN should be specified for replication reset"), - }), r.URL) - return - } - var rinfo ResyncTargetsInfo - target := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, tgtArns[0]) - target.ResetBeforeDate = UTCNow().AddDate(0, 0, -1*int(days/24)) - target.ResetID = resetID - rinfo.Targets = append(rinfo.Targets, ResyncTarget{Arn: tgtArns[0], ResetID: target.ResetID}) - if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, true); err != nil { - switch err.(type) { - case BucketRemoteConnectionErr: - writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationRemoteConnectionError, err), r.URL) - default: - writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) - } - } - if err := startReplicationResync(ctx, bucket, arn, resetID, resetBeforeDate, objectAPI); err != nil { - writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ - Bucket: bucket, - Err: err, - }), r.URL) - return - } - - data, err := json.Marshal(rinfo) - if err != nil { - writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) - return - } - // Write success response. - writeSuccessResponseJSON(w, data) -} - -// ResetBucketReplicationStatusHandler - returns the status of replication reset. -// This API is a MinIO only extension -func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "ResetBucketReplicationStatus") - defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) - - vars := mux.Vars(r) - bucket := vars["bucket"] - arn := r.URL.Query().Get("arn") - var err error - - objectAPI := api.ObjectAPI() - if objectAPI == nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) - return - } - - if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) - return - } - - // Check if bucket exists. - if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - if _, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) - return - } - - globalReplicationPool.resyncState.RLock() - brs, ok := globalReplicationPool.resyncState.statusMap[bucket] - if !ok { - brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI) - if err != nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ - Bucket: bucket, - Err: fmt.Errorf("No replication resync status available for %s", arn), - }), r.URL) - } - return - } - - var rinfo ResyncTargetsInfo - for tarn, st := range brs.TargetsMap { - if arn != "" && tarn != arn { - continue - } - rinfo.Targets = append(rinfo.Targets, ResyncTarget{ - Arn: tarn, - ResetID: st.ResyncID, - StartTime: st.StartTime, - EndTime: st.EndTime, - ResyncStatus: st.ResyncStatus.String(), - ReplicatedSize: st.ReplicatedSize, - ReplicatedCount: st.ReplicatedCount, - FailedSize: st.FailedSize, - FailedCount: st.FailedCount, - Bucket: st.Bucket, - Object: st.Object, - }) - } - globalReplicationPool.resyncState.RUnlock() - data, err := json.Marshal(rinfo) - if err != nil { - writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) - return - } - // Write success response. - writeSuccessResponseJSON(w, data) -} diff --git a/cmd/bucket-replication-handlers.go b/cmd/bucket-replication-handlers.go new file mode 100644 index 000000000..efb1cb7b9 --- /dev/null +++ b/cmd/bucket-replication-handlers.go @@ -0,0 +1,404 @@ +// Copyright (c) 2015-2022 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io" + "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/minio/minio/internal/bucket/replication" + xhttp "github.com/minio/minio/internal/http" + "github.com/minio/minio/internal/logger" + "github.com/minio/pkg/bucket/policy" +) + +// PutBucketReplicationConfigHandler - PUT Bucket replication configuration. +// ---------- +// Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html +func (api objectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "PutBucketReplicationConfig") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + vars := mux.Vars(r) + bucket := vars["bucket"] + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + if globalIsGateway { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) + return + } + if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) + return + } + // Check if bucket exists. + if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL) + return + } + replicationConfig, err := replication.ParseConfig(io.LimitReader(r.Body, r.ContentLength)) + if err != nil { + apiErr := errorCodes.ToAPIErr(ErrMalformedXML) + apiErr.Description = err.Error() + writeErrorResponse(ctx, w, apiErr, r.URL) + return + } + sameTarget, apiErr := validateReplicationDestination(ctx, bucket, replicationConfig, true) + if apiErr != noError { + writeErrorResponse(ctx, w, apiErr, r.URL) + return + } + // Validate the received bucket replication config + if err = replicationConfig.Validate(bucket, sameTarget); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + configData, err := xml.Marshal(replicationConfig) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + if err = globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, configData); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + // Write success response. + writeSuccessResponseHeadersOnly(w) +} + +// GetBucketReplicationConfigHandler - GET Bucket replication configuration. +// ---------- +// Gets the replication configuration for a bucket. +func (api objectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "GetBucketReplicationConfig") + + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + vars := mux.Vars(r) + bucket := vars["bucket"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + + // check if user has permissions to perform this operation + if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) + return + } + // Check if bucket exists. + if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + configData, err := xml.Marshal(config) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + // Write success response. + writeSuccessResponseXML(w, configData) +} + +// DeleteBucketReplicationConfigHandler - DELETE Bucket replication config. +// ---------- +func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "DeleteBucketReplicationConfig") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + vars := mux.Vars(r) + bucket := vars["bucket"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + + if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) + return + } + // Check if bucket exists. + if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + if globalSiteReplicationSys.isEnabled() { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationDenyEditError), r.URL) + return + } + if err := globalBucketMetadataSys.Update(ctx, bucket, bucketReplicationConfig, nil); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + // Write success response. + writeSuccessResponseHeadersOnly(w) +} + +// GetBucketReplicationMetricsHandler - GET Bucket replication metrics. +// ---------- +// Gets the replication metrics for a bucket. +func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "GetBucketReplicationMetrics") + + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + vars := mux.Vars(r) + bucket := vars["bucket"] + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + + // check if user has permissions to perform this operation + if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) + return + } + + // Check if bucket exists. + if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + var usageInfo BucketUsageInfo + dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI) + if err == nil && !dataUsageInfo.LastUpdate.IsZero() { + usageInfo = dataUsageInfo.BucketsUsage[bucket] + } + + w.Header().Set(xhttp.ContentType, string(mimeJSON)) + + enc := json.NewEncoder(w) + if err = enc.Encode(getLatestReplicationStats(bucket, usageInfo)); err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } +} + +// ResetBucketReplicationStartHandler - starts a replication reset for all objects in a bucket which +// qualify for replication and re-sync the object(s) to target, provided ExistingObjectReplication is +// enabled for the qualifying rule. This API is a MinIO only extension provided for situations where +// remote target is entirely lost,and previously replicated objects need to be re-synced. If resync is +// already in progress it returns an error +func (api objectAPIHandlers) ResetBucketReplicationStartHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "ResetBucketReplicationStart") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + vars := mux.Vars(r) + bucket := vars["bucket"] + durationStr := r.URL.Query().Get("older-than") + arn := r.URL.Query().Get("arn") + resetID := r.URL.Query().Get("reset-id") + if resetID == "" { + resetID = mustGetUUID() + } + var ( + days time.Duration + err error + ) + if durationStr != "" { + days, err = time.ParseDuration(durationStr) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, InvalidArgument{ + Bucket: bucket, + Err: fmt.Errorf("invalid query parameter older-than %s for %s : %w", durationStr, bucket, err), + }), r.URL) + } + } + resetBeforeDate := UTCNow().AddDate(0, 0, -1*int(days/24)) + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + + if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) + return + } + + // Check if bucket exists. + if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + if !config.HasExistingObjectReplication(arn) { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNoExistingObjects), r.URL) + return + } + + tgtArns := config.FilterTargetArns( + replication.ObjectOpts{ + OpType: replication.ResyncReplicationType, + TargetArn: arn, + }) + + if len(tgtArns) == 0 { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ + Bucket: bucket, + Err: fmt.Errorf("Remote target ARN %s missing or ineligible for replication resync", arn), + }), r.URL) + return + } + + if len(tgtArns) > 1 && arn == "" { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ + Bucket: bucket, + Err: fmt.Errorf("ARN should be specified for replication reset"), + }), r.URL) + return + } + var rinfo ResyncTargetsInfo + target := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, tgtArns[0]) + target.ResetBeforeDate = UTCNow().AddDate(0, 0, -1*int(days/24)) + target.ResetID = resetID + rinfo.Targets = append(rinfo.Targets, ResyncTarget{Arn: tgtArns[0], ResetID: target.ResetID}) + if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, true); err != nil { + switch err.(type) { + case BucketRemoteConnectionErr: + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrReplicationRemoteConnectionError, err), r.URL) + default: + writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) + } + } + if err := startReplicationResync(ctx, bucket, arn, resetID, resetBeforeDate, objectAPI); err != nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ + Bucket: bucket, + Err: err, + }), r.URL) + return + } + + data, err := json.Marshal(rinfo) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + // Write success response. + writeSuccessResponseJSON(w, data) +} + +// ResetBucketReplicationStatusHandler - returns the status of replication reset. +// This API is a MinIO only extension +func (api objectAPIHandlers) ResetBucketReplicationStatusHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "ResetBucketReplicationStatus") + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + vars := mux.Vars(r) + bucket := vars["bucket"] + arn := r.URL.Query().Get("arn") + var err error + + objectAPI := api.ObjectAPI() + if objectAPI == nil { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + + if s3Error := checkRequestAuthType(ctx, r, policy.ResetBucketReplicationStateAction, bucket, ""); s3Error != ErrNone { + writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) + return + } + + // Check if bucket exists. + if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + if _, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) + return + } + + globalReplicationPool.resyncState.RLock() + brs, ok := globalReplicationPool.resyncState.statusMap[bucket] + globalReplicationPool.resyncState.RUnlock() + if !ok { + brs, err = loadBucketResyncMetadata(ctx, bucket, objectAPI) + if err != nil { + writeErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrBadRequest, InvalidArgument{ + Bucket: bucket, + Err: fmt.Errorf("No replication resync status available for %s", arn), + }), r.URL) + return + } + } + + var rinfo ResyncTargetsInfo + for tarn, st := range brs.TargetsMap { + if arn != "" && tarn != arn { + continue + } + rinfo.Targets = append(rinfo.Targets, ResyncTarget{ + Arn: tarn, + ResetID: st.ResyncID, + StartTime: st.StartTime, + EndTime: st.EndTime, + ResyncStatus: st.ResyncStatus.String(), + ReplicatedSize: st.ReplicatedSize, + ReplicatedCount: st.ReplicatedCount, + FailedSize: st.FailedSize, + FailedCount: st.FailedCount, + Bucket: st.Bucket, + Object: st.Object, + }) + } + data, err := json.Marshal(rinfo) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + // Write success response. + writeSuccessResponseJSON(w, data) +} diff --git a/cmd/bucket-replication.go b/cmd/bucket-replication.go index becdc9c3d..b50afd86f 100644 --- a/cmd/bucket-replication.go +++ b/cmd/bucket-replication.go @@ -1757,10 +1757,6 @@ func (c replicationConfig) Resync(ctx context.Context, oi ObjectInfo, dsc *Repli if c.Empty() { return } - // existing object replication does not apply to un-versioned objects - if oi.VersionID == "" { - return - } // Now overlay existing object replication choices for target if oi.DeleteMarker { @@ -2006,6 +2002,7 @@ func resyncBucket(ctx context.Context, bucket, arn string, heal bool, objectAPI st.EndTime = UTCNow() st.ResyncStatus = resyncStatus m.TargetsMap[arn] = st + globalReplicationPool.resyncState.statusMap[bucket] = m globalReplicationPool.resyncState.Unlock() }() // Allocate new results channel to receive ObjectInfo. @@ -2066,7 +2063,6 @@ func resyncBucket(ctx context.Context, bucket, arn string, heal bool, objectAPI } if roi.DeleteMarker || !roi.VersionPurgeStatus.Empty() { - versionID := "" dmVersionID := "" if roi.VersionPurgeStatus.Empty() { @@ -2113,6 +2109,7 @@ func resyncBucket(ctx context.Context, bucket, arn string, heal bool, objectAPI st.ReplicatedSize += roi.Size } m.TargetsMap[arn] = st + globalReplicationPool.resyncState.statusMap[bucket] = m globalReplicationPool.resyncState.Unlock() } resyncStatus = ResyncCompleted diff --git a/docs/bucket/replication/setup_2site_existing_replication.sh b/docs/bucket/replication/setup_2site_existing_replication.sh new file mode 100755 index 000000000..0336975aa --- /dev/null +++ b/docs/bucket/replication/setup_2site_existing_replication.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +trap 'catch $LINENO' ERR + +# shellcheck disable=SC2120 +catch() { + if [ $# -ne 0 ]; then + echo "error on line $1" + for site in sitea siteb; do + echo "$site server logs =========" + cat "/tmp/${site}_1.log" + echo "===========================" + cat "/tmp/${site}_2.log" + done + fi + + echo "Cleaning up instances of MinIO" + pkill minio + pkill -9 minio + rm -rf /tmp/multisitea + rm -rf /tmp/multisiteb + rm -rf /tmp/data +} + +catch + +set -e +export MINIO_CI_CD=1 +export MINIO_BROWSER=off +export MINIO_ROOT_USER="minio" +export MINIO_ROOT_PASSWORD="minio123" +export MINIO_KMS_AUTO_ENCRYPTION=off +export MINIO_PROMETHEUS_AUTH_TYPE=public +export MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw= +unset MINIO_KMS_KES_CERT_FILE +unset MINIO_KMS_KES_KEY_FILE +unset MINIO_KMS_KES_ENDPOINT +unset MINIO_KMS_KES_KEY_NAME + +wget -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc \ + && chmod +x mc + +minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \ + "http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 & +minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \ + "http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 & + +minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \ + "http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 & +minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \ + "http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 & + +sleep 10s + +export MC_HOST_sitea=http://minio:minio123@127.0.0.1:9001 +export MC_HOST_siteb=http://minio:minio123@127.0.0.1:9004 + +./mc mb sitea/bucket + +## Create 100 files +mkdir -p /tmp/data +for i in $(seq 1 10); do + echo "T" > /tmp/data/file_${i}.txt +done + +./mc mirror /tmp/data sitea/bucket/ +./mc version enable sitea/bucket + +./mc mb siteb/bucket/ +./mc version enable siteb/bucket/ + +echo "adding replication config for site a -> site b" +remote_arn=$(./mc admin bucket remote add sitea/bucket/ \ + http://minio:minio123@127.0.0.1:9004/bucket \ + --service "replication" --json | jq -r ".RemoteARN") +echo "adding replication rule for a -> b : ${remote_arn}" + +./mc replicate add sitea/bucket/ \ + --remote-bucket "${remote_arn}" +sleep 1 + +./mc replicate resync start sitea/bucket/ --remote-bucket "${remote_arn}" +sleep 10s ## sleep for 10s idea is that we give 100ms per object. + +count=$(./mc replicate resync status sitea/bucket --remote-bucket "${remote_arn}" --json | jq .resyncInfo.target[].replicationCount) +./mc ls --versions sitea/bucket +./mc ls --versions siteb/bucket +if [ $count -ne 10 ]; then + echo "resync not complete after 100s unexpected failure" + ./mc diff sitea/bucket siteb/bucket +fi + +catch diff --git a/internal/bucket/replication/replication.go b/internal/bucket/replication/replication.go index 054792e27..dc94d7052 100644 --- a/internal/bucket/replication/replication.go +++ b/internal/bucket/replication/replication.go @@ -146,6 +146,18 @@ type ObjectOpts struct { TargetArn string } +// HasExistingObjectReplication returns true if any of the rule returns 'ExistingObjects' replication. +func (c Config) HasExistingObjectReplication(arn string) bool { + for _, rule := range c.Rules { + if rule.Destination.ARN == arn || c.RoleArn == arn { + if rule.ExistingObjectReplication.Status == Enabled { + return true + } + } + } + return false +} + // FilterActionableRules returns the rules actions that need to be executed // after evaluating prefix/tag filtering func (c Config) FilterActionableRules(obj ObjectOpts) []Rule {