diff options
| author | Stefan Boberg <[email protected]> | 2025-10-14 11:32:16 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-10-14 11:32:16 +0200 |
| commit | ca09abbeef5b1788f4a52b61eedd2f3dd07f81f2 (patch) | |
| tree | 005a50adfddf6982bab3a06bb93d4c50da1a11fd /src/zenserver/storage/storageconfig.cpp | |
| parent | make asiohttp work without IPv6 (#562) (diff) | |
| download | zen-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.cpp | 1055 |
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 |