aboutsummaryrefslogtreecommitdiff
path: root/src/zenutil/cloud/minioprocess.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-18 11:27:07 +0100
committerGitHub Enterprise <[email protected]>2026-03-18 11:27:07 +0100
commite64d76ae1b6993582bf161a61049f0771414a779 (patch)
tree083f3df42cc9e2c7ddbee225708b4848eb217d11 /src/zenutil/cloud/minioprocess.cpp
parentCompute batching (#849) (diff)
downloadzen-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.cpp174
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