Skip to content

Commit 5a7ce3b

Browse files
authored
feat(storage): add MoveObject functionality to JSON and gRPC (#14936)
* feat(storage): add MoveObject functionality to JSON and gRPC * add moveobject integration test and update testbench version * checkers * add patchbucket call to integration test * use folder enabled bucket * create folder bucket in emulator * make non-pure virtual to fix abi issue
1 parent 51e05fe commit 5a7ce3b

34 files changed

+416
-1
lines changed

ci/cloudbuild/builds/lib/integration.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ source module ci/lib/io.sh
3131
export PATH="${HOME}/.local/bin:${PATH}"
3232
python3 -m pip uninstall -y --quiet googleapis-storage-testbench
3333
python3 -m pip install --upgrade --user --quiet --disable-pip-version-check \
34-
"git+https://github.com/googleapis/storage-testbench@v0.46.0"
34+
"git+https://github.com/googleapis/storage-testbench@v0.52.0"
3535

3636
# Some of the tests will need a valid roots.pem file.
3737
rm -f /dev/shm/roots.pem

ci/lib/run_gcs_httpbin_emulator_utils.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,8 @@ create_testbench_resources() {
127127
curl -s -o /dev/null -X POST --data-binary @- \
128128
-H "Content-Type: application/json" \
129129
"${CLOUD_STORAGE_EMULATOR_ENDPOINT}/storage/v1/b?project=${GOOGLE_CLOUD_PROJECT}"
130+
printf '{"name": "%s"}' "${GOOGLE_CLOUD_CPP_STORAGE_TEST_FOLDER_BUCKET_NAME}" |
131+
curl -s -o /dev/null -X POST --data-binary @- \
132+
-H "Content-Type: application/json" \
133+
"${CLOUD_STORAGE_EMULATOR_ENDPOINT}/storage/v1/b?project=${GOOGLE_CLOUD_PROJECT}"
130134
}

google/cloud/storage/client.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,39 @@ class Client {
13721372
return connection_->UpdateObject(request);
13731373
}
13741374

1375+
/**
1376+
* Moves an existing object to a new or existing object within a HNS enabled
1377+
* bucket.
1378+
*
1379+
* @param bucket_name the name of the bucket in which to move the object. The
1380+
* bucket must be HNS enabled.
1381+
* @param source_object_name the name of the source object to move.
1382+
* @param destination_object_name the destination name of the object after the
1383+
* move is completed.
1384+
* @param options a list of optional query parameters and/or request headers.
1385+
* Valid types for this operation include
1386+
* `IfSourceGenerationMatch`, `IfSourceGenerationNotMatch`,
1387+
* `IfSourceMetagenerationMatch`, `IfSourceMetagenerationNotMatch`,
1388+
* `IfGenerationMatch`, `IfGenerationNotMatch`, `IfMetagenerationMatch`
1389+
* `IfMetagenerationNotMatch`, `projection`.
1390+
*
1391+
* @par Idempotency
1392+
* This operation is only idempotent if restricted by pre-conditions.
1393+
*/
1394+
template <typename... Options>
1395+
StatusOr<ObjectMetadata> MoveObject(std::string bucket_name,
1396+
std::string source_object_name,
1397+
std::string destination_object_name,
1398+
Options&&... options) {
1399+
google::cloud::internal::OptionsSpan const span(
1400+
SpanOptions(std::forward<Options>(options)...));
1401+
internal::MoveObjectRequest request(std::move(bucket_name),
1402+
std::move(source_object_name),
1403+
std::move(destination_object_name));
1404+
request.set_multiple_options(std::forward<Options>(options)...);
1405+
return connection_->MoveObject(request);
1406+
}
1407+
13751408
/**
13761409
* Patches the metadata in a Google Cloud Storage Object.
13771410
*

google/cloud/storage/idempotency_policy.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ bool AlwaysRetryIdempotencyPolicy::IsIdempotent(
9292
internal::UpdateObjectRequest const&) const {
9393
return true;
9494
}
95+
bool AlwaysRetryIdempotencyPolicy::IsIdempotent(
96+
internal::MoveObjectRequest const&) const {
97+
return true;
98+
}
9599
bool AlwaysRetryIdempotencyPolicy::IsIdempotent(
96100
internal::PatchObjectRequest const&) const {
97101
return true;
@@ -341,6 +345,11 @@ bool StrictIdempotencyPolicy::IsIdempotent(
341345
request.HasOption<IfMetagenerationMatch>();
342346
}
343347

348+
bool StrictIdempotencyPolicy::IsIdempotent(
349+
internal::MoveObjectRequest const& request) const {
350+
return request.HasOption<IfGenerationMatch>();
351+
}
352+
344353
bool StrictIdempotencyPolicy::IsIdempotent(
345354
internal::PatchObjectRequest const& request) const {
346355
return request.HasOption<IfMatchEtag>() ||

google/cloud/storage/idempotency_policy.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ class IdempotencyPolicy {
102102
internal::DeleteObjectRequest const& request) const = 0;
103103
virtual bool IsIdempotent(
104104
internal::UpdateObjectRequest const& request) const = 0;
105+
virtual bool IsIdempotent(internal::MoveObjectRequest const&) const {
106+
return false;
107+
};
105108
virtual bool IsIdempotent(
106109
internal::PatchObjectRequest const& request) const = 0;
107110
virtual bool IsIdempotent(
@@ -238,6 +241,7 @@ class AlwaysRetryIdempotencyPolicy : public IdempotencyPolicy {
238241
internal::DeleteObjectRequest const& request) const override;
239242
bool IsIdempotent(
240243
internal::UpdateObjectRequest const& request) const override;
244+
bool IsIdempotent(internal::MoveObjectRequest const& request) const override;
241245
bool IsIdempotent(internal::PatchObjectRequest const& request) const override;
242246
bool IsIdempotent(
243247
internal::ComposeObjectRequest const& request) const override;
@@ -370,6 +374,7 @@ class StrictIdempotencyPolicy : public IdempotencyPolicy {
370374
internal::DeleteObjectRequest const& request) const override;
371375
bool IsIdempotent(
372376
internal::UpdateObjectRequest const& request) const override;
377+
bool IsIdempotent(internal::MoveObjectRequest const& request) const override;
373378
bool IsIdempotent(internal::PatchObjectRequest const& request) const override;
374379
bool IsIdempotent(
375380
internal::ComposeObjectRequest const& request) const override;

google/cloud/storage/idempotency_policy_test.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,21 @@ TEST(StrictIdempotencyPolicyTest, UpdateObjectIfMetagenerationMatch) {
231231
EXPECT_TRUE(policy.IsIdempotent(request));
232232
}
233233

234+
TEST(StrictIdempotencyPolicyTest, MoveObject) {
235+
StrictIdempotencyPolicy policy;
236+
internal::MoveObjectRequest request(
237+
"test-bucket-name", "test-src-object-name", "test-dst-object-name");
238+
EXPECT_FALSE(policy.IsIdempotent(request));
239+
}
240+
241+
TEST(StrictIdempotencyPolicyTest, MoveObjectIfGenerationMatch) {
242+
StrictIdempotencyPolicy policy;
243+
internal::MoveObjectRequest request(
244+
"test-bucket-name", "test-src-object-name", "test-dst-object-name");
245+
request.set_option(IfGenerationMatch(7));
246+
EXPECT_TRUE(policy.IsIdempotent(request));
247+
}
248+
234249
TEST(StrictIdempotencyPolicyTest, PatchObject) {
235250
StrictIdempotencyPolicy policy;
236251
internal::PatchObjectRequest request("test-bucket-name", "test-object-name",

google/cloud/storage/internal/connection_impl.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,22 @@ StatusOr<ObjectMetadata> StorageConnectionImpl::UpdateObject(
451451
google::cloud::internal::CurrentOptions(), request, __func__);
452452
}
453453

454+
StatusOr<ObjectMetadata> StorageConnectionImpl::MoveObject(
455+
MoveObjectRequest const& request) {
456+
auto const idempotency = current_idempotency_policy().IsIdempotent(request)
457+
? Idempotency::kIdempotent
458+
: Idempotency::kNonIdempotent;
459+
return RestRetryLoop(
460+
current_retry_policy(), current_backoff_policy(), idempotency,
461+
[token = MakeIdempotencyToken(), this](
462+
rest_internal::RestContext& context, Options const& options,
463+
auto const& request) {
464+
context.AddHeader(kIdempotencyTokenHeader, token);
465+
return stub_->MoveObject(context, options, request);
466+
},
467+
google::cloud::internal::CurrentOptions(), request, __func__);
468+
}
469+
454470
StatusOr<ObjectMetadata> StorageConnectionImpl::PatchObject(
455471
PatchObjectRequest const& request) {
456472
auto const idempotency = current_idempotency_policy().IsIdempotent(request)

google/cloud/storage/internal/connection_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ class StorageConnectionImpl
8080
StatusOr<EmptyResponse> DeleteObject(DeleteObjectRequest const&) override;
8181
StatusOr<ObjectMetadata> UpdateObject(
8282
UpdateObjectRequest const& request) override;
83+
StatusOr<ObjectMetadata> MoveObject(
84+
MoveObjectRequest const& request) override;
8385
StatusOr<ObjectMetadata> PatchObject(
8486
PatchObjectRequest const& request) override;
8587
StatusOr<ObjectMetadata> ComposeObject(

google/cloud/storage/internal/connection_impl_object_copy_test.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,34 @@ TEST(StorageConnectionImpl, RewriteObjectPermanentFailure) {
122122
EXPECT_THAT(permanent.captured_authority_options(), RetryLoopUsesOptions());
123123
}
124124

125+
TEST(StorageConnectionImpl, MoveObjectTooManyFailures) {
126+
auto transient = MockRetryClientFunction(TransientError());
127+
auto mock = std::make_unique<MockGenericStub>();
128+
EXPECT_CALL(*mock, options);
129+
EXPECT_CALL(*mock, MoveObject).Times(3).WillRepeatedly(transient);
130+
auto client =
131+
StorageConnectionImpl::Create(std::move(mock), RetryTestOptions());
132+
google::cloud::internal::OptionsSpan span(client->options());
133+
auto response = client->MoveObject(MoveObjectRequest()).status();
134+
EXPECT_THAT(response, StoppedOnTooManyTransients("MoveObject"));
135+
EXPECT_THAT(transient.captured_tokens(), RetryLoopUsesSingleToken());
136+
EXPECT_THAT(transient.captured_authority_options(), RetryLoopUsesOptions());
137+
}
138+
139+
TEST(StorageConnectionImpl, MoveObjectPermanentFailure) {
140+
auto permanent = MockRetryClientFunction(PermanentError());
141+
auto mock = std::make_unique<MockGenericStub>();
142+
EXPECT_CALL(*mock, options);
143+
EXPECT_CALL(*mock, MoveObject).WillOnce(permanent);
144+
auto client =
145+
StorageConnectionImpl::Create(std::move(mock), RetryTestOptions());
146+
google::cloud::internal::OptionsSpan span(client->options());
147+
auto response = client->MoveObject(MoveObjectRequest()).status();
148+
EXPECT_THAT(response, StoppedOnPermanentError("MoveObject"));
149+
EXPECT_THAT(permanent.captured_tokens(), RetryLoopUsesSingleToken());
150+
EXPECT_THAT(permanent.captured_authority_options(), RetryLoopUsesOptions());
151+
}
152+
125153
} // namespace
126154
} // namespace internal
127155
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END

google/cloud/storage/internal/generic_stub.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ class GenericStub {
121121
virtual StatusOr<storage::ObjectMetadata> UpdateObject(
122122
rest_internal::RestContext&, Options const&,
123123
storage::internal::UpdateObjectRequest const&) = 0;
124+
virtual StatusOr<storage::ObjectMetadata> MoveObject(
125+
rest_internal::RestContext&, Options const&,
126+
storage::internal::MoveObjectRequest const&) = 0;
124127
virtual StatusOr<storage::ObjectMetadata> PatchObject(
125128
rest_internal::RestContext&, Options const&,
126129
storage::internal::PatchObjectRequest const&) = 0;

0 commit comments

Comments
 (0)