Skip to content

Commit bf842c3

Browse files
committed
fix(storage): allow providing expected CRC32C to Finalize()
1 parent 982d3bf commit bf842c3

2 files changed

Lines changed: 161 additions & 0 deletions

File tree

google/cloud/storage/examples/storage_async_samples.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,40 @@ void CreateAndWriteAppendableObject(google::cloud::storage::AsyncClient& client,
728728
std::cout << "File successfully uploaded " << object.DebugString() << "\n";
729729
}
730730

731+
void CreateAndWriteAppendableObjectWithChecksum(
732+
google::cloud::storage::AsyncClient& client,
733+
std::vector<std::string> const& argv) {
734+
//! [create-and-write-appendable-object-with-checksum]
735+
// [START storage_create_and_write_appendable_object_with_checksum]
736+
namespace gcs = google::cloud::storage;
737+
auto coro = [](gcs::AsyncClient& client, std::string bucket_name,
738+
std::string object_name)
739+
-> google::cloud::future<google::storage::v2::Object> {
740+
auto [writer, token] =
741+
(co_await client.StartAppendableObjectUpload(
742+
gcs::BucketName(std::move(bucket_name)), std::move(object_name)))
743+
.value();
744+
std::cout << "Appendable upload started for object " << object_name << "\n";
745+
746+
token = (co_await writer.Write(std::move(token),
747+
gcs::WritePayload("Some data\n")))
748+
.value();
749+
std::cout << "Wrote some data.\n";
750+
751+
// Set the expected CRC32C checksum in the current options scope
752+
// just before calling Finalize().
753+
google::cloud::internal::OptionsSpan span(
754+
google::cloud::Options{}.set<gcs::UseCrc32cValueOption>(1848151177U));
755+
co_return (co_await writer.Finalize(std::move(token))).value();
756+
};
757+
// [END storage_create_and_write_appendable_object_with_checksum]
758+
//! [create-and-write-appendable-object-with-checksum]
759+
// The example is easier to test and run if we call the coroutine and block
760+
// until it completes..
761+
auto const object = coro(client, argv.at(0), argv.at(1)).get();
762+
std::cout << "File successfully uploaded " << object.DebugString() << "\n";
763+
}
764+
731765
void PauseAndResumeAppendableUpload(google::cloud::storage::AsyncClient& client,
732766
std::vector<std::string> const& argv) {
733767
//! [pause-and-resume-appendable-upload]
@@ -1035,6 +1069,12 @@ void CreateAndWriteAppendableObject(google::cloud::storage::AsyncClient&,
10351069
"coroutines\n";
10361070
}
10371071

1072+
void CreateAndWriteAppendableObjectWithChecksum(
1073+
google::cloud::storage::AsyncClient&, std::vector<std::string> const&) {
1074+
std::cerr << "AsyncClient::CreateAndWriteAppendableObjectWithChecksum() "
1075+
"example requires coroutines\n";
1076+
}
1077+
10381078
void PauseAndResumeAppendableUpload(google::cloud::storage::AsyncClient&,
10391079
std::vector<std::string> const&) {
10401080
std::cerr << "AsyncClient::PauseAndResumeAppendableUpload() example requires "
@@ -1505,6 +1545,8 @@ int main(int argc, char* argv[]) try {
15051545

15061546
make_entry("create-and-write-appendable-object", {},
15071547
CreateAndWriteAppendableObject),
1548+
make_entry("create-and-write-appendable-object-with-checksum", {},
1549+
CreateAndWriteAppendableObjectWithChecksum),
15081550
make_entry("pause-and-resume-appendable-upload", {},
15091551
PauseAndResumeAppendableUpload),
15101552
make_entry("finalize-appendable-object-upload", {},

google/cloud/storage/internal/async/writer_connection_impl_test.cc

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
#include "google/cloud/storage/internal/async/writer_connection_impl.h"
1616
#include "google/cloud/mocks/mock_async_streaming_read_write_rpc.h"
17+
#include "google/cloud/storage/async/options.h"
1718
#include "google/cloud/storage/internal/async/write_object.h"
1819
#include "google/cloud/storage/internal/crc32c.h"
1920
#include "google/cloud/storage/internal/grpc/ctype_cord_workaround.h"
21+
#include "google/cloud/storage/internal/grpc/object_metadata_parser.h"
2022
#include "google/cloud/storage/internal/hash_function_impl.h"
2123
#include "google/cloud/storage/options.h"
2224
#include "google/cloud/storage/testing/canonical_errors.h"
@@ -744,6 +746,123 @@ TEST(AsyncWriterConnectionTest, FinalizeAppendableNoChecksum) {
744746
next.first.set_value(true);
745747
}
746748

749+
TEST(AsyncWriterConnectionTest, FinalizeAppendableWithExpectedChecksum) {
750+
AsyncSequencer<bool> sequencer;
751+
auto mock = std::make_unique<MockStream>();
752+
EXPECT_CALL(*mock, Cancel).Times(1);
753+
EXPECT_CALL(*mock, Write)
754+
.WillOnce([&](Request const& request, grpc::WriteOptions wopt) {
755+
EXPECT_TRUE(request.finish_write());
756+
EXPECT_TRUE(wopt.is_last_message());
757+
EXPECT_EQ(request.common_object_request_params().encryption_algorithm(),
758+
"test-only-algo");
759+
EXPECT_TRUE(request.has_object_checksums());
760+
EXPECT_EQ(request.object_checksums().crc32c(),
761+
123456); // wait, it might be an int in proto
762+
return sequencer.PushBack("Write");
763+
});
764+
EXPECT_CALL(*mock, Read).WillOnce([&]() {
765+
return sequencer.PushBack("Read").then([](auto f) {
766+
if (!f.get()) return absl::optional<Response>();
767+
return absl::make_optional(MakeTestResponse());
768+
});
769+
});
770+
EXPECT_CALL(*mock, Finish).WillOnce([&] {
771+
return sequencer.PushBack("Finish").then([](auto f) {
772+
if (f.get()) return Status{};
773+
return PermanentError();
774+
});
775+
});
776+
auto hash = std::make_shared<MockHashFunction>();
777+
EXPECT_CALL(*hash, Update(_, An<absl::Cord const&>(), _)).Times(1);
778+
EXPECT_CALL(*hash, Finish)
779+
.WillOnce(Return(storage::internal::HashValues{
780+
storage_internal::Crc32cFromProto(123456), {}}));
781+
782+
auto request = MakeRequest();
783+
request.mutable_write_object_spec()->set_appendable(true);
784+
785+
auto options = internal::MakeImmutableOptions(
786+
Options{}.set<storage::UseCrc32cValueOption>(123456));
787+
788+
auto tested = std::make_unique<AsyncWriterConnectionImpl>(
789+
options, std::move(request), std::move(mock), hash, 1024);
790+
auto response = tested->Finalize(WritePayload{});
791+
792+
auto next = sequencer.PopFrontWithName();
793+
ASSERT_THAT(next.second, "Write");
794+
next.first.set_value(true);
795+
next = sequencer.PopFrontWithName();
796+
ASSERT_THAT(next.second, "Read");
797+
next.first.set_value(true);
798+
auto object = response.get();
799+
EXPECT_THAT(object, IsOkAndHolds(IsProtoEqual(MakeTestObject())))
800+
<< "=" << object->DebugString();
801+
802+
tested = {};
803+
next = sequencer.PopFrontWithName();
804+
ASSERT_THAT(next.second, "Finish");
805+
next.first.set_value(true);
806+
}
807+
808+
TEST(AsyncWriterConnectionTest,
809+
FinalizeAppendableWithExpectedChecksumFromCurrentOptions) {
810+
AsyncSequencer<bool> sequencer;
811+
auto mock = std::make_unique<MockStream>();
812+
EXPECT_CALL(*mock, Cancel).Times(1);
813+
EXPECT_CALL(*mock, Write)
814+
.WillOnce([&](Request const& request, grpc::WriteOptions wopt) {
815+
EXPECT_TRUE(request.finish_write());
816+
EXPECT_TRUE(wopt.is_last_message());
817+
EXPECT_EQ(request.common_object_request_params().encryption_algorithm(),
818+
"test-only-algo");
819+
EXPECT_TRUE(request.has_object_checksums());
820+
EXPECT_EQ(request.object_checksums().crc32c(), 654321);
821+
return sequencer.PushBack("Write");
822+
});
823+
EXPECT_CALL(*mock, Read).WillOnce([&]() {
824+
return sequencer.PushBack("Read").then([](auto f) {
825+
if (!f.get()) return absl::optional<Response>();
826+
return absl::make_optional(MakeTestResponse());
827+
});
828+
});
829+
EXPECT_CALL(*mock, Finish).WillOnce([&] {
830+
return sequencer.PushBack("Finish").then([](auto f) {
831+
if (f.get()) return Status{};
832+
return PermanentError();
833+
});
834+
});
835+
auto hash = std::make_shared<MockHashFunction>();
836+
EXPECT_CALL(*hash, Update(_, An<absl::Cord const&>(), _)).Times(1);
837+
// It shouldn't call Finish() because we use kFinalize!
838+
EXPECT_CALL(*hash, Finish).Times(0);
839+
840+
auto request = MakeRequest();
841+
request.mutable_write_object_spec()->set_appendable(true);
842+
843+
auto tested = std::make_unique<AsyncWriterConnectionImpl>(
844+
TestOptions(), std::move(request), std::move(mock), hash, 1024);
845+
846+
internal::OptionsSpan span(
847+
Options{}.set<storage::UseCrc32cValueOption>(654321));
848+
auto response = tested->Finalize(WritePayload{});
849+
850+
auto next = sequencer.PopFrontWithName();
851+
ASSERT_THAT(next.second, "Write");
852+
next.first.set_value(true);
853+
next = sequencer.PopFrontWithName();
854+
ASSERT_THAT(next.second, "Read");
855+
next.first.set_value(true);
856+
auto object = response.get();
857+
EXPECT_THAT(object, IsOkAndHolds(IsProtoEqual(MakeTestObject())))
858+
<< "=" << object->DebugString();
859+
860+
tested = {};
861+
next = sequencer.PopFrontWithName();
862+
ASSERT_THAT(next.second, "Finish");
863+
next.first.set_value(true);
864+
}
865+
747866
TEST(AsyncWriterConnectionTest, ResumeWithHandle) {
748867
AsyncSequencer<bool> sequencer;
749868
auto mock = std::make_unique<MockStream>();

0 commit comments

Comments
 (0)