Skip to content

Commit 9bb18b8

Browse files
feat(storage): Create OTel tracing decorator for Client::UploadFile() (#15245)
1 parent e1de149 commit 9bb18b8

14 files changed

+578
-89
lines changed

google/cloud/storage/client.cc

Lines changed: 7 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -130,98 +130,17 @@ bool Client::UseSimpleUpload(std::string const& file_name, std::size_t& size) {
130130
StatusOr<ObjectMetadata> Client::UploadFileSimple(
131131
std::string const& file_name, std::size_t file_size,
132132
internal::InsertObjectMediaRequest request) {
133-
auto upload_offset = request.GetOption<UploadFromOffset>().value_or(0);
134-
if (file_size < upload_offset) {
135-
std::ostringstream os;
136-
os << __func__ << "(" << request << ", " << file_name
137-
<< "): UploadFromOffset (" << upload_offset
138-
<< ") is bigger than the size of file source (" << file_size << ")";
139-
return google::cloud::internal::InvalidArgumentError(std::move(os).str(),
140-
GCP_ERROR_INFO());
141-
}
142-
auto upload_size = (std::min)(
143-
request.GetOption<UploadLimit>().value_or(file_size - upload_offset),
144-
file_size - upload_offset);
145-
146-
std::ifstream is(file_name, std::ios::binary);
147-
if (!is.is_open()) {
148-
std::ostringstream os;
149-
os << __func__ << "(" << request << ", " << file_name
150-
<< "): cannot open upload file source";
151-
return google::cloud::internal::NotFoundError(std::move(os).str(),
152-
GCP_ERROR_INFO());
153-
}
154-
155-
std::string payload(static_cast<std::size_t>(upload_size), char{});
156-
is.seekg(upload_offset, std::ios::beg);
157-
// We need to use `&payload[0]` until C++17
158-
// NOLINTNEXTLINE(readability-container-data-pointer)
159-
is.read(&payload[0], payload.size());
160-
if (static_cast<std::size_t>(is.gcount()) < payload.size()) {
161-
std::ostringstream os;
162-
os << __func__ << "(" << request << ", " << file_name << "): Actual read ("
163-
<< is.gcount() << ") is smaller than upload_size (" << payload.size()
164-
<< ")";
165-
return google::cloud::internal::InternalError(std::move(os).str(),
166-
GCP_ERROR_INFO());
167-
}
168-
is.close();
169-
request.set_payload(payload);
170-
133+
auto payload = connection_->UploadFileSimple(file_name, file_size, request);
134+
if (!payload) return payload.status();
135+
request.set_payload(*payload.value());
171136
return connection_->InsertObjectMedia(request);
172137
}
173138

174139
StatusOr<ObjectMetadata> Client::UploadFileResumable(
175-
std::string const& file_name,
176-
google::cloud::storage::internal::ResumableUploadRequest request) {
177-
auto upload_offset = request.GetOption<UploadFromOffset>().value_or(0);
178-
auto status = google::cloud::internal::status(file_name);
179-
if (!is_regular(status)) {
180-
GCP_LOG(WARNING) << "Trying to upload " << file_name
181-
<< R"""( which is not a regular file.
182-
This is often a problem because:
183-
- Some non-regular files are infinite sources of data, and the load will
184-
never complete.
185-
- Some non-regular files can only be read once, and UploadFile() may need to
186-
read the file more than once to compute the checksum and hashes needed to
187-
preserve data integrity.
188-
189-
Consider using UploadLimit option or Client::WriteObject(). You may also need to disable data
190-
integrity checks using the DisableMD5Hash() and DisableCrc32cChecksum() options.
191-
)""";
192-
} else {
193-
std::error_code size_err;
194-
auto file_size = google::cloud::internal::file_size(file_name, size_err);
195-
if (size_err) {
196-
return google::cloud::internal::NotFoundError(size_err.message(),
197-
GCP_ERROR_INFO());
198-
}
199-
if (file_size < upload_offset) {
200-
std::ostringstream os;
201-
os << __func__ << "(" << request << ", " << file_name
202-
<< "): UploadFromOffset (" << upload_offset
203-
<< ") is bigger than the size of file source (" << file_size << ")";
204-
return google::cloud::internal::InvalidArgumentError(std::move(os).str(),
205-
GCP_ERROR_INFO());
206-
}
207-
208-
auto upload_size = (std::min)(
209-
request.GetOption<UploadLimit>().value_or(file_size - upload_offset),
210-
file_size - upload_offset);
211-
request.set_option(UploadContentLength(upload_size));
212-
}
213-
std::ifstream source(file_name, std::ios::binary);
214-
if (!source.is_open()) {
215-
std::ostringstream os;
216-
os << __func__ << "(" << request << ", " << file_name
217-
<< "): cannot open upload file source";
218-
return google::cloud::internal::NotFoundError(std::move(os).str(),
219-
GCP_ERROR_INFO());
220-
}
221-
// We set its offset before passing it to `UploadStreamResumable` so we don't
222-
// need to compute `UploadFromOffset` again.
223-
source.seekg(upload_offset, std::ios::beg);
224-
return UploadStreamResumable(source, request);
140+
std::string const& file_name, internal::ResumableUploadRequest request) {
141+
auto source = connection_->UploadFileResumable(file_name, request);
142+
if (!source) return source.status();
143+
return UploadStreamResumable(*source.value(), request);
225144
}
226145

227146
StatusOr<ObjectMetadata> Client::UploadStreamResumable(

google/cloud/storage/client.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3530,7 +3530,7 @@ class Client {
35303530
internal::InsertObjectMediaRequest request(bucket_name, object_name,
35313531
std::string{});
35323532
request.set_multiple_options(std::forward<Options>(options)...);
3533-
return UploadFileSimple(file_name, file_size, request);
3533+
return UploadFileSimple(file_name, file_size, std::move(request));
35343534
}
35353535
internal::ResumableUploadRequest request(bucket_name, object_name);
35363536
request.set_multiple_options(std::forward<Options>(options)...);

google/cloud/storage/client_object_test.cc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,54 @@ TEST_F(ObjectTest, UploadFile) {
254254
EXPECT_EQ(expected, *actual);
255255
}
256256

257+
TEST_F(ObjectTest, UploadFileSimple) {
258+
std::string text = R"""({
259+
"name": "test-bucket-name/test-object-name/1"
260+
})""";
261+
ObjectMetadata expected =
262+
storage::internal::ObjectMetadataParser::FromString(text).value();
263+
std::string const contents = std::string{"some simple contents"};
264+
265+
EXPECT_CALL(*mock_, InsertObjectMedia)
266+
.WillOnce([&](internal::InsertObjectMediaRequest const& request) {
267+
EXPECT_EQ("test-bucket-name", request.bucket_name());
268+
EXPECT_EQ("test-object-name", request.object_name());
269+
EXPECT_EQ(contents, request.payload());
270+
return make_status_or(expected);
271+
});
272+
273+
TempFile temp(contents);
274+
auto client = ClientForMock();
275+
StatusOr<ObjectMetadata> actual =
276+
client.UploadFile(temp.name(), "test-bucket-name", "test-object-name");
277+
ASSERT_STATUS_OK(actual);
278+
EXPECT_EQ(expected, *actual);
279+
}
280+
281+
TEST_F(ObjectTest, UploadFileResumable) {
282+
std::string text = R"""({
283+
"name": "test-bucket-name/test-object-name/1"
284+
})""";
285+
ObjectMetadata expected =
286+
storage::internal::ObjectMetadataParser::FromString(text).value();
287+
std::string const contents = std::string{"some not so simple contents"};
288+
289+
EXPECT_CALL(*mock_, CreateResumableUpload)
290+
.WillOnce(
291+
Return(internal::CreateResumableUploadResponse{"test-upload-id"}));
292+
EXPECT_CALL(*mock_, UploadChunk)
293+
.WillOnce(Return(make_status_or(
294+
internal::QueryResumableUploadResponse{absl::nullopt, expected})));
295+
296+
TempFile temp(contents);
297+
auto client = ClientForMock();
298+
StatusOr<ObjectMetadata> actual =
299+
client.UploadFile(temp.name(), "test-bucket-name", "test-object-name",
300+
UseResumableUploadSession(""));
301+
ASSERT_STATUS_OK(actual);
302+
EXPECT_EQ(expected, *actual);
303+
}
304+
257305
TEST_F(ObjectTest, DeleteResumableUpload) {
258306
EXPECT_CALL(*mock_, DeleteResumableUpload)
259307
.WillOnce(Return(StatusOr<internal::EmptyResponse>(TransientError())))

google/cloud/storage/google_cloud_cpp_storage.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ if (BUILD_TESTING)
462462
internal/connection_impl_bucket_acl_test.cc
463463
internal/connection_impl_bucket_test.cc
464464
internal/connection_impl_default_object_acl_test.cc
465+
internal/connection_impl_file_upload_test.cc
465466
internal/connection_impl_notifications_test.cc
466467
internal/connection_impl_object_acl_test.cc
467468
internal/connection_impl_object_copy_test.cc
@@ -499,6 +500,7 @@ if (BUILD_TESTING)
499500
internal/service_account_requests_test.cc
500501
internal/sign_blob_requests_test.cc
501502
internal/signed_url_requests_test.cc
503+
internal/storage_connection_test.cc
502504
internal/tracing_connection_test.cc
503505
internal/tracing_object_read_source_test.cc
504506
internal/tuple_filter_test.cc

google/cloud/storage/internal/connection_impl.cc

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414

1515
#include "google/cloud/storage/internal/connection_impl.h"
1616
#include "google/cloud/storage/internal/retry_object_read_source.h"
17+
#include "google/cloud/internal/filesystem.h"
1718
#include "google/cloud/internal/opentelemetry.h"
1819
#include "google/cloud/internal/rest_retry_loop.h"
20+
#include "google/cloud/log.h"
1921
#include "absl/strings/match.h"
2022
#include <chrono>
23+
#include <fstream>
2124
#include <functional>
2225
#include <memory>
2326
#include <sstream>
@@ -754,6 +757,103 @@ StatusOr<QueryResumableUploadResponse> StorageConnectionImpl::UploadChunk(
754757
return RetryError(last_status, *retry_policy, __func__);
755758
}
756759

760+
StatusOr<std::unique_ptr<std::string>> StorageConnectionImpl::UploadFileSimple(
761+
std::string const& file_name, std::size_t file_size,
762+
InsertObjectMediaRequest& request) {
763+
auto upload_offset = request.GetOption<UploadFromOffset>().value_or(0);
764+
if (file_size < upload_offset) {
765+
std::ostringstream os;
766+
os << __func__ << "(" << request << ", " << file_name
767+
<< "): UploadFromOffset (" << upload_offset
768+
<< ") is bigger than the size of file source (" << file_size << ")";
769+
return google::cloud::internal::InvalidArgumentError(std::move(os).str(),
770+
GCP_ERROR_INFO());
771+
}
772+
auto upload_size = (std::min)(
773+
request.GetOption<UploadLimit>().value_or(file_size - upload_offset),
774+
file_size - upload_offset);
775+
776+
std::ifstream is(file_name, std::ios::binary);
777+
if (!is.is_open()) {
778+
std::ostringstream os;
779+
os << __func__ << "(" << request << ", " << file_name
780+
<< "): cannot open upload file source";
781+
return google::cloud::internal::NotFoundError(std::move(os).str(),
782+
GCP_ERROR_INFO());
783+
}
784+
785+
auto payload = std::make_unique<std::string>(
786+
static_cast<std::size_t>(upload_size), char{});
787+
is.seekg(upload_offset, std::ios::beg);
788+
// We need to use `&payload[0]` until C++17
789+
// NOLINTNEXTLINE(readability-container-data-pointer)
790+
is.read(&(*payload)[0], payload->size());
791+
if (static_cast<std::size_t>(is.gcount()) < payload->size()) {
792+
std::ostringstream os;
793+
os << __func__ << "(" << request << ", " << file_name << "): Actual read ("
794+
<< is.gcount() << ") is smaller than upload_size (" << payload->size()
795+
<< ")";
796+
return google::cloud::internal::InternalError(std::move(os).str(),
797+
GCP_ERROR_INFO());
798+
}
799+
is.close();
800+
801+
return payload;
802+
}
803+
804+
StatusOr<std::unique_ptr<std::istream>>
805+
StorageConnectionImpl::UploadFileResumable(std::string const& file_name,
806+
ResumableUploadRequest& request) {
807+
auto upload_offset = request.GetOption<UploadFromOffset>().value_or(0);
808+
auto status = google::cloud::internal::status(file_name);
809+
if (!is_regular(status)) {
810+
GCP_LOG(WARNING) << "Trying to upload " << file_name
811+
<< R"""( which is not a regular file.
812+
This is often a problem because:
813+
- Some non-regular files are infinite sources of data, and the load will
814+
never complete.
815+
- Some non-regular files can only be read once, and UploadFile() may need to
816+
read the file more than once to compute the checksum and hashes needed to
817+
preserve data integrity.
818+
819+
Consider using UploadLimit option or Client::WriteObject(). You may also need to disable data
820+
integrity checks using the DisableMD5Hash() and DisableCrc32cChecksum() options.
821+
)""";
822+
} else {
823+
std::error_code size_err;
824+
auto file_size = google::cloud::internal::file_size(file_name, size_err);
825+
if (size_err) {
826+
return google::cloud::internal::NotFoundError(size_err.message(),
827+
GCP_ERROR_INFO());
828+
}
829+
if (file_size < upload_offset) {
830+
std::ostringstream os;
831+
os << __func__ << "(" << request << ", " << file_name
832+
<< "): UploadFromOffset (" << upload_offset
833+
<< ") is bigger than the size of file source (" << file_size << ")";
834+
return google::cloud::internal::InvalidArgumentError(std::move(os).str(),
835+
GCP_ERROR_INFO());
836+
}
837+
838+
auto upload_size = (std::min)(
839+
request.GetOption<UploadLimit>().value_or(file_size - upload_offset),
840+
file_size - upload_offset);
841+
request.set_option(UploadContentLength(upload_size));
842+
}
843+
auto source = std::make_unique<std::ifstream>(file_name, std::ios::binary);
844+
if (!source->is_open()) {
845+
std::ostringstream os;
846+
os << __func__ << "(" << request << ", " << file_name
847+
<< "): cannot open upload file source";
848+
return google::cloud::internal::NotFoundError(std::move(os).str(),
849+
GCP_ERROR_INFO());
850+
}
851+
// We set its offset before passing it to `UploadStreamResumable` so we don't
852+
// need to compute `UploadFromOffset` again.
853+
source->seekg(upload_offset, std::ios::beg);
854+
return std::unique_ptr<std::istream>(std::move(source));
855+
}
856+
757857
StatusOr<ListBucketAclResponse> StorageConnectionImpl::ListBucketAcl(
758858
ListBucketAclRequest const& request) {
759859
auto const idempotency = current_idempotency_policy().IsIdempotent(request)

google/cloud/storage/internal/connection_impl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ class StorageConnectionImpl
9999
DeleteResumableUploadRequest const& request) override;
100100
StatusOr<QueryResumableUploadResponse> UploadChunk(
101101
UploadChunkRequest const& request) override;
102+
StatusOr<std::unique_ptr<std::string>> UploadFileSimple(
103+
std::string const& file_name, std::size_t file_size,
104+
InsertObjectMediaRequest& request) override;
105+
StatusOr<std::unique_ptr<std::istream>> UploadFileResumable(
106+
std::string const& file_name, ResumableUploadRequest& request) override;
102107

103108
StatusOr<ListBucketAclResponse> ListBucketAcl(
104109
ListBucketAclRequest const& request) override;

0 commit comments

Comments
 (0)