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
Binary file modified ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions generator/generator_config.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -3905,6 +3905,7 @@ service {
gen_async_rpcs: [
"ComposeObject",
"DeleteObject",
"GetBucket",
"ReadObject",
"RewriteObject",
"StartResumableWrite",
Expand Down
14 changes: 14 additions & 0 deletions google/cloud/storage/async/client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ AsyncClient::AsyncClient(Options options) {
AsyncClient::AsyncClient(std::shared_ptr<AsyncConnection> connection)
: connection_(std::move(connection)) {}

future<StatusOr<google::storage::v2::Bucket>> 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<StatusOr<google::storage::v2::Bucket>> 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<StatusOr<google::storage::v2::Object>> AsyncClient::InsertObject(
google::storage::v2::WriteObjectRequest request, WritePayload contents,
Options opts) {
Expand Down
31 changes: 31 additions & 0 deletions google/cloud/storage/async/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<StatusOr<google::storage::v2::Bucket>> 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<StatusOr<google::storage::v2::Bucket>> GetBucket(
google::storage::v2::GetBucketRequest request, Options opts = {});

/*
This snippet discusses the tradeoffs between `InsertObject()`,
`StartBufferedUpload()`, and `StartUnbufferedUpload()`. The text is included
Expand Down
70 changes: 70 additions & 0 deletions google/cloud/storage/async/client_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<MockAsyncConnection>();
EXPECT_CALL(*mock, options)
.WillRepeatedly(
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));

EXPECT_CALL(*mock, GetBucket)
.WillOnce([&](AsyncConnection::GetBucketParams const& p) {
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
EXPECT_THAT(p.options.get<TestOption<2>>(), "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<TestOption<1>>("O1-function")
.set<TestOption<2>>("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<MockAsyncConnection>();
EXPECT_CALL(*mock, options)
.WillRepeatedly(
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));

EXPECT_CALL(*mock, GetBucket)
.WillOnce([&](AsyncConnection::GetBucketParams const& p) {
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
EXPECT_THAT(p.options.get<TestOption<2>>(), "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<TestOption<1>>("O1-function")
.set<TestOption<2>>("O2-function"))
.get();
ASSERT_STATUS_OK(response);
EXPECT_THAT(*response, IsProtoEqual(TestProtoBucket()));
}

TEST(AsyncClient, InsertObject1) {
auto constexpr kExpectedRequest = R"pb(
write_object_spec {
Expand Down
15 changes: 15 additions & 0 deletions google/cloud/storage/async/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<StatusOr<google::storage::v2::Bucket>> GetBucket(
GetBucketParams p) = 0;

/**
* A thin wrapper around the `InsertObject()` parameters.
*
Expand Down
11 changes: 11 additions & 0 deletions google/cloud/storage/async/idempotency_policy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.

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.

Please add a unit test for changes in this file.

return Idempotency::kIdempotent;
}

google::cloud::Idempotency AsyncIdempotencyPolicy::InsertObject(
google::storage::v2::WriteObjectRequest const& request) {
auto const& spec = request.write_object_spec();
Expand Down
4 changes: 4 additions & 0 deletions google/cloud/storage/async/idempotency_policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
43 changes: 43 additions & 0 deletions google/cloud/storage/examples/storage_async_samples.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -1171,6 +1172,24 @@ void ComposeObjectRequest(google::cloud::storage::AsyncClient& client,
argv.at(4));
}

void GetBucket(google::cloud::storage::AsyncClient& client,
std::vector<std::string> 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,
Expand Down Expand Up @@ -1235,6 +1254,9 @@ void AutoRun(std::vector<std::string> 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-");
Expand Down Expand Up @@ -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<std::string> arg_names,
Command const& command) -> examples::Commands::value_type {
arg_names.insert(arg_names.begin(), "<bucket-name>");
auto adapter = [=](std::vector<std::string> 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),
Expand Down
22 changes: 22 additions & 0 deletions google/cloud/storage/internal/async/connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,28 @@ AsyncConnectionImpl::AsyncConnectionImpl(
stub_(std::move(stub)),
options_(std::move(options)) {}

future<StatusOr<google::storage::v2::Bucket>> 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_, id = invocation_id_generator_.MakeInvocationId()](
CompletionQueue& cq,
std::shared_ptr<grpc::ClientContext> context,
google::cloud::internal::ImmutableOptions options,
google::storage::v2::GetBucketRequest const& request) {
AddIdempotencyToken(*context, id);

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.

These tokens are intended for mutating operations (like WriteObject, DeleteObject) to prevent duplicate execution on the server side. Read operations are naturally idempotent and do not need to generate or send this token.

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<StatusOr<google::storage::v2::Object>> AsyncConnectionImpl::InsertObject(
InsertObjectParams p) {
auto current = internal::MakeImmutableOptions(std::move(p.options));
Expand Down
3 changes: 3 additions & 0 deletions google/cloud/storage/internal/async/connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class AsyncConnectionImpl

Options options() const override { return options_; }

future<StatusOr<google::storage::v2::Bucket>> GetBucket(
GetBucketParams p) override;

future<StatusOr<google::storage::v2::Object>> InsertObject(
InsertObjectParams p) override;

Expand Down
57 changes: 57 additions & 0 deletions google/cloud/storage/internal/async/connection_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,63 @@ std::shared_ptr<storage::AsyncConnection> 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<bool> sequencer;
auto mock = std::make_shared<storage::testing::MockStorageStub>();
EXPECT_CALL(*mock, AsyncGetBucket)
.WillOnce([&] {
return sequencer.PushBack("GetBucket(1)").then([](auto) {
return StatusOr<google::storage::v2::Bucket>(TransientError());
});
})
.WillOnce([&](CompletionQueue&,
std::shared_ptr<grpc::ClientContext> const& context,
google::cloud::internal::ImmutableOptions const& options,
google::storage::v2::GetBucketRequest const& request) {
EXPECT_THAT(
GetMetadata(*context),
testing::Contains(testing::Pair("x-goog-gcs-idempotency-token",
testing::Not(testing::IsEmpty()))));
EXPECT_EQ(options->get<AuthorityOption>(), 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" }
Expand Down
7 changes: 7 additions & 0 deletions google/cloud/storage/internal/async/connection_tracing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ class AsyncConnectionTracing : public storage::AsyncConnection {

Options options() const override { return impl_->options(); }

future<StatusOr<google::storage::v2::Bucket>> 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<StatusOr<google::storage::v2::Object>> InsertObject(
InsertObjectParams p) override {
auto span = internal::MakeSpan("storage::AsyncConnection::InsertObject");
Expand Down
Loading
Loading