aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/storage/storageconfig.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2025-10-14 11:32:16 +0200
committerGitHub Enterprise <[email protected]>2025-10-14 11:32:16 +0200
commitca09abbeef5b1788f4a52b61eedd2f3dd07f81f2 (patch)
tree005a50adfddf6982bab3a06bb93d4c50da1a11fd /src/zenserver/storage/storageconfig.cpp
parentmake asiohttp work without IPv6 (#562) (diff)
downloadzen-ca09abbeef5b1788f4a52b61eedd2f3dd07f81f2.tar.xz
zen-ca09abbeef5b1788f4a52b61eedd2f3dd07f81f2.zip
move all storage-related services into storage tree (#571)
* move all storage-related services into storage tree * move config into config/ * also move admin service into storage since it mostly has storage related functionality * header consolidation
Diffstat (limited to 'src/zenserver/storage/storageconfig.cpp')
-rw-r--r--src/zenserver/storage/storageconfig.cpp1055
1 files changed, 1055 insertions, 0 deletions
diff --git a/src/zenserver/storage/storageconfig.cpp b/src/zenserver/storage/storageconfig.cpp
new file mode 100644
index 000000000..86bb09c21
--- /dev/null
+++ b/src/zenserver/storage/storageconfig.cpp
@@ -0,0 +1,1055 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "storageconfig.h"
+
+#include <zencore/basicfile.h>
+#include <zencore/crypto.h>
+#include <zencore/except.h>
+#include <zencore/logging.h>
+
+#include "config/luaconfig.h"
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <fmt/format.h>
+#include <cxxopts.hpp>
+#include <json11.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+void
+ValidateOptions(ZenStorageServerOptions& ServerOptions)
+{
+ if (ServerOptions.EncryptionKey.empty() == false)
+ {
+ const auto Key = AesKey256Bit::FromString(ServerOptions.EncryptionKey);
+
+ if (Key.IsValid() == false)
+ {
+ throw OptionParseException(fmt::format("'--encryption-aes-key' ('{}') is malformed", ServerOptions.EncryptionKey), {});
+ }
+ }
+
+ if (ServerOptions.EncryptionIV.empty() == false)
+ {
+ const auto IV = AesIV128Bit::FromString(ServerOptions.EncryptionIV);
+
+ if (IV.IsValid() == false)
+ {
+ throw OptionParseException(fmt::format("'--encryption-aes-iv' ('{}') is malformed", ServerOptions.EncryptionIV), {});
+ }
+ }
+ if (ServerOptions.HttpServerConfig.ForceLoopback && ServerOptions.IsDedicated)
+ {
+ throw OptionParseException("'--dedicated' conflicts with '--http-forceloopback'", {});
+ }
+ if (ServerOptions.GcConfig.AttachmentPassCount > ZenGcConfig::GcMaxAttachmentPassCount)
+ {
+ throw OptionParseException(fmt::format("'--gc-attachment-passes' ('{}') is invalid, maximum is {}.",
+ ServerOptions.GcConfig.AttachmentPassCount,
+ ZenGcConfig::GcMaxAttachmentPassCount),
+ {});
+ }
+ if (ServerOptions.GcConfig.UseGCV2 == false)
+ {
+ ZEN_WARN("'--gc-v2=false' is deprecated, reverting to '--gc-v2=true'");
+ ServerOptions.GcConfig.UseGCV2 = true;
+ }
+}
+
+class ZenStructuredCacheBucketsConfigOption : public LuaConfig::OptionValue
+{
+public:
+ ZenStructuredCacheBucketsConfigOption(std::vector<std::pair<std::string, ZenStructuredCacheBucketConfig>>& Value) : Value(Value) {}
+ virtual void Print(std::string_view Indent, StringBuilderBase& StringBuilder) override
+ {
+ if (Value.empty())
+ {
+ StringBuilder.Append("{}");
+ return;
+ }
+ LuaConfig::LuaContainerWriter Writer(StringBuilder, Indent);
+ for (const std::pair<std::string, ZenStructuredCacheBucketConfig>& Bucket : Value)
+ {
+ Writer.BeginContainer("");
+ {
+ Writer.WriteValue("name", Bucket.first);
+ const ZenStructuredCacheBucketConfig& BucketConfig = Bucket.second;
+
+ Writer.WriteValue("maxblocksize", fmt::format("{}", BucketConfig.MaxBlockSize));
+ Writer.BeginContainer("memlayer");
+ {
+ Writer.WriteValue("sizethreshold", fmt::format("{}", BucketConfig.MemCacheSizeThreshold));
+ }
+ Writer.EndContainer();
+
+ Writer.WriteValue("payloadalignment", fmt::format("{}", BucketConfig.PayloadAlignment));
+ Writer.WriteValue("largeobjectthreshold", fmt::format("{}", BucketConfig.PayloadAlignment));
+ Writer.WriteValue("limitoverwrites", fmt::format("{}", BucketConfig.LimitOverwrites));
+ }
+ Writer.EndContainer();
+ }
+ }
+ virtual void Parse(sol::object Object) override
+ {
+ if (sol::optional<sol::table> Buckets = Object.as<sol::table>())
+ {
+ for (const auto& Kv : Buckets.value())
+ {
+ if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>())
+ {
+ ZenStructuredCacheBucketConfig BucketConfig;
+ std::string Name = Kv.first.as<std::string>();
+ if (Name.empty())
+ {
+ throw OptionParseException("Cache bucket option must have a name.", {});
+ }
+
+ const uint64_t MaxBlockSize = Bucket.value().get_or("maxblocksize", BucketConfig.MaxBlockSize);
+ if (MaxBlockSize == 0)
+ {
+ throw OptionParseException(
+ fmt::format("'maxblocksize' option for cache bucket '{}' is invalid. It must be non-zero.", Name),
+ {});
+ }
+ BucketConfig.MaxBlockSize = MaxBlockSize;
+
+ if (sol::optional<sol::table> Memlayer = Bucket.value().get_or("memlayer", sol::table()))
+ {
+ const uint64_t MemCacheSizeThreshold = Bucket.value().get_or("sizethreshold", BucketConfig.MemCacheSizeThreshold);
+ if (MemCacheSizeThreshold == 0)
+ {
+ throw OptionParseException(
+ fmt::format("'memlayer.sizethreshold' option for cache bucket '{}' is invalid. It must be non-zero.", Name),
+ {});
+ }
+ BucketConfig.MemCacheSizeThreshold = Bucket.value().get_or("sizethreshold", BucketConfig.MemCacheSizeThreshold);
+ }
+
+ const uint32_t PayloadAlignment = Bucket.value().get_or("payloadalignment", BucketConfig.PayloadAlignment);
+ if (PayloadAlignment == 0 || !IsPow2(PayloadAlignment))
+ {
+ throw OptionParseException(
+ fmt::format(
+ "'payloadalignment' option for cache bucket '{}' is invalid. It needs to be non-zero and a power of two.",
+ Name),
+ {});
+ }
+ BucketConfig.PayloadAlignment = PayloadAlignment;
+
+ const uint64_t LargeObjectThreshold = Bucket.value().get_or("largeobjectthreshold", BucketConfig.LargeObjectThreshold);
+ if (LargeObjectThreshold == 0)
+ {
+ throw OptionParseException(
+ fmt::format("'largeobjectthreshold' option for cache bucket '{}' is invalid. It must be non-zero.", Name),
+ {});
+ }
+ BucketConfig.LargeObjectThreshold = LargeObjectThreshold;
+
+ BucketConfig.LimitOverwrites = Bucket.value().get_or("limitoverwrites", BucketConfig.LimitOverwrites);
+
+ Value.push_back(std::make_pair(std::move(Name), BucketConfig));
+ }
+ }
+ }
+ }
+ std::vector<std::pair<std::string, ZenStructuredCacheBucketConfig>>& Value;
+};
+
+UpstreamCachePolicy
+ParseUpstreamCachePolicy(std::string_view Options)
+{
+ if (Options == "readonly")
+ {
+ return UpstreamCachePolicy::Read;
+ }
+ else if (Options == "writeonly")
+ {
+ return UpstreamCachePolicy::Write;
+ }
+ else if (Options == "disabled")
+ {
+ return UpstreamCachePolicy::Disabled;
+ }
+ else
+ {
+ return UpstreamCachePolicy::ReadWrite;
+ }
+}
+
+ZenObjectStoreConfig
+ParseBucketConfigs(std::span<std::string> Buckets)
+{
+ using namespace std::literals;
+
+ ZenObjectStoreConfig Cfg;
+
+ // split bucket args in the form of "{BucketName};{LocalPath}"
+ for (std::string_view Bucket : Buckets)
+ {
+ ZenObjectStoreConfig::BucketConfig NewBucket;
+
+ if (auto Idx = Bucket.find_first_of(";"); Idx != std::string_view::npos)
+ {
+ NewBucket.Name = Bucket.substr(0, Idx);
+ NewBucket.Directory = Bucket.substr(Idx + 1);
+ }
+ else
+ {
+ NewBucket.Name = Bucket;
+ }
+
+ Cfg.Buckets.push_back(std::move(NewBucket));
+ }
+
+ return Cfg;
+}
+
+class CachePolicyOption : public LuaConfig::OptionValue
+{
+public:
+ CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {}
+ virtual void Print(std::string_view, StringBuilderBase& StringBuilder) override
+ {
+ switch (Value)
+ {
+ case UpstreamCachePolicy::Read:
+ StringBuilder.Append("readonly");
+ break;
+ case UpstreamCachePolicy::Write:
+ StringBuilder.Append("writeonly");
+ break;
+ case UpstreamCachePolicy::Disabled:
+ StringBuilder.Append("disabled");
+ break;
+ case UpstreamCachePolicy::ReadWrite:
+ StringBuilder.Append("readwrite");
+ break;
+ default:
+ ZEN_ASSERT(false);
+ }
+ }
+ virtual void Parse(sol::object Object) override
+ {
+ std::string PolicyString = Object.as<std::string>();
+ if (PolicyString == "readonly")
+ {
+ Value = UpstreamCachePolicy::Read;
+ }
+ else if (PolicyString == "writeonly")
+ {
+ Value = UpstreamCachePolicy::Write;
+ }
+ else if (PolicyString == "disabled")
+ {
+ Value = UpstreamCachePolicy::Disabled;
+ }
+ else if (PolicyString == "readwrite")
+ {
+ Value = UpstreamCachePolicy::ReadWrite;
+ }
+ }
+ UpstreamCachePolicy& Value;
+};
+
+class ZenAuthConfigOption : public LuaConfig::OptionValue
+{
+public:
+ ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {}
+ virtual void Print(std::string_view Indent, StringBuilderBase& StringBuilder) override
+ {
+ if (Value.OpenIdProviders.empty())
+ {
+ StringBuilder.Append("{}");
+ return;
+ }
+ LuaConfig::LuaContainerWriter Writer(StringBuilder, Indent);
+ for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders)
+ {
+ Writer.BeginContainer("");
+ {
+ Writer.WriteValue("name", Config.Name);
+ Writer.WriteValue("url", Config.Url);
+ Writer.WriteValue("clientid", Config.ClientId);
+ }
+ Writer.EndContainer();
+ }
+ }
+ virtual void Parse(sol::object Object) override
+ {
+ if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>())
+ {
+ for (const auto& Kv : OpenIdProviders.value())
+ {
+ if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>())
+ {
+ std::string Name = OpenIdProvider.value().get_or("name", std::string("Default"));
+ std::string Url = OpenIdProvider.value().get_or("url", std::string());
+ std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string());
+
+ Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)});
+ }
+ }
+ }
+ }
+ ZenAuthConfig& Value;
+};
+
+class ZenObjectStoreConfigOption : public LuaConfig::OptionValue
+{
+public:
+ ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {}
+ virtual void Print(std::string_view Indent, StringBuilderBase& StringBuilder) override
+ {
+ if (Value.Buckets.empty())
+ {
+ StringBuilder.Append("{}");
+ return;
+ }
+ LuaConfig::LuaContainerWriter Writer(StringBuilder, Indent);
+ for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets)
+ {
+ Writer.BeginContainer("");
+ {
+ Writer.WriteValue("name", Config.Name);
+ std::string Directory = Config.Directory.string();
+ LuaConfig::EscapeBackslash(Directory);
+ Writer.WriteValue("directory", Directory);
+ }
+ Writer.EndContainer();
+ }
+ }
+ virtual void Parse(sol::object Object) override
+ {
+ if (sol::optional<sol::table> Buckets = Object.as<sol::table>())
+ {
+ for (const auto& Kv : Buckets.value())
+ {
+ if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>())
+ {
+ std::string Name = Bucket.value().get_or("name", std::string("Default"));
+ std::string Directory = Bucket.value().get_or("directory", std::string());
+
+ Value.Buckets.push_back({.Name = std::move(Name), .Directory = MakeSafeAbsolutePath(Directory)});
+ }
+ }
+ }
+ }
+ ZenObjectStoreConfig& Value;
+};
+
+std::shared_ptr<LuaConfig::OptionValue>
+MakeOption(UpstreamCachePolicy& Value)
+{
+ return std::make_shared<CachePolicyOption>(Value);
+};
+
+std::shared_ptr<LuaConfig::OptionValue>
+MakeOption(ZenAuthConfig& Value)
+{
+ return std::make_shared<ZenAuthConfigOption>(Value);
+};
+
+std::shared_ptr<LuaConfig::OptionValue>
+MakeOption(ZenObjectStoreConfig& Value)
+{
+ return std::make_shared<ZenObjectStoreConfigOption>(Value);
+};
+
+std::shared_ptr<LuaConfig::OptionValue>
+MakeOption(std::vector<std::pair<std::string, ZenStructuredCacheBucketConfig>>& Value)
+{
+ return std::make_shared<ZenStructuredCacheBucketsConfigOption>(Value);
+};
+
+void
+ParseConfigFile(const std::filesystem::path& Path,
+ ZenStorageServerOptions& ServerOptions,
+ const cxxopts::ParseResult& CmdLineResult,
+ std::string_view OutputConfigFile)
+{
+ ZEN_TRACE_CPU("ParseConfigFile");
+
+ using namespace std::literals;
+
+ LuaConfig::Options LuaOptions;
+
+ AddServerConfigOptions(LuaOptions, ServerOptions);
+
+ ////// server
+ LuaOptions.AddOption("server.pluginsconfigfile"sv, ServerOptions.PluginsConfigFile, "plugins-config"sv);
+
+ ////// objectstore
+ LuaOptions.AddOption("server.objectstore.enabled"sv, ServerOptions.ObjectStoreEnabled, "objectstore-enabled"sv);
+ LuaOptions.AddOption("server.objectstore.buckets"sv, ServerOptions.ObjectStoreConfig);
+
+ ////// buildsstore
+ LuaOptions.AddOption("server.buildstore.enabled"sv, ServerOptions.BuildStoreConfig.Enabled, "buildstore-enabled"sv);
+ LuaOptions.AddOption("server.buildstore.disksizelimit"sv, ServerOptions.BuildStoreConfig.MaxDiskSpaceLimit, "buildstore-disksizelimit");
+
+ ////// cache
+ LuaOptions.AddOption("cache.enable"sv, ServerOptions.StructuredCacheConfig.Enabled);
+ LuaOptions.AddOption("cache.writelog"sv, ServerOptions.StructuredCacheConfig.WriteLogEnabled, "cache-write-log"sv);
+ LuaOptions.AddOption("cache.accesslog"sv, ServerOptions.StructuredCacheConfig.AccessLogEnabled, "cache-access-log"sv);
+
+ LuaOptions.AddOption("cache.buckets"sv, ServerOptions.StructuredCacheConfig.PerBucketConfigs, "cache.buckets"sv);
+
+ LuaOptions.AddOption("cache.memlayer.sizethreshold"sv,
+ ServerOptions.StructuredCacheConfig.BucketConfig.MemCacheSizeThreshold,
+ "cache-memlayer-sizethreshold"sv);
+ LuaOptions.AddOption("cache.memlayer.targetfootprint"sv,
+ ServerOptions.StructuredCacheConfig.MemTargetFootprintBytes,
+ "cache-memlayer-targetfootprint"sv);
+ LuaOptions.AddOption("cache.memlayer.triminterval"sv,
+ ServerOptions.StructuredCacheConfig.MemTrimIntervalSeconds,
+ "cache-memlayer-triminterval"sv);
+ LuaOptions.AddOption("cache.memlayer.maxage"sv, ServerOptions.StructuredCacheConfig.MemMaxAgeSeconds, "cache-memlayer-maxage"sv);
+
+ LuaOptions.AddOption("cache.bucket.maxblocksize"sv,
+ ServerOptions.StructuredCacheConfig.BucketConfig.MaxBlockSize,
+ "cache-bucket-maxblocksize"sv);
+ LuaOptions.AddOption("cache.bucket.memlayer.sizethreshold"sv,
+ ServerOptions.StructuredCacheConfig.BucketConfig.MemCacheSizeThreshold,
+ "cache-bucket-memlayer-sizethreshold"sv);
+ LuaOptions.AddOption("cache.bucket.payloadalignment"sv,
+ ServerOptions.StructuredCacheConfig.BucketConfig.PayloadAlignment,
+ "cache-bucket-payloadalignment"sv);
+ LuaOptions.AddOption("cache.bucket.largeobjectthreshold"sv,
+ ServerOptions.StructuredCacheConfig.BucketConfig.LargeObjectThreshold,
+ "cache-bucket-largeobjectthreshold"sv);
+ LuaOptions.AddOption("cache.bucket.limitoverwrites"sv,
+ ServerOptions.StructuredCacheConfig.BucketConfig.LimitOverwrites,
+ "cache-bucket-limit-overwrites"sv);
+
+ ////// cache.upstream
+ LuaOptions.AddOption("cache.upstream.policy"sv, ServerOptions.UpstreamCacheConfig.CachePolicy, "upstream-cache-policy"sv);
+ LuaOptions.AddOption("cache.upstream.upstreamthreadcount"sv,
+ ServerOptions.UpstreamCacheConfig.UpstreamThreadCount,
+ "upstream-thread-count"sv);
+ LuaOptions.AddOption("cache.upstream.connecttimeoutms"sv,
+ ServerOptions.UpstreamCacheConfig.ConnectTimeoutMilliseconds,
+ "upstream-connect-timeout-ms"sv);
+ LuaOptions.AddOption("cache.upstream.timeoutms"sv, ServerOptions.UpstreamCacheConfig.TimeoutMilliseconds, "upstream-timeout-ms"sv);
+
+ ////// cache.upstream.jupiter
+ LuaOptions.AddOption("cache.upstream.jupiter.name"sv, ServerOptions.UpstreamCacheConfig.JupiterConfig.Name);
+ LuaOptions.AddOption("cache.upstream.jupiter.url"sv, ServerOptions.UpstreamCacheConfig.JupiterConfig.Url, "upstream-jupiter-url"sv);
+ LuaOptions.AddOption("cache.upstream.jupiter.oauthprovider"sv,
+ ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthUrl,
+ "upstream-jupiter-oauth-url"sv);
+ LuaOptions.AddOption("cache.upstream.jupiter.oauthclientid"sv,
+ ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientId,
+ "upstream-jupiter-oauth-clientid");
+ LuaOptions.AddOption("cache.upstream.jupiter.oauthclientsecret"sv,
+ ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientSecret,
+ "upstream-jupiter-oauth-clientsecret"sv);
+ LuaOptions.AddOption("cache.upstream.jupiter.openidprovider"sv,
+ ServerOptions.UpstreamCacheConfig.JupiterConfig.OpenIdProvider,
+ "upstream-jupiter-openid-provider"sv);
+ LuaOptions.AddOption("cache.upstream.jupiter.token"sv,
+ ServerOptions.UpstreamCacheConfig.JupiterConfig.AccessToken,
+ "upstream-jupiter-token"sv);
+ LuaOptions.AddOption("cache.upstream.jupiter.namespace"sv,
+ ServerOptions.UpstreamCacheConfig.JupiterConfig.Namespace,
+ "upstream-jupiter-namespace"sv);
+ LuaOptions.AddOption("cache.upstream.jupiter.ddcnamespace"sv,
+ ServerOptions.UpstreamCacheConfig.JupiterConfig.DdcNamespace,
+ "upstream-jupiter-namespace-ddc"sv);
+
+ ////// cache.upstream.zen
+ // LuaOptions.AddOption("cache.upstream.zen"sv, ServerOptions.UpstreamCacheConfig.ZenConfig);
+ LuaOptions.AddOption("cache.upstream.zen.name"sv, ServerOptions.UpstreamCacheConfig.ZenConfig.Name);
+ LuaOptions.AddOption("cache.upstream.zen.dns"sv, ServerOptions.UpstreamCacheConfig.ZenConfig.Dns);
+ LuaOptions.AddOption("cache.upstream.zen.url"sv, ServerOptions.UpstreamCacheConfig.ZenConfig.Urls);
+
+ ////// gc
+ LuaOptions.AddOption("gc.enabled"sv, ServerOptions.GcConfig.Enabled, "gc-enabled"sv);
+ LuaOptions.AddOption("gc.v2"sv, ServerOptions.GcConfig.UseGCV2, "gc-v2"sv);
+
+ LuaOptions.AddOption("gc.monitorintervalseconds"sv, ServerOptions.GcConfig.MonitorIntervalSeconds, "gc-monitor-interval-seconds"sv);
+ LuaOptions.AddOption("gc.intervalseconds"sv, ServerOptions.GcConfig.IntervalSeconds, "gc-interval-seconds"sv);
+ LuaOptions.AddOption("gc.collectsmallobjects"sv, ServerOptions.GcConfig.CollectSmallObjects, "gc-small-objects"sv);
+ LuaOptions.AddOption("gc.diskreservesize"sv, ServerOptions.GcConfig.DiskReserveSize, "disk-reserve-size"sv);
+ LuaOptions.AddOption("gc.disksizesoftlimit"sv, ServerOptions.GcConfig.DiskSizeSoftLimit, "gc-disksize-softlimit"sv);
+ LuaOptions.AddOption("gc.lowdiskspacethreshold"sv,
+ ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites,
+ "gc-low-diskspace-threshold"sv);
+ LuaOptions.AddOption("gc.lightweightintervalseconds"sv,
+ ServerOptions.GcConfig.LightweightIntervalSeconds,
+ "gc-lightweight-interval-seconds"sv);
+ LuaOptions.AddOption("gc.compactblockthreshold"sv,
+ ServerOptions.GcConfig.CompactBlockUsageThresholdPercent,
+ "gc-compactblock-threshold"sv);
+ LuaOptions.AddOption("gc.verbose"sv, ServerOptions.GcConfig.Verbose, "gc-verbose"sv);
+ LuaOptions.AddOption("gc.single-threaded"sv, ServerOptions.GcConfig.SingleThreaded, "gc-single-threaded"sv);
+ LuaOptions.AddOption("gc.cache.attachment.store"sv, ServerOptions.GcConfig.StoreCacheAttachmentMetaData, "gc-cache-attachment-store");
+ LuaOptions.AddOption("gc.projectstore.attachment.store"sv,
+ ServerOptions.GcConfig.StoreProjectAttachmentMetaData,
+ "gc-projectstore-attachment-store");
+ LuaOptions.AddOption("gc.attachment.passes"sv, ServerOptions.GcConfig.AttachmentPassCount, "gc-attachment-passes"sv);
+ LuaOptions.AddOption("gc.validation"sv, ServerOptions.GcConfig.EnableValidation, "gc-validation");
+
+ LuaOptions.AddOption("gc.cache.maxdurationseconds"sv, ServerOptions.GcConfig.Cache.MaxDurationSeconds, "gc-cache-duration-seconds"sv);
+ LuaOptions.AddOption("gc.projectstore.duration.seconds"sv,
+ ServerOptions.GcConfig.ProjectStore.MaxDurationSeconds,
+ "gc-projectstore-duration-seconds");
+ LuaOptions.AddOption("gc.buildstore.duration.seconds"sv,
+ ServerOptions.GcConfig.BuildStore.MaxDurationSeconds,
+ "gc-buildstore-duration-seconds");
+
+ ////// security
+ LuaOptions.AddOption("security.encryptionaeskey"sv, ServerOptions.EncryptionKey, "encryption-aes-key"sv);
+ LuaOptions.AddOption("security.encryptionaesiv"sv, ServerOptions.EncryptionIV, "encryption-aes-iv"sv);
+ LuaOptions.AddOption("security.openidproviders"sv, ServerOptions.AuthConfig);
+
+ ////// workspaces
+ LuaOptions.AddOption("workspaces.enabled"sv, ServerOptions.WorksSpacesConfig.Enabled, "workspaces-enabled"sv);
+ LuaOptions.AddOption("workspaces.allowconfigchanges"sv,
+ ServerOptions.WorksSpacesConfig.AllowConfigurationChanges,
+ "workspaces-allow-changes"sv);
+
+ LuaOptions.Parse(Path, CmdLineResult);
+
+ // These have special command line processing so we make sure we export them if they were configured on command line
+ if (!ServerOptions.AuthConfig.OpenIdProviders.empty())
+ {
+ LuaOptions.Touch("security.openidproviders"sv);
+ }
+ if (!ServerOptions.ObjectStoreConfig.Buckets.empty())
+ {
+ LuaOptions.Touch("server.objectstore.buckets"sv);
+ }
+ if (!ServerOptions.StructuredCacheConfig.PerBucketConfigs.empty())
+ {
+ LuaOptions.Touch("cache.buckets"sv);
+ }
+
+ if (!OutputConfigFile.empty())
+ {
+ std::filesystem::path WritePath(MakeSafeAbsolutePath(OutputConfigFile));
+ ExtendableStringBuilder<512> ConfigStringBuilder;
+ LuaOptions.Print(ConfigStringBuilder, CmdLineResult);
+ BasicFile Output;
+ Output.Open(WritePath, BasicFile::Mode::kTruncate);
+ Output.Write(ConfigStringBuilder.Data(), ConfigStringBuilder.Size(), 0);
+ }
+}
+
+void
+ParsePluginsConfigFile(const std::filesystem::path& Path, ZenStorageServerOptions& ServerOptions, int BasePort)
+{
+ using namespace std::literals;
+
+ IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
+ std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
+ std::string JsonError;
+ json11::Json PluginsInfo = json11::Json::parse(JsonText, JsonError);
+ if (!JsonError.empty())
+ {
+ ZEN_WARN("failed parsing plugins config file '{}'. Reason: '{}'", Path, JsonError);
+ return;
+ }
+ for (const json11::Json& PluginInfo : PluginsInfo.array_items())
+ {
+ if (!PluginInfo.is_object())
+ {
+ ZEN_WARN("the json file '{}' does not contain a valid plugin definition, object expected, got '{}'", Path, PluginInfo.dump());
+ continue;
+ }
+
+ HttpServerPluginConfig Config = {};
+
+ bool bNeedsPort = true;
+
+ for (const std::pair<const std::string, json11::Json>& Items : PluginInfo.object_items())
+ {
+ if (!Items.second.is_string())
+ {
+ ZEN_WARN("the json file '{}' does not contain a valid plugins definition, string expected, got '{}'",
+ Path,
+ Items.second.dump());
+ continue;
+ }
+
+ const std::string& Name = Items.first;
+ const std::string& Value = Items.second.string_value();
+
+ if (Name == "name"sv)
+ Config.PluginName = Value;
+ else
+ {
+ Config.PluginOptions.push_back({Name, Value});
+
+ if (Name == "port"sv)
+ {
+ bNeedsPort = false;
+ }
+ }
+ }
+
+ // add a default base port in case if json config didn't provide one
+ if (bNeedsPort)
+ {
+ Config.PluginOptions.push_back({"port", std::to_string(BasePort)});
+ }
+
+ ServerOptions.HttpServerConfig.PluginConfigs.push_back(Config);
+ }
+}
+
+void
+ZenStorageServerCmdLineOptions::AddCliOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ options.add_options()("snapshot-dir",
+ "Specify a snapshot of server state to mirror into the persistence root at startup",
+ cxxopts::value<std::string>(BaseSnapshotDir));
+ options.add_options()("plugins-config", "Path to plugins config file", cxxopts::value<std::string>(PluginsConfigFile));
+ options.add_options()("scrub",
+ "Validate state at startup",
+ cxxopts::value(ServerOptions.ScrubOptions)->implicit_value("yes"),
+ "(nocas,nogc,nodelete,yes,no)*");
+
+ AddSecurityOptions(options, ServerOptions);
+ AddCacheOptions(options, ServerOptions);
+ AddGcOptions(options, ServerOptions);
+ AddObjectStoreOptions(options, ServerOptions);
+ AddBuildStoreOptions(options, ServerOptions);
+ AddWorkspacesOptions(options, ServerOptions);
+}
+
+void
+ZenStorageServerCmdLineOptions::AddSecurityOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ options.add_option("security",
+ "",
+ "encryption-aes-key",
+ "256 bit AES encryption key",
+ cxxopts::value<std::string>(ServerOptions.EncryptionKey),
+ "");
+
+ options.add_option("security",
+ "",
+ "encryption-aes-iv",
+ "128 bit AES encryption initialization vector",
+ cxxopts::value<std::string>(ServerOptions.EncryptionIV),
+ "");
+
+ options.add_option("security",
+ "",
+ "openid-provider-name",
+ "Open ID provider name",
+ cxxopts::value<std::string>(OpenIdProviderName),
+ "Default");
+
+ options.add_option("security", "", "openid-provider-url", "Open ID provider URL", cxxopts::value<std::string>(OpenIdProviderUrl), "");
+ options.add_option("security", "", "openid-client-id", "Open ID client ID", cxxopts::value<std::string>(OpenIdClientId), "");
+}
+
+void
+ZenStorageServerCmdLineOptions::AddCacheOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ options.add_option("cache",
+ "",
+ "upstream-cache-policy",
+ "",
+ cxxopts::value<std::string>(UpstreamCachePolicyOptions)->default_value(""),
+ "Upstream cache policy (readwrite|readonly|writeonly|disabled)");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-url",
+ "URL to a Jupiter instance",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.Url)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-oauth-url",
+ "URL to the OAuth provier",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthUrl)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-oauth-clientid",
+ "The OAuth client ID",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientId)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-oauth-clientsecret",
+ "The OAuth client secret",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientSecret)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-openid-provider",
+ "Name of a registered Open ID provider",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.OpenIdProvider)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-token",
+ "A static authentication token",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.AccessToken)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-namespace",
+ "The Common Blob Store API namespace",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.Namespace)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-jupiter-namespace-ddc",
+ "The lecacy DDC namespace",
+ cxxopts::value<std::string>(ServerOptions.UpstreamCacheConfig.JupiterConfig.DdcNamespace)->default_value(""),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-zen-url",
+ "URL to remote Zen server. Use a comma separated list to choose the one with the best latency.",
+ cxxopts::value<std::vector<std::string>>(ServerOptions.UpstreamCacheConfig.ZenConfig.Urls),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-zen-dns",
+ "DNS that resolves to one or more Zen server instance(s)",
+ cxxopts::value<std::vector<std::string>>(ServerOptions.UpstreamCacheConfig.ZenConfig.Dns),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-thread-count",
+ "Number of threads used for upstream procsssing",
+ cxxopts::value<int32_t>(ServerOptions.UpstreamCacheConfig.UpstreamThreadCount)->default_value("4"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-connect-timeout-ms",
+ "Connect timeout in millisecond(s). Default 5000 ms.",
+ cxxopts::value<int32_t>(ServerOptions.UpstreamCacheConfig.ConnectTimeoutMilliseconds)->default_value("5000"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "upstream-timeout-ms",
+ "Timeout in millisecond(s). Default 0 ms",
+ cxxopts::value<int32_t>(ServerOptions.UpstreamCacheConfig.TimeoutMilliseconds)->default_value("0"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-write-log",
+ "Whether cache write log is enabled",
+ cxxopts::value<bool>(ServerOptions.StructuredCacheConfig.WriteLogEnabled)->default_value("false"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-access-log",
+ "Whether cache access log is enabled",
+ cxxopts::value<bool>(ServerOptions.StructuredCacheConfig.AccessLogEnabled)->default_value("false"),
+ "");
+
+ options.add_option(
+ "cache",
+ "",
+ "cache-memlayer-sizethreshold",
+ "The largest size of a cache entry that may be cached in memory. Default set to 1024 (1 Kb). Set to 0 to disable memory "
+ "caching. "
+ "Obsolete, replaced by `--cache-bucket-memlayer-sizethreshold`",
+ cxxopts::value<uint64_t>(ServerOptions.StructuredCacheConfig.BucketConfig.MemCacheSizeThreshold)->default_value("1024"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-memlayer-targetfootprint",
+ "Max allowed memory used by cache memory layer per namespace in bytes. Default set to 536870912 (512 Mb).",
+ cxxopts::value<uint64_t>(ServerOptions.StructuredCacheConfig.MemTargetFootprintBytes)->default_value("536870912"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-memlayer-triminterval",
+ "Minimum time between each attempt to trim cache memory layers in seconds. Default set to 60 (1 min).",
+ cxxopts::value<uint64_t>(ServerOptions.StructuredCacheConfig.MemTrimIntervalSeconds)->default_value("60"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-memlayer-maxage",
+ "Maximum age of payloads when trimming cache memory layers in seconds. Default set to 86400 (1 day).",
+ cxxopts::value<uint64_t>(ServerOptions.StructuredCacheConfig.MemMaxAgeSeconds)->default_value("86400"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-bucket-maxblocksize",
+ "Max size of cache bucket blocks. Default set to 1073741824 (1GB).",
+ cxxopts::value<uint64_t>(ServerOptions.StructuredCacheConfig.BucketConfig.MaxBlockSize)->default_value("1073741824"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-bucket-payloadalignment",
+ "Payload alignement for cache bucket blocks. Default set to 16.",
+ cxxopts::value<uint32_t>(ServerOptions.StructuredCacheConfig.BucketConfig.PayloadAlignment)->default_value("16"),
+ "");
+
+ options.add_option(
+ "cache",
+ "",
+ "cache-bucket-largeobjectthreshold",
+ "Threshold for storing cache bucket values as loose files. Default set to 131072 (128 KB).",
+ cxxopts::value<uint64_t>(ServerOptions.StructuredCacheConfig.BucketConfig.LargeObjectThreshold)->default_value("131072"),
+ "");
+
+ options.add_option(
+ "cache",
+ "",
+ "cache-bucket-memlayer-sizethreshold",
+ "The largest size of a cache entry that may be cached in memory. Default set to 1024 (1 Kb). Set to 0 to disable memory "
+ "caching.",
+ cxxopts::value<uint64_t>(ServerOptions.StructuredCacheConfig.BucketConfig.MemCacheSizeThreshold)->default_value("1024"),
+ "");
+
+ options.add_option("cache",
+ "",
+ "cache-bucket-limit-overwrites",
+ "Whether to require policy flag pattern before allowing overwrites in cache bucket",
+ cxxopts::value<bool>(ServerOptions.StructuredCacheConfig.BucketConfig.LimitOverwrites)->default_value("false"),
+ "");
+}
+
+void
+ZenStorageServerCmdLineOptions::AddGcOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ options.add_option("gc",
+ "",
+ "gc-cache-attachment-store",
+ "Enable storing attachments referenced by a cache record in block store meta data.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.StoreCacheAttachmentMetaData)->default_value("false"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-projectstore-attachment-store",
+ "Enable storing attachments referenced by project oplogs in meta data.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.StoreProjectAttachmentMetaData)->default_value("false"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-validation",
+ "Enable validation of references after full GC.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.EnableValidation)->default_value("true"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-enabled",
+ "Whether garbage collection is enabled or not.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.Enabled)->default_value("true"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-v2",
+ "Use V2 of GC implementation or not.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.UseGCV2)->default_value("true"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-small-objects",
+ "Whether garbage collection of small objects is enabled or not.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.CollectSmallObjects)->default_value("true"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-interval-seconds",
+ "Garbage collection interval in seconds. Default set to 3600 (1 hour).",
+ cxxopts::value<int32_t>(ServerOptions.GcConfig.IntervalSeconds)->default_value("3600"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-lightweight-interval-seconds",
+ "Lightweight garbage collection interval in seconds. Default set to 900 (30 min).",
+ cxxopts::value<int32_t>(ServerOptions.GcConfig.LightweightIntervalSeconds)->default_value("900"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-cache-duration-seconds",
+ "Max duration in seconds before Z$ entries get evicted. Default set to 1209600 (2 weeks)",
+ cxxopts::value<int32_t>(ServerOptions.GcConfig.Cache.MaxDurationSeconds)->default_value("1209600"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-projectstore-duration-seconds",
+ "Max duration in seconds before project store entries get evicted. Default set to 1209600 (2 weeks)",
+ cxxopts::value<int32_t>(ServerOptions.GcConfig.ProjectStore.MaxDurationSeconds)->default_value("1209600"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-buildstore-duration-seconds",
+ "Max duration in seconds before build store entries get evicted. Default set to 604800 (1 week)",
+ cxxopts::value<int32_t>(ServerOptions.GcConfig.BuildStore.MaxDurationSeconds)->default_value("604800"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "disk-reserve-size",
+ "Size of gc disk reserve in bytes. Default set to 268435456 (256 Mb). Set to zero to disable.",
+ cxxopts::value<uint64_t>(ServerOptions.GcConfig.DiskReserveSize)->default_value("268435456"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-monitor-interval-seconds",
+ "Garbage collection monitoring interval in seconds. Default set to 30 (30 seconds)",
+ cxxopts::value<int32_t>(ServerOptions.GcConfig.MonitorIntervalSeconds)->default_value("30"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-low-diskspace-threshold",
+ "Minimum free space on disk to allow writes to disk. Default set to 268435456 (256 Mb). Set to zero to disable.",
+ cxxopts::value<uint64_t>(ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites)->default_value("268435456"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-disksize-softlimit",
+ "Garbage collection disk usage soft limit. Default set to 0 (Off).",
+ cxxopts::value<uint64_t>(ServerOptions.GcConfig.DiskSizeSoftLimit)->default_value("0"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-compactblock-threshold",
+ "Garbage collection - how much of a compact block should be used to skip compacting the block. 0 - compact only "
+ "empty eligible blocks, 100 - compact all non-full eligible blocks.",
+ cxxopts::value<uint32_t>(ServerOptions.GcConfig.CompactBlockUsageThresholdPercent)->default_value("60"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-verbose",
+ "Enable verbose logging for GC.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.Verbose)->default_value("false"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-single-threaded",
+ "Force GC to run single threaded.",
+ cxxopts::value<bool>(ServerOptions.GcConfig.SingleThreaded)->default_value("false"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-attachment-passes",
+ "Limit the range of unreferenced attachments included in GC check by breaking it into passes. Default is one pass "
+ "which includes all the attachments.",
+ cxxopts::value<uint16_t>(ServerOptions.GcConfig.AttachmentPassCount)->default_value("1"),
+ "");
+}
+
+void
+ZenStorageServerCmdLineOptions::AddObjectStoreOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ options.add_option("objectstore",
+ "",
+ "objectstore-enabled",
+ "Whether the object store is enabled or not.",
+ cxxopts::value<bool>(ServerOptions.ObjectStoreEnabled)->default_value("false"),
+ "");
+
+ options.add_option("objectstore",
+ "",
+ "objectstore-bucket",
+ "Object store bucket mappings.",
+ cxxopts::value<std::vector<std::string>>(BucketConfigs),
+ "");
+}
+
+void
+ZenStorageServerCmdLineOptions::AddBuildStoreOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ options.add_option("buildstore",
+ "",
+ "buildstore-enabled",
+ "Whether the builds store is enabled or not.",
+ cxxopts::value<bool>(ServerOptions.BuildStoreConfig.Enabled)->default_value("false"),
+ "");
+ options.add_option("buildstore",
+ "",
+ "buildstore-disksizelimit",
+ "Max number of bytes before build store entries get evicted. Default set to 1099511627776 (1TB week)",
+ cxxopts::value<uint64_t>(ServerOptions.BuildStoreConfig.MaxDiskSpaceLimit)->default_value("1099511627776"),
+ "");
+}
+
+void
+ZenStorageServerCmdLineOptions::AddWorkspacesOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ options.add_option("workspaces",
+ "",
+ "workspaces-enabled",
+ "",
+ cxxopts::value<bool>(ServerOptions.WorksSpacesConfig.Enabled)->default_value("true"),
+ "Enable workspaces support with folder sharing");
+
+ options.add_option("workspaces",
+ "",
+ "workspaces-allow-changes",
+ "",
+ cxxopts::value<bool>(ServerOptions.WorksSpacesConfig.AllowConfigurationChanges)->default_value("false"),
+ "Allow adding/modifying/deleting of workspace and shares via http endpoint");
+}
+
+void
+ZenStorageServerCmdLineOptions::ApplyOptions(cxxopts::Options& options, ZenStorageServerOptions& ServerOptions)
+{
+ ServerOptions.BaseSnapshotDir = MakeSafeAbsolutePath(BaseSnapshotDir);
+ ServerOptions.PluginsConfigFile = MakeSafeAbsolutePath(PluginsConfigFile);
+ ServerOptions.UpstreamCacheConfig.CachePolicy = ParseUpstreamCachePolicy(UpstreamCachePolicyOptions);
+
+ if (!BaseSnapshotDir.empty())
+ {
+ if (ServerOptions.DataDir.empty())
+ throw OptionParseException("'--snapshot-dir' requires '--data-dir'", options.help());
+
+ if (!IsDir(ServerOptions.BaseSnapshotDir))
+ throw std::runtime_error(fmt::format("'--snapshot-dir' ('{}') must be a directory", ServerOptions.BaseSnapshotDir));
+ }
+
+ if (OpenIdProviderUrl.empty() == false)
+ {
+ if (OpenIdClientId.empty())
+ {
+ throw OptionParseException("'--openid-provider-url' requires '--openid-client-id'", options.help());
+ }
+
+ ServerOptions.AuthConfig.OpenIdProviders.push_back(
+ {.Name = OpenIdProviderName, .Url = OpenIdProviderUrl, .ClientId = OpenIdClientId});
+ }
+
+ ServerOptions.ObjectStoreConfig = ParseBucketConfigs(BucketConfigs);
+}
+
+} // namespace zen