Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions google/cloud/storage/internal/grpc/object_request_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,9 @@ StatusOr<google::storage::v2::ComposeObjectRequest> ToProto(
request.GetOption<storage::IfMetagenerationMatch>().value());
}
result.set_kms_key(request.GetOption<storage::KmsKeyName>().value_or(""));
if (request.GetOption<storage::DeleteSourceObjects>().value_or(false)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we're using GetOption instead of HasOption, which seems to be intentional so that the default behavior (no deletion) is preserved if the option is not explicitly provided. Should we add a comment to clarify?

result.set_delete_source_objects(true);
}
return result;
}

Expand Down
3 changes: 3 additions & 0 deletions google/cloud/storage/internal/object_requests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,9 @@ std::string ComposeObjectRequest::JsonPayload() const {
source_object_list.emplace_back(std::move(source_object_json));
}
compose_object_payload_json["sourceObjects"] = source_object_list;
if (GetOption<DeleteSourceObjects>().value_or(false)) {
compose_object_payload_json["deleteSourceObjects"] = true;
}

return compose_object_payload_json.dump();
}
Expand Down
8 changes: 4 additions & 4 deletions google/cloud/storage/internal/object_requests.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,10 @@ std::ostream& operator<<(std::ostream& os, UpdateObjectRequest const& r);
* Represents a request to the `Objects: compose` API.
*/
class ComposeObjectRequest
: public GenericObjectRequest<ComposeObjectRequest, EncryptionKey,
DestinationPredefinedAcl, KmsKeyName,
IfGenerationMatch, IfMetagenerationMatch,
UserProject, WithObjectMetadata> {
: public GenericObjectRequest<
ComposeObjectRequest, EncryptionKey, DestinationPredefinedAcl,
KmsKeyName, IfGenerationMatch, IfMetagenerationMatch, UserProject,
WithObjectMetadata, DeleteSourceObjects> {
public:
ComposeObjectRequest() = default;
explicit ComposeObjectRequest(std::string bucket_name,
Expand Down
76 changes: 76 additions & 0 deletions google/cloud/storage/tests/object_rewrite_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,82 @@ TEST_F(ObjectRewriteIntegrationTest, ComposeSimple) {
EXPECT_EQ(meta->size() * 2, composed_meta->size());
}

TEST_F(ObjectRewriteIntegrationTest, ComposeDeleteSourceObjectsFalse) {
auto client = MakeIntegrationTestClient();

auto object_name1 = MakeRandomObjectName();
StatusOr<ObjectMetadata> meta1 = client.InsertObject(
bucket_name_, object_name1, LoremIpsum(), IfGenerationMatch(0));
ASSERT_STATUS_OK(meta1);
ScheduleForDelete(*meta1);

auto object_name2 = MakeRandomObjectName();
StatusOr<ObjectMetadata> meta2 = client.InsertObject(
bucket_name_, object_name2, LoremIpsum(), IfGenerationMatch(0));
ASSERT_STATUS_OK(meta2);
ScheduleForDelete(*meta2);

auto composed_object_name = MakeRandomObjectName();
std::vector<ComposeSourceObject> source_objects = {{object_name1, {}, {}},
{object_name2, {}, {}}};

StatusOr<ObjectMetadata> composed_meta = client.ComposeObject(
bucket_name_, source_objects, composed_object_name,
WithObjectMetadata(ObjectMetadata().set_content_type("plain/text")),
DeleteSourceObjects(false));
ASSERT_STATUS_OK(composed_meta);
ScheduleForDelete(*composed_meta);

EXPECT_EQ(meta1->size() + meta2->size(), composed_meta->size());

auto check1 = client.GetObjectMetadata(bucket_name_, object_name1);
EXPECT_STATUS_OK(check1) << "Source object 1 (" << object_name1
<< ") should NOT have been deleted.";

auto check2 = client.GetObjectMetadata(bucket_name_, object_name2);
EXPECT_STATUS_OK(check2) << "Source object 2 (" << object_name2
<< ") should NOT have been deleted.";
}

TEST_F(ObjectRewriteIntegrationTest, ComposeDeleteSourceObjectsTrue) {
auto client = MakeIntegrationTestClient();

auto object_name1 = MakeRandomObjectName();
StatusOr<ObjectMetadata> meta1 = client.InsertObject(
bucket_name_, object_name1, LoremIpsum(), IfGenerationMatch(0));
ASSERT_STATUS_OK(meta1);
ScheduleForDelete(*meta1);

auto object_name2 = MakeRandomObjectName();
StatusOr<ObjectMetadata> meta2 = client.InsertObject(
bucket_name_, object_name2, LoremIpsum(), IfGenerationMatch(0));
ASSERT_STATUS_OK(meta2);
ScheduleForDelete(*meta2);

auto composed_object_name = MakeRandomObjectName();
std::vector<ComposeSourceObject> source_objects = {{object_name1, {}, {}},
{object_name2, {}, {}}};

StatusOr<ObjectMetadata> composed_meta = client.ComposeObject(
bucket_name_, source_objects, composed_object_name,
WithObjectMetadata(ObjectMetadata().set_content_type("plain/text")),
DeleteSourceObjects(true));
ASSERT_STATUS_OK(composed_meta);
ScheduleForDelete(*composed_meta);

EXPECT_EQ(meta1->size() + meta2->size(), composed_meta->size());

auto check1 = client.GetObjectMetadata(bucket_name_, object_name1);
EXPECT_FALSE(check1.ok());
EXPECT_EQ(StatusCode::kNotFound, check1.status().code())
<< "Source object 1 (" << object_name1 << ") should have been deleted.";

auto check2 = client.GetObjectMetadata(bucket_name_, object_name2);
EXPECT_FALSE(check2.ok());
EXPECT_EQ(StatusCode::kNotFound, check2.status().code())
<< "Source object 2 (" << object_name2 << ") should have been deleted.";
}

TEST_F(ObjectRewriteIntegrationTest, ComposedUsingEncryptedObject) {
// TODO(#14385) - the emulator does not support this feature for gRPC.
if (UsingEmulator() && UsingGrpc()) GTEST_SKIP();
Expand Down
11 changes: 11 additions & 0 deletions google/cloud/storage/well_known_parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,17 @@ struct ReturnPartialSuccess
}
};

/**
* Controls if the source objects should be deleted after a successful compose.
*/
struct DeleteSourceObjects
: public internal::WellKnownParameter<DeleteSourceObjects, bool> {
using WellKnownParameter<DeleteSourceObjects, bool>::WellKnownParameter;
static char const* well_known_parameter_name() {
return "deleteSourceObjects";
}
};
Comment thread
v-pratap marked this conversation as resolved.

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace storage
} // namespace cloud
Expand Down
Loading