diff options
| author | Stefan Boberg <[email protected]> | 2026-03-18 11:27:07 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-18 11:27:07 +0100 |
| commit | e64d76ae1b6993582bf161a61049f0771414a779 (patch) | |
| tree | 083f3df42cc9e2c7ddbee225708b4848eb217d11 /src/zenutil/cloud/minioprocess.cpp | |
| parent | Compute batching (#849) (diff) | |
| download | zen-e64d76ae1b6993582bf161a61049f0771414a779.tar.xz zen-e64d76ae1b6993582bf161a61049f0771414a779.zip | |
Simple S3 client (#836)
This functionality is intended to be used to manage datasets for test cases, but may be useful elsewhere in the future.
- **Add S3 client with AWS Signature V4 (SigV4) signing** — new `S3Client` in `zenutil/cloud/` supporting `GetObject`, `PutObject`, `DeleteObject`, `HeadObject`, and `ListObjects` operations
- **Add EC2 IMDS credential provider** — automatically fetches and refreshes temporary AWS credentials from the EC2 Instance Metadata Service (IMDSv2) for use by the S3 client
- **Add SigV4 signing library** — standalone implementation of AWS Signature Version 4 request signing (headers and query-string presigning)
- **Add path-style addressing support** — enables compatibility with S3-compatible stores like MinIO (in addition to virtual-hosted style)
- **Add S3 integration tests** — includes a `MinioProcess` test helper that spins up a local MinIO server, plus integration tests exercising the S3 client end-to-end
- **Add S3-backed `HttpObjectStoreService` tests** — integration tests verifying the zenserver object store works against an S3 backend
- **Refactor mock IMDS into `zenutil/cloud/`** — moved and generalized the mock IMDS server from `zencompute` so it can be reused by both compute and S3 credential tests
Diffstat (limited to 'src/zenutil/cloud/minioprocess.cpp')
| -rw-r--r-- | src/zenutil/cloud/minioprocess.cpp | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/src/zenutil/cloud/minioprocess.cpp b/src/zenutil/cloud/minioprocess.cpp new file mode 100644 index 000000000..565705731 --- /dev/null +++ b/src/zenutil/cloud/minioprocess.cpp @@ -0,0 +1,174 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zenutil/cloud/minioprocess.h> + +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> +#include <zencore/logging.h> +#include <zencore/process.h> +#include <zencore/timer.h> +#include <zenhttp/httpclient.h> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <fmt/format.h> +ZEN_THIRD_PARTY_INCLUDES_END + +namespace zen { + +struct MinioProcess::Impl +{ + Impl(const MinioProcessOptions& Options) : m_Options(Options), m_HttpClient(fmt::format("http://localhost:{}/", Options.Port)) {} + ~Impl() = default; + + void SpawnMinioServer() + { + if (m_ProcessHandle.IsValid()) + { + return; + } + + // Create a clean temp data directory, removing any stale data from a previous run + std::error_code Ec; + m_DataDir = std::filesystem::temp_directory_path(Ec) / fmt::format("zen-minio-{}", GetCurrentProcessId()); + if (Ec) + { + ZEN_WARN("MinIO: Failed to get temp directory: {}", Ec.message()); + return; + } + std::filesystem::remove_all(m_DataDir, Ec); + Ec.clear(); + std::filesystem::create_directories(m_DataDir, Ec); + if (Ec) + { + ZEN_WARN("MinIO: Failed to create data directory '{}': {}", m_DataDir.string(), Ec.message()); + return; + } + + CreateProcOptions Options; + Options.Flags |= CreateProcOptions::Flag_Windows_NewProcessGroup; + Options.Environment.emplace_back("MINIO_ROOT_USER", m_Options.RootUser); + Options.Environment.emplace_back("MINIO_ROOT_PASSWORD", m_Options.RootPassword); + + const std::filesystem::path MinioExe = GetRunningExecutablePath().parent_path() / ("minio" ZEN_EXE_SUFFIX_LITERAL); + + std::string CommandLine = + fmt::format("minio" ZEN_EXE_SUFFIX_LITERAL " server {} --address :{} --quiet", m_DataDir.string(), m_Options.Port); + + CreateProcResult Result = CreateProc(MinioExe, CommandLine, Options); + + if (Result) + { + m_ProcessHandle.Initialize(Result); + + Stopwatch Timer; + + // Poll to check when the server is ready + do + { + Sleep(100); + HttpClient::Response Resp = m_HttpClient.Get("minio/health/live"); + if (Resp) + { + ZEN_INFO("MinIO server started successfully (waited {})", NiceTimeSpanMs(Timer.GetElapsedTimeMs())); + return; + } + } while (Timer.GetElapsedTimeMs() < 10000); + } + + // Report failure + ZEN_WARN("MinIO server failed to start within timeout period"); + } + + void StopMinioServer() + { + if (!m_ProcessHandle.IsValid()) + { + return; + } + + m_ProcessHandle.Kill(); + + // Clean up temp data directory + std::error_code Ec; + std::filesystem::remove_all(m_DataDir, Ec); + if (Ec) + { + ZEN_WARN("MinIO: Failed to clean up data directory '{}': {}", m_DataDir.string(), Ec.message()); + } + } + + void CreateBucket(std::string_view BucketName) + { + if (m_DataDir.empty()) + { + ZEN_WARN("MinIO: Cannot create bucket before data directory is initialized — call SpawnMinioServer() first"); + return; + } + + std::filesystem::path BucketDir = m_DataDir / std::string(BucketName); + std::error_code Ec; + std::filesystem::create_directories(BucketDir, Ec); + if (Ec) + { + ZEN_WARN("MinIO: Failed to create bucket directory '{}': {}", BucketDir.string(), Ec.message()); + } + } + + MinioProcessOptions m_Options; + ProcessHandle m_ProcessHandle; + HttpClient m_HttpClient; + std::filesystem::path m_DataDir; +}; + +MinioProcess::MinioProcess(const MinioProcessOptions& Options) : m_Impl(std::make_unique<Impl>(Options)) +{ +} + +MinioProcess::~MinioProcess() +{ + m_Impl->StopMinioServer(); +} + +void +MinioProcess::SpawnMinioServer() +{ + m_Impl->SpawnMinioServer(); +} + +void +MinioProcess::StopMinioServer() +{ + m_Impl->StopMinioServer(); +} + +void +MinioProcess::CreateBucket(std::string_view BucketName) +{ + m_Impl->CreateBucket(BucketName); +} + +uint16_t +MinioProcess::Port() const +{ + return m_Impl->m_Options.Port; +} + +std::string_view +MinioProcess::RootUser() const +{ + return m_Impl->m_Options.RootUser; +} + +std::string_view +MinioProcess::RootPassword() const +{ + return m_Impl->m_Options.RootPassword; +} + +std::string +MinioProcess::Endpoint() const +{ + return fmt::format("http://localhost:{}", m_Impl->m_Options.Port); +} + +} // namespace zen |