diff --git a/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz index 30757c6898d1d..660326ecb971a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz index 900b8a9f5a462..6dd6f1d810101 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz differ diff --git a/generator/generator_config.textproto b/generator/generator_config.textproto index b18d11ee0976d..c62acf5c64025 100644 --- a/generator/generator_config.textproto +++ b/generator/generator_config.textproto @@ -3905,6 +3905,7 @@ service { gen_async_rpcs: [ "ComposeObject", "DeleteObject", + "GetBucket", "ReadObject", "RewriteObject", "StartResumableWrite", diff --git a/google/cloud/storage/async/client.cc b/google/cloud/storage/async/client.cc index 9f9baf89da6ca..6b01986317b2b 100644 --- a/google/cloud/storage/async/client.cc +++ b/google/cloud/storage/async/client.cc @@ -41,6 +41,20 @@ AsyncClient::AsyncClient(Options options) { AsyncClient::AsyncClient(std::shared_ptr connection) : connection_(std::move(connection)) {} +future> AsyncClient::GetBucket( + BucketName const& bucket_name, Options opts) { + auto request = google::storage::v2::GetBucketRequest{}; + request.set_name(bucket_name.FullName()); + return GetBucket(std::move(request), std::move(opts)); +} + +future> AsyncClient::GetBucket( + google::storage::v2::GetBucketRequest request, Options opts) { + return connection_->GetBucket( + {std::move(request), google::cloud::internal::MergeOptions( + std::move(opts), connection_->options())}); +} + future> AsyncClient::InsertObject( google::storage::v2::WriteObjectRequest request, WritePayload contents, Options opts) { diff --git a/google/cloud/storage/async/client.h b/google/cloud/storage/async/client.h index 93009005af3c5..cfdc78d044b32 100644 --- a/google/cloud/storage/async/client.h +++ b/google/cloud/storage/async/client.h @@ -18,6 +18,7 @@ #include "google/cloud/storage/async/bucket_name.h" #include "google/cloud/storage/async/connection.h" #include "google/cloud/storage/async/object_descriptor.h" +#include "google/cloud/storage/async/options.h" #include "google/cloud/storage/async/reader.h" #include "google/cloud/storage/async/rewriter.h" #include "google/cloud/storage/async/token.h" @@ -89,6 +90,36 @@ class AsyncClient { ~AsyncClient() = default; + /** + * Get bucket metadata. + * + * @par Example + * @snippet storage_async_samples.cc get-bucket + * + * @par Idempotency + * This is a read-only operation and is always idempotent. + * + * @param bucket_name the name of the bucket to get metadata for. + * @param opts options controlling the behavior of this RPC. + */ + future> GetBucket( + BucketName const& bucket_name, Options opts = {}); + + /** + * Get bucket metadata using a raw request. + * + * @par Example + * @snippet storage_async_samples.cc get-bucket + * + * @par Idempotency + * This is a read-only operation and is always idempotent. + * + * @param request the request contents. + * @param opts options controlling the behavior of this RPC. + */ + future> GetBucket( + google::storage::v2::GetBucketRequest request, Options opts = {}); + /* This snippet discusses the tradeoffs between `InsertObject()`, `StartBufferedUpload()`, and `StartUnbufferedUpload()`. The text is included diff --git a/google/cloud/storage/async/client_test.cc b/google/cloud/storage/async/client_test.cc index ae50c9f167413..0c369deb0dd21 100644 --- a/google/cloud/storage/async/client_test.cc +++ b/google/cloud/storage/async/client_test.cc @@ -61,6 +61,76 @@ auto TestProtoObject() { return result; } +auto TestProtoBucket() { + google::storage::v2::Bucket result; + result.set_name("projects/_/buckets/test-bucket"); + return result; +} + +TEST(AsyncClient, GetBucket1) { + auto constexpr kExpectedRequest = R"pb( + name: "projects/_/buckets/test-bucket" + )pb"; + auto mock = std::make_shared(); + EXPECT_CALL(*mock, options) + .WillRepeatedly( + Return(Options{}.set>("O0").set>("O1"))); + + EXPECT_CALL(*mock, GetBucket) + .WillOnce([&](AsyncConnection::GetBucketParams const& p) { + EXPECT_THAT(p.options.get>(), "O0"); + EXPECT_THAT(p.options.get>(), "O1-function"); + EXPECT_THAT(p.options.get>(), "O2-function"); + auto expected = google::storage::v2::GetBucketRequest{}; + EXPECT_TRUE(TextFormat::ParseFromString(kExpectedRequest, &expected)); + EXPECT_THAT(p.request, IsProtoEqual(expected)); + return make_ready_future(make_status_or(TestProtoBucket())); + }); + + auto client = AsyncClient(mock); + auto response = client + .GetBucket(BucketName("test-bucket"), + Options{} + .set>("O1-function") + .set>("O2-function")) + .get(); + ASSERT_STATUS_OK(response); + EXPECT_THAT(*response, IsProtoEqual(TestProtoBucket())); +} + +TEST(AsyncClient, GetBucket2) { + auto constexpr kExpectedRequest = R"pb( + name: "projects/_/buckets/test-bucket" + )pb"; + auto mock = std::make_shared(); + EXPECT_CALL(*mock, options) + .WillRepeatedly( + Return(Options{}.set>("O0").set>("O1"))); + + EXPECT_CALL(*mock, GetBucket) + .WillOnce([&](AsyncConnection::GetBucketParams const& p) { + EXPECT_THAT(p.options.get>(), "O0"); + EXPECT_THAT(p.options.get>(), "O1-function"); + EXPECT_THAT(p.options.get>(), "O2-function"); + auto expected = google::storage::v2::GetBucketRequest{}; + EXPECT_TRUE(TextFormat::ParseFromString(kExpectedRequest, &expected)); + EXPECT_THAT(p.request, IsProtoEqual(expected)); + return make_ready_future(make_status_or(TestProtoBucket())); + }); + + auto request = google::storage::v2::GetBucketRequest{}; + request.set_name("projects/_/buckets/test-bucket"); + auto client = AsyncClient(mock); + auto response = + client + .GetBucket(std::move(request), Options{} + .set>("O1-function") + .set>("O2-function")) + .get(); + ASSERT_STATUS_OK(response); + EXPECT_THAT(*response, IsProtoEqual(TestProtoBucket())); +} + TEST(AsyncClient, InsertObject1) { auto constexpr kExpectedRequest = R"pb( write_object_spec { diff --git a/google/cloud/storage/async/connection.h b/google/cloud/storage/async/connection.h index 8e9df06d9f9e9..ef369617781b9 100644 --- a/google/cloud/storage/async/connection.h +++ b/google/cloud/storage/async/connection.h @@ -54,6 +54,21 @@ class AsyncConnection { /// The options used to configure this connection, with any defaults applied. virtual Options options() const = 0; + /** + * A thin wrapper around the `GetBucket()` parameters. + * + * We use a single struct as the input parameter for this function to + * prevent breaking any mocks when additional parameters are needed. + */ + struct GetBucketParams { + google::storage::v2::GetBucketRequest request; + Options options; + }; + + /// Get bucket metadata. + virtual future> GetBucket( + GetBucketParams p) = 0; + /** * A thin wrapper around the `InsertObject()` parameters. * diff --git a/google/cloud/storage/async/idempotency_policy.cc b/google/cloud/storage/async/idempotency_policy.cc index b39a58d0bb06a..68063409ce76e 100644 --- a/google/cloud/storage/async/idempotency_policy.cc +++ b/google/cloud/storage/async/idempotency_policy.cc @@ -35,6 +35,11 @@ class AlwaysRetryAsyncIdempotencyPolicy : public AsyncIdempotencyPolicy { return Idempotency::kIdempotent; } + google::cloud::Idempotency GetBucket( + google::storage::v2::GetBucketRequest const&) override { + return Idempotency::kIdempotent; + } + google::cloud::Idempotency InsertObject( google::storage::v2::WriteObjectRequest const&) override { return Idempotency::kIdempotent; @@ -71,6 +76,12 @@ google::cloud::Idempotency AsyncIdempotencyPolicy::ReadObject( return Idempotency::kIdempotent; } +google::cloud::Idempotency AsyncIdempotencyPolicy::GetBucket( + google::storage::v2::GetBucketRequest const&) { + // Read operations are always idempotent. + return Idempotency::kIdempotent; +} + google::cloud::Idempotency AsyncIdempotencyPolicy::InsertObject( google::storage::v2::WriteObjectRequest const& request) { auto const& spec = request.write_object_spec(); diff --git a/google/cloud/storage/async/idempotency_policy.h b/google/cloud/storage/async/idempotency_policy.h index 6f4b721ae1296..f4bae6989d71e 100644 --- a/google/cloud/storage/async/idempotency_policy.h +++ b/google/cloud/storage/async/idempotency_policy.h @@ -61,6 +61,10 @@ class AsyncIdempotencyPolicy { virtual google::cloud::Idempotency ReadObject( google::storage::v2::ReadObjectRequest const&); + /// Determine if a google.storage.v2.GetBucketRequest is idempotent. + virtual google::cloud::Idempotency GetBucket( + google::storage::v2::GetBucketRequest const&); + /// Determine if a google.storage.v2.WriteObjectRequest for a one-shot upload /// is idempotent. virtual google::cloud::Idempotency InsertObject( diff --git a/google/cloud/storage/async/idempotency_policy_test.cc b/google/cloud/storage/async/idempotency_policy_test.cc index 26f396165bda5..039fd60750dd9 100644 --- a/google/cloud/storage/async/idempotency_policy_test.cc +++ b/google/cloud/storage/async/idempotency_policy_test.cc @@ -37,6 +37,8 @@ TEST(IdempotencyPolicy, Strict) { EXPECT_EQ(policy->ReadObject(google::storage::v2::ReadObjectRequest{}), Idempotency::kIdempotent); + EXPECT_EQ(policy->GetBucket(google::storage::v2::GetBucketRequest{}), + Idempotency::kIdempotent); EXPECT_EQ(policy->InsertObject(google::storage::v2::WriteObjectRequest{}), Idempotency::kNonIdempotent); @@ -90,6 +92,8 @@ TEST(IdempotencyPolicy, AlwaysRetry) { ASSERT_THAT(policy, NotNull()); EXPECT_EQ(policy->ReadObject(google::storage::v2::ReadObjectRequest{}), Idempotency::kIdempotent); + EXPECT_EQ(policy->GetBucket(google::storage::v2::GetBucketRequest{}), + Idempotency::kIdempotent); EXPECT_EQ(policy->InsertObject(google::storage::v2::WriteObjectRequest{}), Idempotency::kIdempotent); EXPECT_EQ(policy->WriteObject(google::storage::v2::WriteObjectRequest{}), diff --git a/google/cloud/storage/examples/storage_async_samples.cc b/google/cloud/storage/examples/storage_async_samples.cc index 8bf4933c483b5..902855efcdfe4 100644 --- a/google/cloud/storage/examples/storage_async_samples.cc +++ b/google/cloud/storage/examples/storage_async_samples.cc @@ -17,6 +17,7 @@ // in the final rendering. //! [async-includes] #include "google/cloud/storage/async/client.h" +#include "google/cloud/storage/async/options.h" #include "google/cloud/storage/async/read_all.h" //! [async-includes] @@ -1171,6 +1172,24 @@ void ComposeObjectRequest(google::cloud::storage::AsyncClient& client, argv.at(4)); } +void GetBucket(google::cloud::storage::AsyncClient& client, + std::vector const& argv) { + //! [get-bucket] + namespace gcs = google::cloud::storage; + [](gcs::AsyncClient& client, std::string bucket_name) { + client.GetBucket(gcs::BucketName(std::move(bucket_name))) + .then([](auto f) { + auto metadata = f.get(); + if (!metadata) throw std::move(metadata).status(); + std::cout << "Bucket metadata successfully fetched: " + << metadata->DebugString() << "\n"; + }) + .get(); + } + //! [get-bucket] + (client, argv.at(0)); +} + // We would like to call this function `DeleteObject()`, but that conflicts with // a global `DeleteObject()` function on Windows. void AsyncDeleteObject(google::cloud::storage::AsyncClient& client, @@ -1235,6 +1254,9 @@ void AutoRun(std::vector const& argv) { auto client = google::cloud::storage::AsyncClient(); + std::cout << "Running GetBucket() example" << std::endl; + GetBucket(client, {bucket_name}); + // We need different object names because writing to the same object within // a second exceeds the service's quota. auto object_name = examples::MakeRandomObjectName(generator, "object-"); @@ -1509,9 +1531,30 @@ int main(int argc, char* argv[]) try { return {name, std::move(adapter)}; }; + auto make_bucket_entry = + [](std::string const& name, std::vector arg_names, + Command const& command) -> examples::Commands::value_type { + arg_names.insert(arg_names.begin(), ""); + auto adapter = [=](std::vector const& argv) { + if (argv.size() != arg_names.size() || + (!argv.empty() && argv[0] == "--help")) { + std::ostringstream os; + os << name; + for (auto const& a : arg_names) { + os << " " << a; + } + throw examples::Usage{std::move(os).str()}; + } + auto client = google::cloud::storage::AsyncClient(); + command(client, argv); + }; + return {name, std::move(adapter)}; + }; + examples::Example example({ {"create-client", CreateClientCommand}, {"create-client-with-dp", CreateClientWithDPCommand}, + make_bucket_entry("get-bucket", {}, GetBucket), make_entry("insert-object", {}, InsertObject), make_entry("insert-object-vector", {}, InsertObjectVector), make_entry("insert-object-vector-strings", {}, InsertObjectVectorStrings), diff --git a/google/cloud/storage/internal/async/connection_impl.cc b/google/cloud/storage/internal/async/connection_impl.cc index b1622bd87a386..25e3d1556379a 100644 --- a/google/cloud/storage/internal/async/connection_impl.cc +++ b/google/cloud/storage/internal/async/connection_impl.cc @@ -145,6 +145,27 @@ AsyncConnectionImpl::AsyncConnectionImpl( stub_(std::move(stub)), options_(std::move(options)) {} +future> AsyncConnectionImpl::GetBucket( + GetBucketParams p) { + auto current = internal::MakeImmutableOptions(std::move(p.options)); + auto request = std::move(p.request); + auto const idempotency = idempotency_policy(*current)->GetBucket(request); + + auto call = [stub = stub_]( + CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) { + return stub->AsyncGetBucket(cq, std::move(context), std::move(options), + request); + }; + auto retry = retry_policy(*current); + auto backoff = backoff_policy(*current); + return google::cloud::internal::AsyncRetryLoop( + std::move(retry), std::move(backoff), idempotency, cq_, std::move(call), + std::move(current), std::move(request), __func__); +} + future> AsyncConnectionImpl::InsertObject( InsertObjectParams p) { auto current = internal::MakeImmutableOptions(std::move(p.options)); diff --git a/google/cloud/storage/internal/async/connection_impl.h b/google/cloud/storage/internal/async/connection_impl.h index f52750caa427c..2d2cef79fcebe 100644 --- a/google/cloud/storage/internal/async/connection_impl.h +++ b/google/cloud/storage/internal/async/connection_impl.h @@ -57,6 +57,9 @@ class AsyncConnectionImpl Options options() const override { return options_; } + future> GetBucket( + GetBucketParams p) override; + future> InsertObject( InsertObjectParams p) override; diff --git a/google/cloud/storage/internal/async/connection_impl_test.cc b/google/cloud/storage/internal/async/connection_impl_test.cc index 741e1ec92905e..6f47a8adcfd5a 100644 --- a/google/cloud/storage/internal/async/connection_impl_test.cc +++ b/google/cloud/storage/internal/async/connection_impl_test.cc @@ -84,6 +84,62 @@ std::shared_ptr MakeTestConnection( TestOptions(std::move(options))); } +TEST_F(AsyncConnectionImplTest, GetBucket) { + auto constexpr kExpectedRequest = R"pb( + name: "projects/_/buckets/test-bucket" + )pb"; + auto constexpr kExpectedBucket = R"pb( + name: "projects/_/buckets/test-bucket" + )pb"; + + AsyncSequencer sequencer; + auto mock = std::make_shared(); + EXPECT_CALL(*mock, AsyncGetBucket) + .WillOnce([&] { + return sequencer.PushBack("GetBucket(1)").then([](auto) { + return StatusOr(TransientError()); + }); + }) + .WillOnce([&](CompletionQueue&, + std::shared_ptr const& context, + google::cloud::internal::ImmutableOptions const& options, + google::storage::v2::GetBucketRequest const& request) { + EXPECT_THAT(GetMetadata(*context), + testing::Not(testing::Contains( + testing::Key("x-goog-gcs-idempotency-token")))); + EXPECT_EQ(options->get(), kAuthority); + auto expected = google::storage::v2::GetBucketRequest{}; + EXPECT_TRUE(TextFormat::ParseFromString(kExpectedRequest, &expected)); + EXPECT_THAT(request, IsProtoEqual(expected)); + return sequencer.PushBack("GetBucket(2)").then([&](auto) { + auto result = google::storage::v2::Bucket{}; + EXPECT_TRUE(TextFormat::ParseFromString(kExpectedBucket, &result)); + return make_status_or(std::move(result)); + }); + }); + + internal::AutomaticallyCreatedBackgroundThreads pool(1); + auto connection = MakeTestConnection(pool.cq(), mock); + auto request = google::storage::v2::GetBucketRequest{}; + EXPECT_TRUE(TextFormat::ParseFromString(kExpectedRequest, &request)); + auto pending = + connection->GetBucket({std::move(request), connection->options()}); + + auto next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "GetBucket(1)"); + next.first.set_value(false); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "GetBucket(2)"); + next.first.set_value(true); + + auto expected = google::storage::v2::Bucket{}; + EXPECT_TRUE(TextFormat::ParseFromString(kExpectedBucket, &expected)); + auto response = pending.get(); + ASSERT_STATUS_OK(response); + EXPECT_THAT(*response, IsProtoEqual(expected)); +} + TEST_F(AsyncConnectionImplTest, ComposeObject) { auto constexpr kExpectedRequest = R"pb( destination { bucket: "projects/_/buckets/test-bucket" name: "test-object" } diff --git a/google/cloud/storage/internal/async/connection_tracing.cc b/google/cloud/storage/internal/async/connection_tracing.cc index a3c6c8b1e531e..2e29660276078 100644 --- a/google/cloud/storage/internal/async/connection_tracing.cc +++ b/google/cloud/storage/internal/async/connection_tracing.cc @@ -37,6 +37,13 @@ class AsyncConnectionTracing : public storage::AsyncConnection { Options options() const override { return impl_->options(); } + future> GetBucket( + GetBucketParams p) override { + auto span = internal::MakeSpan("storage::AsyncConnection::GetBucket"); + internal::OTelScope scope(span); + return internal::EndSpan(std::move(span), impl_->GetBucket(std::move(p))); + } + future> InsertObject( InsertObjectParams p) override { auto span = internal::MakeSpan("storage::AsyncConnection::InsertObject"); diff --git a/google/cloud/storage/internal/storage_auth_decorator.cc b/google/cloud/storage/internal/storage_auth_decorator.cc index 53da6fc29644e..5e6d5666b30f3 100644 --- a/google/cloud/storage/internal/storage_auth_decorator.cc +++ b/google/cloud/storage/internal/storage_auth_decorator.cc @@ -265,6 +265,25 @@ StatusOr StorageAuth::MoveObject( return child_->MoveObject(context, options, request); } +future> StorageAuth::AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) { + return auth_->AsyncConfigureContext(std::move(context)) + .then([cq, child = child_, options = std::move(options), + request](future>> + f) mutable { + auto context = f.get(); + if (!context) { + return make_ready_future(StatusOr( + std::move(context).status())); + } + return child->AsyncGetBucket(cq, *std::move(context), + std::move(options), request); + }); +} + future> StorageAuth::AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, diff --git a/google/cloud/storage/internal/storage_auth_decorator.h b/google/cloud/storage/internal/storage_auth_decorator.h index c0a8225164980..d1723d099ee36 100644 --- a/google/cloud/storage/internal/storage_auth_decorator.h +++ b/google/cloud/storage/internal/storage_auth_decorator.h @@ -152,6 +152,12 @@ class StorageAuth : public StorageStub { grpc::ClientContext& context, Options const& options, google::storage::v2::MoveObjectRequest const& request) override; + future> AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) override; + future> AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, diff --git a/google/cloud/storage/internal/storage_logging_decorator.cc b/google/cloud/storage/internal/storage_logging_decorator.cc index 2d522fd8cf244..26a62296cd4a1 100644 --- a/google/cloud/storage/internal/storage_logging_decorator.cc +++ b/google/cloud/storage/internal/storage_logging_decorator.cc @@ -359,6 +359,23 @@ StatusOr StorageLogging::MoveObject( context, options, request, __func__, tracing_options_); } +future> StorageLogging::AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) { + return google::cloud::internal::LogWrapper( + [this](google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) { + return child_->AsyncGetBucket(cq, std::move(context), + std::move(options), request); + }, + cq, std::move(context), std::move(options), request, __func__, + tracing_options_); +} + future> StorageLogging::AsyncComposeObject( google::cloud::CompletionQueue& cq, diff --git a/google/cloud/storage/internal/storage_logging_decorator.h b/google/cloud/storage/internal/storage_logging_decorator.h index 423ca1fcbb141..a1d67c396c47f 100644 --- a/google/cloud/storage/internal/storage_logging_decorator.h +++ b/google/cloud/storage/internal/storage_logging_decorator.h @@ -152,6 +152,12 @@ class StorageLogging : public StorageStub { grpc::ClientContext& context, Options const& options, google::storage::v2::MoveObjectRequest const& request) override; + future> AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) override; + future> AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, diff --git a/google/cloud/storage/internal/storage_metadata_decorator.cc b/google/cloud/storage/internal/storage_metadata_decorator.cc index 3d3ccc9d394a2..c6297e57c25f5 100644 --- a/google/cloud/storage/internal/storage_metadata_decorator.cc +++ b/google/cloud/storage/internal/storage_metadata_decorator.cc @@ -550,6 +550,28 @@ StatusOr StorageMetadata::MoveObject( return child_->MoveObject(context, options, request); } +future> StorageMetadata::AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) { + std::vector params; + params.reserve(1); + + if (!request.name().empty()) { + params.push_back( + absl::StrCat("bucket=", internal::UrlEncode(request.name()))); + } + + if (params.empty()) { + SetMetadata(*context, *options); + } else { + SetMetadata(*context, *options, absl::StrJoin(params, "&")); + } + return child_->AsyncGetBucket(cq, std::move(context), std::move(options), + request); +} + future> StorageMetadata::AsyncComposeObject( google::cloud::CompletionQueue& cq, diff --git a/google/cloud/storage/internal/storage_metadata_decorator.h b/google/cloud/storage/internal/storage_metadata_decorator.h index 17d1589fd1f58..9fd4bca1ee047 100644 --- a/google/cloud/storage/internal/storage_metadata_decorator.h +++ b/google/cloud/storage/internal/storage_metadata_decorator.h @@ -152,6 +152,12 @@ class StorageMetadata : public StorageStub { grpc::ClientContext& context, Options const& options, google::storage::v2::MoveObjectRequest const& request) override; + future> AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) override; + future> AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, diff --git a/google/cloud/storage/internal/storage_round_robin_decorator.cc b/google/cloud/storage/internal/storage_round_robin_decorator.cc index 97651673f61ae..5a356dd1fb1b3 100644 --- a/google/cloud/storage/internal/storage_round_robin_decorator.cc +++ b/google/cloud/storage/internal/storage_round_robin_decorator.cc @@ -198,6 +198,15 @@ StatusOr StorageRoundRobin::MoveObject( return Child()->MoveObject(context, options, request); } +future> StorageRoundRobin::AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) { + return Child()->AsyncGetBucket(cq, std::move(context), std::move(options), + request); +} + future> StorageRoundRobin::AsyncComposeObject( google::cloud::CompletionQueue& cq, diff --git a/google/cloud/storage/internal/storage_round_robin_decorator.h b/google/cloud/storage/internal/storage_round_robin_decorator.h index 714622e727e7c..f2bbcdb3e70d2 100644 --- a/google/cloud/storage/internal/storage_round_robin_decorator.h +++ b/google/cloud/storage/internal/storage_round_robin_decorator.h @@ -150,6 +150,12 @@ class StorageRoundRobin : public StorageStub { grpc::ClientContext& context, Options const& options, google::storage::v2::MoveObjectRequest const& request) override; + future> AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) override; + future> AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, diff --git a/google/cloud/storage/internal/storage_stub.cc b/google/cloud/storage/internal/storage_stub.cc index 70ceb62cdfba5..2c96e50a8f966 100644 --- a/google/cloud/storage/internal/storage_stub.cc +++ b/google/cloud/storage/internal/storage_stub.cc @@ -322,6 +322,24 @@ StatusOr DefaultStorageStub::MoveObject( return response; } +future> +DefaultStorageStub::AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + // NOLINTNEXTLINE(performance-unnecessary-value-param) + google::cloud::internal::ImmutableOptions, + google::storage::v2::GetBucketRequest const& request) { + return internal::MakeUnaryRpcImpl( + cq, + [this](grpc::ClientContext* context, + google::storage::v2::GetBucketRequest const& request, + grpc::CompletionQueue* cq) { + return grpc_stub_->AsyncGetBucket(context, request, cq); + }, + request, std::move(context)); +} + future> DefaultStorageStub::AsyncComposeObject( google::cloud::CompletionQueue& cq, diff --git a/google/cloud/storage/internal/storage_stub.h b/google/cloud/storage/internal/storage_stub.h index 135f474739207..bd5bb08a451c7 100644 --- a/google/cloud/storage/internal/storage_stub.h +++ b/google/cloud/storage/internal/storage_stub.h @@ -155,6 +155,12 @@ class StorageStub { grpc::ClientContext& context, Options const& options, google::storage::v2::MoveObjectRequest const& request) = 0; + virtual future> AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) = 0; + virtual future> AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, @@ -320,6 +326,12 @@ class DefaultStorageStub : public StorageStub { grpc::ClientContext& context, Options const& options, google::storage::v2::MoveObjectRequest const& request) override; + future> AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) override; + future> AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, diff --git a/google/cloud/storage/internal/storage_tracing_stub.cc b/google/cloud/storage/internal/storage_tracing_stub.cc index 09043577736f5..208ceefbec474 100644 --- a/google/cloud/storage/internal/storage_tracing_stub.cc +++ b/google/cloud/storage/internal/storage_tracing_stub.cc @@ -327,6 +327,19 @@ StatusOr StorageTracingStub::MoveObject( child_->MoveObject(context, options, request)); } +future> +StorageTracingStub::AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) { + auto span = internal::MakeSpanGrpc("google.storage.v2.Storage", "GetBucket"); + internal::OTelScope scope(span); + internal::InjectTraceContext(*context, *propagator_); + auto f = child_->AsyncGetBucket(cq, context, std::move(options), request); + return internal::EndSpan(std::move(context), std::move(span), std::move(f)); +} + future> StorageTracingStub::AsyncComposeObject( google::cloud::CompletionQueue& cq, diff --git a/google/cloud/storage/internal/storage_tracing_stub.h b/google/cloud/storage/internal/storage_tracing_stub.h index 74e639e517b75..6ba76e714de22 100644 --- a/google/cloud/storage/internal/storage_tracing_stub.h +++ b/google/cloud/storage/internal/storage_tracing_stub.h @@ -150,6 +150,12 @@ class StorageTracingStub : public StorageStub { grpc::ClientContext& context, Options const& options, google::storage::v2::MoveObjectRequest const& request) override; + future> AsyncGetBucket( + google::cloud::CompletionQueue& cq, + std::shared_ptr context, + google::cloud::internal::ImmutableOptions options, + google::storage::v2::GetBucketRequest const& request) override; + future> AsyncComposeObject( google::cloud::CompletionQueue& cq, std::shared_ptr context, diff --git a/google/cloud/storage/mocks/mock_async_connection.h b/google/cloud/storage/mocks/mock_async_connection.h index 44610ba56142f..fc41441916491 100644 --- a/google/cloud/storage/mocks/mock_async_connection.h +++ b/google/cloud/storage/mocks/mock_async_connection.h @@ -32,6 +32,8 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN class MockAsyncConnection : public storage::AsyncConnection { public: MOCK_METHOD(Options, options, (), (const, override)); + MOCK_METHOD(future>, GetBucket, + (GetBucketParams), (override)); MOCK_METHOD(future>, InsertObject, (InsertObjectParams), (override)); MOCK_METHOD( diff --git a/google/cloud/storage/testing/mock_storage_stub.h b/google/cloud/storage/testing/mock_storage_stub.h index 55f1e22d2c9d3..1ed211691534c 100644 --- a/google/cloud/storage/testing/mock_storage_stub.h +++ b/google/cloud/storage/testing/mock_storage_stub.h @@ -152,6 +152,12 @@ class MockStorageStub : public storage_internal::StorageStub { google::cloud::internal::ImmutableOptions, google::storage::v2::ComposeObjectRequest const&), (override)); + MOCK_METHOD(future>, AsyncGetBucket, + (google::cloud::CompletionQueue&, + std::shared_ptr, + google::cloud::internal::ImmutableOptions, + google::storage::v2::GetBucketRequest const&), + (override)); MOCK_METHOD(future, AsyncDeleteObject, (google::cloud::CompletionQueue&, std::shared_ptr, diff --git a/google/cloud/storage/tests/async_client_integration_test.cc b/google/cloud/storage/tests/async_client_integration_test.cc index e383aeee30f14..144c055d9c71f 100644 --- a/google/cloud/storage/tests/async_client_integration_test.cc +++ b/google/cloud/storage/tests/async_client_integration_test.cc @@ -146,6 +146,14 @@ TEST_F(AsyncClientIntegrationTest, ObjectCRUD) { EXPECT_THAT(head, StatusIs(StatusCode::kNotFound)); } +TEST_F(AsyncClientIntegrationTest, GetBucket) { + auto async = AsyncClient(TestOptions()); + + auto bucket = async.GetBucket(BucketName(bucket_name()), AlwaysRetry()).get(); + ASSERT_STATUS_OK(bucket); + EXPECT_EQ(bucket->name(), BucketName(bucket_name()).FullName()); +} + TEST_F(AsyncClientIntegrationTest, ComposeObject) { auto async = AsyncClient(TestOptions()); auto o1 = MakeRandomObjectName(); @@ -680,6 +688,13 @@ TEST_F(AsyncClientIntegrationTest, DeleteObjectFailure) { ASSERT_THAT(deleted, Not(IsOk())); } +TEST_F(AsyncClientIntegrationTest, GetBucketFailure) { + auto async = AsyncClient(TestOptions()); + + auto bucket = async.GetBucket(BucketName(MakeRandomObjectName())).get(); + ASSERT_THAT(bucket, Not(IsOk())); +} + TEST_F(AsyncClientIntegrationTest, StartRewriteFailure) { auto async = AsyncClient(TestOptions());