Skip to content

Commit 0faaa81

Browse files
authored
cleanup(GCS+gRPC): add public DeleteObjectRequest (#12523)
1 parent 5727177 commit 0faaa81

File tree

10 files changed

+190
-45
lines changed

10 files changed

+190
-45
lines changed

google/cloud/storage/async_client.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ class AsyncClient {
241241
*
242242
* @param bucket_name the name of the bucket that contains the object.
243243
* @param object_name the name of the object to be deleted.
244-
* @param options a list of optional query parameters and/or request headers.
244+
* @param args a list of optional query parameters and/or request headers.
245245
* Valid types for this operation include `Generation`,
246246
* `IfGenerationMatch`, `IfGenerationNotMatch`, `IfMetagenerationMatch`,
247247
* `IfMetagenerationNotMatch`, and `UserProject`.
@@ -255,15 +255,14 @@ class AsyncClient {
255255
* @par Example
256256
* @snippet storage_async_samples.cc delete-object
257257
*/
258-
template <typename... RequestOptions>
259-
future<Status> DeleteObject(std::string const& bucket_name,
260-
std::string const& object_name,
261-
RequestOptions&&... options) {
262-
google::cloud::internal::OptionsSpan const span(
263-
SpanOptions(std::forward<RequestOptions>(options)...));
264-
storage::internal::DeleteObjectRequest request(bucket_name, object_name);
265-
request.set_multiple_options(std::forward<RequestOptions>(options)...);
266-
return connection_->AsyncDeleteObject(std::move(request));
258+
template <typename... Args>
259+
future<Status> DeleteObject(std::string bucket_name, std::string object_name,
260+
Args&&... args) {
261+
auto options = SpanOptions(std::forward<Args>(args)...);
262+
return connection_->AsyncDeleteObject(
263+
{DeleteObjectRequest(std::move(bucket_name), std::move(object_name))
264+
.set_multiple_options(std::forward<Args>(args)...),
265+
/*.options=*/std::move(options)});
267266
}
268267

269268
/**

google/cloud/storage/async_object_requests.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_ASYNC_OBJECT_REQUESTS_H
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_ASYNC_OBJECT_REQUESTS_H
1717

18+
#include "google/cloud/storage/internal/async/connection_fwd.h"
1819
#include "google/cloud/storage/internal/async/write_payload_fwd.h"
1920
#include "google/cloud/storage/internal/object_requests.h"
2021
#include "google/cloud/storage/version.h"
@@ -110,6 +111,45 @@ class InsertObjectRequest {
110111
Impl impl_;
111112
};
112113

114+
/**
115+
* A request to delete an object.
116+
*
117+
* This class can hold all the mandatory and optional parameters to delete an
118+
* object. This class is the public API because it is required for mocking.
119+
*/
120+
class DeleteObjectRequest {
121+
public:
122+
DeleteObjectRequest() = default;
123+
DeleteObjectRequest(std::string bucket_name, std::string object_name)
124+
: impl_(std::move(bucket_name), std::move(object_name)) {}
125+
126+
std::string const& bucket_name() const { return impl_.bucket_name(); }
127+
std::string const& object_name() const { return impl_.object_name(); }
128+
129+
template <typename... O>
130+
DeleteObjectRequest& set_multiple_options(O&&... o) & {
131+
impl_.set_multiple_options(std::forward<O>(o)...);
132+
return *this;
133+
}
134+
template <typename... O>
135+
DeleteObjectRequest&& set_multiple_options(O&&... o) && {
136+
return std::move(set_multiple_options(std::forward<O>(o)...));
137+
}
138+
139+
template <typename O>
140+
bool HasOption() const {
141+
return impl_.HasOption<O>();
142+
}
143+
template <typename O>
144+
O GetOption() const {
145+
return impl_.GetOption<O>();
146+
}
147+
148+
protected:
149+
friend class storage_internal::AsyncConnectionImpl;
150+
storage::internal::DeleteObjectRequest impl_;
151+
};
152+
113153
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
114154
} // namespace storage_experimental
115155
} // namespace cloud

google/cloud/storage/examples/storage_async_samples.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ void AsyncDeleteObject(google::cloud::storage_experimental::AsyncClient& client,
147147
//! [delete-object]
148148
namespace g = google::cloud;
149149
namespace gcs_ex = google::cloud::storage_experimental;
150-
[](gcs_ex::AsyncClient& client, std::string const& bucket_name,
151-
std::string const& object_name) {
152-
client.DeleteObject(bucket_name, object_name)
150+
[](gcs_ex::AsyncClient& client, std::string bucket_name,
151+
std::string object_name) {
152+
client.DeleteObject(std::move(bucket_name), std::move(object_name))
153153
.then([](auto f) {
154154
auto status = f.get();
155155
if (!status.ok()) throw g::Status(std::move(status));

google/cloud/storage/google_cloud_cpp_storage_grpc.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ google_cloud_cpp_storage_grpc_hdrs = [
2424
"grpc_plugin.h",
2525
"internal/async/accumulate_read_object.h",
2626
"internal/async/connection.h",
27+
"internal/async/connection_fwd.h",
2728
"internal/async/connection_impl.h",
2829
"internal/async/insert_object.h",
2930
"internal/async/token_impl.h",

google/cloud/storage/google_cloud_cpp_storage_grpc.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ else ()
5050
internal/async/accumulate_read_object.cc
5151
internal/async/accumulate_read_object.h
5252
internal/async/connection.h
53+
internal/async/connection_fwd.h
5354
internal/async/connection_impl.cc
5455
internal/async/connection_impl.h
5556
internal/async/insert_object.cc

google/cloud/storage/internal/async/connection.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ class AsyncConnection {
6565
Options options;
6666
};
6767

68+
/**
69+
* A thin wrapper around the `DeleteObject()` parameters.
70+
*
71+
* We use a single struct as the input parameter for this function to
72+
* prevent breaking any mocks when additional parameters are needed.
73+
*/
74+
struct DeleteObjectParams {
75+
/// The metadata attributes to create the object.
76+
storage_experimental::DeleteObjectRequest request;
77+
/// Any options modifying the RPC behavior, including per-client and
78+
/// per-connection options.
79+
Options options;
80+
};
81+
6882
/// Insert a new object.
6983
virtual future<StatusOr<storage::ObjectMetadata>> AsyncInsertObject(
7084
InsertObjectParams p) = 0;
@@ -75,8 +89,8 @@ class AsyncConnection {
7589
virtual future<StatusOr<storage::ObjectMetadata>> AsyncComposeObject(
7690
storage::internal::ComposeObjectRequest request) = 0;
7791

78-
virtual future<Status> AsyncDeleteObject(
79-
storage::internal::DeleteObjectRequest request) = 0;
92+
// Delete an object.
93+
virtual future<Status> AsyncDeleteObject(DeleteObjectParams p) = 0;
8094

8195
virtual future<StatusOr<std::string>> AsyncStartResumableWrite(
8296
storage::internal::ResumableUploadRequest request) = 0;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_ASYNC_CONNECTION_FWD_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_ASYNC_CONNECTION_FWD_H
17+
18+
#include "google/cloud/version.h"
19+
20+
namespace google {
21+
namespace cloud {
22+
namespace storage_internal {
23+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
24+
25+
class AsyncConnectionImpl;
26+
27+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
28+
} // namespace storage_internal
29+
} // namespace cloud
30+
} // namespace google
31+
32+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_ASYNC_CONNECTION_FWD_H

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,19 +157,21 @@ AsyncConnectionImpl::AsyncComposeObject(
157157
});
158158
}
159159

160-
future<Status> AsyncConnectionImpl::AsyncDeleteObject(
161-
storage::internal::DeleteObjectRequest request) {
162-
auto current = google::cloud::internal::SaveCurrentOptions();
163-
auto proto = ToProto(request);
164-
auto const idempotency = idempotency_policy(*current)->IsIdempotent(request)
165-
? Idempotency::kIdempotent
166-
: Idempotency::kNonIdempotent;
160+
future<Status> AsyncConnectionImpl::AsyncDeleteObject(DeleteObjectParams p) {
161+
auto current = internal::MakeImmutableOptions(std::move(p.options));
162+
auto proto = ToProto(p.request.impl_);
163+
auto const idempotency =
164+
idempotency_policy(*current)->IsIdempotent(p.request.impl_)
165+
? Idempotency::kIdempotent
166+
: Idempotency::kNonIdempotent;
167167
return google::cloud::internal::AsyncRetryLoop(
168168
retry_policy(*current), backoff_policy(*current), idempotency, cq_,
169-
[stub = stub_, current, request = std::move(request)](
169+
[stub = stub_, current, request = std::move(p.request)](
170170
CompletionQueue& cq, std::shared_ptr<grpc::ClientContext> context,
171171
google::storage::v2::DeleteObjectRequest const& proto) {
172172
ApplyQueryParameters(*context, *current, request);
173+
// TODO(#12359) - pass the `options` parameter
174+
google::cloud::internal::OptionsSpan span(current);
173175
return stub->AsyncDeleteObject(cq, std::move(context), proto);
174176
},
175177
proto, __func__);

google/cloud/storage/internal/async/connection_impl.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ class AsyncConnectionImpl : public AsyncConnection {
5656
future<StatusOr<storage::ObjectMetadata>> AsyncComposeObject(
5757
storage::internal::ComposeObjectRequest request) override;
5858

59-
future<Status> AsyncDeleteObject(
60-
storage::internal::DeleteObjectRequest request) override;
59+
future<Status> AsyncDeleteObject(DeleteObjectParams p) override;
6160

6261
future<StatusOr<std::string>> AsyncStartResumableWrite(
6362
storage::internal::ResumableUploadRequest request) override;

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

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
3333
namespace {
3434

3535
using ::google::cloud::internal::CurrentOptions;
36-
using ::google::cloud::internal::OptionsSpan;
3736
using ::google::cloud::storage::testing::MockAsyncInsertStream;
3837
using ::google::cloud::storage::testing::MockStorageStub;
3938
using ::google::cloud::storage::testing::canonical_errors::PermanentError;
@@ -238,10 +237,15 @@ TEST_F(AsyncConnectionImplTest, AsyncInsertObjectTooManyTransients) {
238237
}
239238

240239
TEST_F(AsyncConnectionImplTest, AsyncDeleteObject) {
240+
AsyncSequencer<bool> sequencer;
241241
auto mock = std::make_shared<storage::testing::MockStorageStub>();
242242
EXPECT_CALL(*mock, AsyncDeleteObject)
243-
.WillOnce([this](
244-
CompletionQueue&, auto context,
243+
.WillOnce([&] {
244+
return sequencer.PushBack("DeleteObject(1)").then([](auto) {
245+
return TransientError();
246+
});
247+
})
248+
.WillOnce([&](CompletionQueue&, auto context,
245249
google::storage::v2::DeleteObjectRequest const& request) {
246250
// Verify at least one option is initialized with the correct
247251
// values.
@@ -252,24 +256,77 @@ TEST_F(AsyncConnectionImplTest, AsyncDeleteObject) {
252256
Pair("x-goog-fieldmask", "field1,field2")));
253257
EXPECT_THAT(request.bucket(), "projects/_/buckets/test-bucket");
254258
EXPECT_THAT(request.object(), "test-object");
255-
return make_ready_future(PermanentError());
259+
return sequencer.PushBack("DeleteObject(2)").then([](auto) {
260+
return Status{};
261+
});
256262
});
257-
CompletionQueue cq;
258-
auto connection = MakeTestConnection(cq, mock);
259-
// Simulate the option span created by the `*Client` class. The
260-
// `AuthorityOption` value is used by the `*Stub` classes. We want to verify
261-
// it is initialized properly by the time it makes it to the mocked call.
262-
OptionsSpan span(connection->options());
263-
auto response =
264-
connection
265-
->AsyncDeleteObject(
266-
storage::internal::DeleteObjectRequest("test-bucket",
267-
"test-object")
268-
.set_multiple_options(storage::Fields("field1,field2"),
269-
storage::QuotaUser("test-quota-user")))
270-
.get();
271-
EXPECT_THAT(response, StatusIs(PermanentError().code(),
272-
HasSubstr(PermanentError().message())));
263+
264+
internal::AutomaticallyCreatedBackgroundThreads pool(1);
265+
auto connection = MakeTestConnection(pool.cq(), mock);
266+
auto pending = connection->AsyncDeleteObject(
267+
{storage_experimental::DeleteObjectRequest("test-bucket", "test-object")
268+
.set_multiple_options(storage::Fields("field1,field2"),
269+
storage::QuotaUser("test-quota-user")),
270+
connection->options()});
271+
272+
auto next = sequencer.PopFrontWithName();
273+
EXPECT_EQ(next.second, "DeleteObject(1)");
274+
next.first.set_value(false);
275+
276+
next = sequencer.PopFrontWithName();
277+
EXPECT_EQ(next.second, "DeleteObject(2)");
278+
next.first.set_value(true);
279+
280+
auto response = pending.get();
281+
ASSERT_STATUS_OK(response);
282+
}
283+
284+
TEST_F(AsyncConnectionImplTest, AsyncDeleteObjectPermanentError) {
285+
AsyncSequencer<bool> sequencer;
286+
auto mock = std::make_shared<storage::testing::MockStorageStub>();
287+
EXPECT_CALL(*mock, AsyncDeleteObject).WillOnce([&] {
288+
return sequencer.PushBack("DeleteObject").then([](auto) {
289+
return PermanentError();
290+
});
291+
});
292+
293+
internal::AutomaticallyCreatedBackgroundThreads pool(1);
294+
auto connection = MakeTestConnection(pool.cq(), mock);
295+
auto pending = connection->AsyncDeleteObject(
296+
{storage_experimental::DeleteObjectRequest("test-bucket", "test-object"),
297+
connection->options()});
298+
299+
auto next = sequencer.PopFrontWithName();
300+
EXPECT_EQ(next.second, "DeleteObject");
301+
next.first.set_value(false);
302+
303+
auto response = pending.get();
304+
EXPECT_THAT(response, StatusIs(PermanentError().code()));
305+
}
306+
307+
TEST_F(AsyncConnectionImplTest, AsyncDeleteObjectTooManyTransients) {
308+
AsyncSequencer<bool> sequencer;
309+
auto mock = std::make_shared<storage::testing::MockStorageStub>();
310+
EXPECT_CALL(*mock, AsyncDeleteObject).Times(3).WillRepeatedly([&] {
311+
return sequencer.PushBack("DeleteObject").then([](auto) {
312+
return TransientError();
313+
});
314+
});
315+
316+
internal::AutomaticallyCreatedBackgroundThreads pool(1);
317+
auto connection = MakeTestConnection(pool.cq(), mock);
318+
auto pending = connection->AsyncDeleteObject(
319+
{storage_experimental::DeleteObjectRequest("test-bucket", "test-object"),
320+
connection->options()});
321+
322+
for (int i = 0; i != 3; ++i) {
323+
auto next = sequencer.PopFrontWithName();
324+
EXPECT_EQ(next.second, "DeleteObject");
325+
next.first.set_value(false);
326+
}
327+
328+
auto response = pending.get();
329+
EXPECT_THAT(response, StatusIs(TransientError().code()));
273330
}
274331

275332
} // namespace

0 commit comments

Comments
 (0)