aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzousar <[email protected]>2025-09-22 16:38:15 -0600
committerzousar <[email protected]>2025-09-22 16:38:15 -0600
commit1c591941878e5c5009d51a82ff61986a63bd2e11 (patch)
tree3860ab5ebddf1919aab3cca019422fa76cfcdda4 /src
parentChange batch put responses for client reporting (diff)
parent5.7.2-pre1 (diff)
downloadzen-1c591941878e5c5009d51a82ff61986a63bd2e11.tar.xz
zen-1c591941878e5c5009d51a82ff61986a63bd2e11.zip
Merge branch 'main' into zs/put-overwrite-policy-response
Diffstat (limited to 'src')
-rw-r--r--src/zen/authutils.cpp246
-rw-r--r--src/zen/authutils.h51
-rw-r--r--src/zen/cmds/builds_cmd.cpp1282
-rw-r--r--src/zen/cmds/builds_cmd.h25
-rw-r--r--src/zen/cmds/print_cmd.cpp10
-rw-r--r--src/zen/cmds/projectstore_cmd.cpp397
-rw-r--r--src/zen/cmds/projectstore_cmd.h37
-rw-r--r--src/zen/zen.cpp170
-rw-r--r--src/zencore/sentryintegration.cpp2
-rw-r--r--src/zenhttp/httpclientauth.cpp5
-rw-r--r--src/zenhttp/include/zenhttp/httpclientauth.h3
-rw-r--r--src/zenserver/projectstore/buildsremoteprojectstore.cpp8
-rw-r--r--src/zenserver/projectstore/buildsremoteprojectstore.h3
-rw-r--r--src/zenserver/projectstore/jupiterremoteprojectstore.cpp5
-rw-r--r--src/zenserver/projectstore/jupiterremoteprojectstore.h3
-rw-r--r--src/zenserver/projectstore/projectstore.cpp13
-rw-r--r--src/zenserver/projectstore/remoteprojectstore.cpp18
-rw-r--r--src/zenstore/blockstore.cpp13
-rw-r--r--src/zenstore/gc.cpp4
-rw-r--r--src/zenstore/include/zenstore/blockstore.h12
-rw-r--r--src/zenutil/buildstoragecache.cpp18
-rw-r--r--src/zenutil/include/zenutil/buildstoragecache.h9
-rw-r--r--src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h7
-rw-r--r--src/zenutil/include/zenutil/jupiter/jupiterhost.h35
-rw-r--r--src/zenutil/jupiter/jupiterbuildstorage.cpp47
-rw-r--r--src/zenutil/jupiter/jupiterhost.cpp66
26 files changed, 1647 insertions, 842 deletions
diff --git a/src/zen/authutils.cpp b/src/zen/authutils.cpp
new file mode 100644
index 000000000..69ea3f9c2
--- /dev/null
+++ b/src/zen/authutils.cpp
@@ -0,0 +1,246 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "authutils.h"
+
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zencore/logging.h>
+
+#include <zenhttp/auth/authmgr.h>
+#include <zenhttp/httpclient.h>
+#include <zenhttp/httpclientauth.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <json11.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+using namespace std::literals;
+
+std::string_view
+GetDefaultAccessTokenEnvVariableName()
+{
+#if ZEN_PLATFORM_WINDOWS
+ return "UE-CloudDataCacheAccessToken"sv;
+#endif
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ return "UE_CloudDataCacheAccessToken"sv;
+#endif
+}
+
+std::string
+ReadAccessTokenFromJsonFile(const std::filesystem::path& Path)
+{
+ if (!IsFile(Path))
+ {
+ throw std::runtime_error(fmt::format("the file '{}' does not exist", Path));
+ }
+ IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
+ std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
+ std::string JsonError;
+ json11::Json TokenInfo = json11::Json::parse(JsonText, JsonError);
+ if (!JsonError.empty())
+ {
+ throw std::runtime_error(fmt::format("failed parsing json file '{}'. Reason: '{}'", Path, JsonError));
+ }
+ const std::string AuthToken = TokenInfo["Token"].string_value();
+ if (AuthToken.empty())
+ {
+ throw std::runtime_error(fmt::format("the json file '{}' does not contain a value for \"Token\"", Path));
+ }
+ return AuthToken;
+}
+
+std::filesystem::path
+FindOidcTokenExePath(std::string_view OidcTokenAuthExecutablePath)
+{
+ if (OidcTokenAuthExecutablePath.empty())
+ {
+ const std::string OidcExecutableName = "OidcToken" ZEN_EXE_SUFFIX_LITERAL;
+ std::filesystem::path OidcTokenPath = (GetRunningExecutablePath().parent_path() / OidcExecutableName).make_preferred();
+ if (IsFile(OidcTokenPath))
+ {
+ return OidcTokenPath;
+ }
+ OidcTokenPath = (std::filesystem::current_path() / OidcExecutableName).make_preferred();
+ if (IsFile(OidcTokenPath))
+ {
+ return OidcTokenPath;
+ }
+ }
+ else
+ {
+ std::filesystem::path OidcTokenPath = std::filesystem::absolute(StringToPath(OidcTokenAuthExecutablePath)).make_preferred();
+ if (IsFile(OidcTokenPath))
+ {
+ return OidcTokenPath;
+ }
+ }
+ return {};
+};
+
+void
+AuthCommandLineOptions::AddOptions(cxxopts::Options& Ops)
+{
+ // Direct access token (may expire)
+ Ops.add_option("auth-token", "", "access-token", "Remote host access token", cxxopts::value(m_AccessToken), "<accesstoken>");
+ Ops.add_option("auth-token",
+ "",
+ "access-token-env",
+ "Name of environment variable that holds the remote host access token",
+ cxxopts::value(m_AccessTokenEnv)->default_value(std::string(GetDefaultAccessTokenEnvVariableName())),
+ "<envvariable>");
+ Ops.add_option("auth-token",
+ "",
+ "access-token-path",
+ "Path to json file that holds the remote host access token",
+ cxxopts::value(m_AccessTokenPath),
+ "<filepath>");
+
+ // Auth manager token encryption
+ Ops.add_option("security", "", "encryption-aes-key", "256 bit AES encryption key", cxxopts::value<std::string>(m_EncryptionKey), "");
+ Ops.add_option("security",
+ "",
+ "encryption-aes-iv",
+ "128 bit AES encryption initialization vector",
+ cxxopts::value<std::string>(m_EncryptionIV),
+ "");
+
+ // OpenId acccess token
+ Ops.add_option("openid",
+ "",
+ "openid-provider-name",
+ "Open ID provider name",
+ cxxopts::value<std::string>(m_OpenIdProviderName),
+ "Default");
+ Ops.add_option("openid", "", "openid-provider-url", "Open ID provider url", cxxopts::value<std::string>(m_OpenIdProviderUrl), "");
+ Ops.add_option("openid", "", "openid-client-id", "Open ID client id", cxxopts::value<std::string>(m_OpenIdClientId), "");
+ Ops.add_option("openid", "", "openid-refresh-token", "Open ID refresh token", cxxopts::value<std::string>(m_OpenIdRefreshToken), "");
+
+ // OAuth acccess token
+ Ops.add_option("oauth", "", "oauth-url", "OAuth provier url", cxxopts::value<std::string>(m_OAuthUrl)->default_value(""), "");
+ Ops.add_option("oauth", "", "oauth-clientid", "OAuth client id", cxxopts::value<std::string>(m_OAuthClientId)->default_value(""), "");
+ Ops.add_option("oauth",
+ "",
+ "oauth-clientsecret",
+ "OAuth client secret",
+ cxxopts::value<std::string>(m_OAuthClientSecret)->default_value(""),
+ "");
+ Ops.add_option("auth",
+ "",
+ "oidctoken-exe-path",
+ "Path to OidcToken executable",
+ cxxopts::value<std::string>(m_OidcTokenAuthExecutablePath)->default_value(""),
+ "");
+ Ops.add_option("auth",
+ "",
+ "oidctoken-exe-unattended",
+ "Set mode to unattended when launcing OidcToken executable",
+ cxxopts::value<bool>(m_OidcTokenUnattended),
+ "");
+};
+
+void
+AuthCommandLineOptions::ParseOptions(cxxopts::Options& Ops,
+ const std::filesystem::path& SystemRootDir,
+ HttpClientSettings& ClientSettings,
+ std::string_view HostUrl,
+ std::unique_ptr<AuthMgr>& Auth,
+ bool Quiet)
+{
+ auto CreateAuthMgr = [&]() {
+ ZEN_ASSERT(!SystemRootDir.empty());
+ if (!Auth)
+ {
+ if (m_EncryptionKey.empty())
+ {
+ m_EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456";
+ if (!Quiet)
+ {
+ ZEN_CONSOLE_WARN("Using default encryption key");
+ }
+ }
+
+ if (m_EncryptionIV.empty())
+ {
+ m_EncryptionIV = "0123456789abcdef";
+ if (!Quiet)
+ {
+ ZEN_CONSOLE_WARN("Using default encryption initialization vector");
+ }
+ }
+
+ AuthConfig AuthMgrConfig = {.RootDirectory = SystemRootDir / "auth",
+ .EncryptionKey = AesKey256Bit::FromString(m_EncryptionKey),
+ .EncryptionIV = AesIV128Bit::FromString(m_EncryptionIV)};
+ if (!AuthMgrConfig.EncryptionKey.IsValid())
+ {
+ throw OptionParseException(fmt::format("'--encryption-aes-key' ('{}') is malformed", m_EncryptionKey), Ops.help());
+ }
+ if (!AuthMgrConfig.EncryptionIV.IsValid())
+ {
+ throw OptionParseException(fmt::format("'--encryption-aes-iv' ('{}') is malformed", m_EncryptionIV), Ops.help());
+ }
+ Auth = AuthMgr::Create(AuthMgrConfig);
+ }
+ };
+
+ if (!m_OpenIdProviderUrl.empty() && !m_OpenIdClientId.empty())
+ {
+ CreateAuthMgr();
+ std::string ProviderName = m_OpenIdProviderName.empty() ? "Default" : m_OpenIdProviderName;
+ Auth->AddOpenIdProvider({.Name = ProviderName, .Url = m_OpenIdProviderUrl, .ClientId = m_OpenIdClientId});
+ if (!m_OpenIdRefreshToken.empty())
+ {
+ Auth->AddOpenIdToken({.ProviderName = ProviderName, .RefreshToken = m_OpenIdRefreshToken});
+ }
+ }
+
+ auto GetEnvAccessToken = [](const std::string& AccessTokenEnv) -> std::string {
+ if (!AccessTokenEnv.empty())
+ {
+ return GetEnvVariable(AccessTokenEnv);
+ }
+ return {};
+ };
+
+ if (!m_AccessToken.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(m_AccessToken);
+ }
+ else if (!m_AccessTokenPath.empty())
+ {
+ MakeSafeAbsolutePathÍnPlace(m_AccessTokenPath);
+ std::string ResolvedAccessToken = ReadAccessTokenFromJsonFile(m_AccessTokenPath);
+ if (!ResolvedAccessToken.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
+ }
+ }
+ else if (!m_OAuthUrl.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOAuthClientCredentials(
+ {.Url = m_OAuthUrl, .ClientId = m_OAuthClientId, .ClientSecret = m_OAuthClientSecret});
+ }
+ else if (!m_OpenIdProviderName.empty())
+ {
+ CreateAuthMgr();
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOpenIdProvider(*Auth, m_OpenIdProviderName);
+ }
+ else if (std::string ResolvedAccessToken = GetEnvAccessToken(m_AccessTokenEnv); !ResolvedAccessToken.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
+ }
+ else if (std::filesystem::path OidcTokenExePath = FindOidcTokenExePath(m_OidcTokenAuthExecutablePath); !OidcTokenExePath.empty())
+ {
+ ClientSettings.AccessTokenProvider =
+ httpclientauth::CreateFromOidcTokenExecutable(OidcTokenExePath, HostUrl, Quiet, m_OidcTokenUnattended);
+ }
+
+ if (!ClientSettings.AccessTokenProvider)
+ {
+ CreateAuthMgr();
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromDefaultOpenIdProvider(*Auth);
+ }
+}
+} // namespace zen
diff --git a/src/zen/authutils.h b/src/zen/authutils.h
new file mode 100644
index 000000000..3cc9d4d4a
--- /dev/null
+++ b/src/zen/authutils.h
@@ -0,0 +1,51 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "zen.h"
+
+namespace zen {
+
+struct HttpClientSettings;
+class AuthMgr;
+
+struct AuthCommandLineOptions
+{
+ // Direct access token (may expire)
+ std::string m_AccessToken;
+ std::string m_AccessTokenEnv;
+ std::filesystem::path m_AccessTokenPath;
+
+ // Auth manager token encryption
+ std::string m_EncryptionKey; // 256 bit AES encryption key
+ std::string m_EncryptionIV; // 128 bit AES initialization vector
+
+ // OpenId acccess token
+ std::string m_OpenIdProviderName;
+ std::string m_OpenIdProviderUrl;
+ std::string m_OpenIdClientId;
+ std::string m_OpenIdRefreshToken;
+
+ // OAuth acccess token
+ std::string m_OAuthUrl;
+ std::string m_OAuthClientId;
+ std::string m_OAuthClientSecret;
+
+ std::string m_OidcTokenAuthExecutablePath;
+ bool m_OidcTokenUnattended = false;
+
+ void AddOptions(cxxopts::Options& Ops);
+
+ void ParseOptions(cxxopts::Options& Ops,
+ const std::filesystem::path& SystemRootDir,
+ HttpClientSettings& InOutClientSettings,
+ std::string_view HostUrl,
+ std::unique_ptr<AuthMgr>& OutAuthMgr,
+ bool Quiet);
+};
+
+std::string ReadAccessTokenFromJsonFile(const std::filesystem::path& Path);
+std::string_view GetDefaultAccessTokenEnvVariableName();
+std::filesystem::path FindOidcTokenExePath(std::string_view OidcTokenAuthExecutablePath);
+
+} // namespace zen
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp
index 949e40085..d9f57959e 100644
--- a/src/zen/cmds/builds_cmd.cpp
+++ b/src/zen/cmds/builds_cmd.cpp
@@ -31,6 +31,7 @@
#include <zenutil/chunkingcontroller.h>
#include <zenutil/filebuildstorage.h>
#include <zenutil/jupiter/jupiterbuildstorage.h>
+#include <zenutil/jupiter/jupiterhost.h>
#include <zenutil/jupiter/jupitersession.h>
#include <zenutil/parallelwork.h>
#include <zenutil/wildcard.h>
@@ -39,7 +40,6 @@
#include <signal.h>
#include <memory>
-#include <regex>
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
@@ -823,72 +823,6 @@ namespace {
return CleanWipe;
}
- bool ParseCloudUrl(std::string_view InUrl,
- std::string& OutHost,
- std::string& OutNamespace,
- std::string& OutBucket,
- std::string& OutBuildId)
- {
- std::string Url(InUrl);
- const std::string_view ExtendedApiString = "api/v2/builds/";
- if (auto ApiString = ToLower(Url).find(ExtendedApiString); ApiString != std::string::npos)
- {
- Url.erase(ApiString, ExtendedApiString.length());
- }
-
- const std::string ArtifactURLRegExString = R"((http[s]?:\/\/.*?)\/(.*?)\/(.*?)\/(.*))";
- const std::regex ArtifactURLRegEx(ArtifactURLRegExString, std::regex::ECMAScript | std::regex::icase);
- std::match_results<std::string_view::const_iterator> MatchResults;
- std::string_view UrlToParse(Url);
- if (regex_match(begin(UrlToParse), end(UrlToParse), MatchResults, ArtifactURLRegEx) && MatchResults.size() == 5)
- {
- auto GetMatch = [&MatchResults](uint32_t Index) -> std::string_view {
- ZEN_ASSERT(Index < MatchResults.size());
-
- const auto& Match = MatchResults[Index];
-
- return std::string_view(&*Match.first, Match.second - Match.first);
- };
-
- const std::string_view Host = GetMatch(1);
- const std::string_view Namespace = GetMatch(2);
- const std::string_view Bucket = GetMatch(3);
- const std::string_view BuildId = GetMatch(4);
-
- OutHost = Host;
- OutNamespace = Namespace;
- OutBucket = Bucket;
- OutBuildId = BuildId;
- return true;
- }
- else
- {
- return false;
- }
- }
-
- std::string ReadAccessTokenFromFile(const std::filesystem::path& Path)
- {
- if (!IsFile(Path))
- {
- throw std::runtime_error(fmt::format("the file '{}' does not exist", Path));
- }
- IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
- std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
- std::string JsonError;
- json11::Json TokenInfo = json11::Json::parse(JsonText, JsonError);
- if (!JsonError.empty())
- {
- throw std::runtime_error(fmt::format("failed parsing json file '{}'. Reason: '{}'", Path, JsonError));
- }
- const std::string AuthToken = TokenInfo["Token"].string_value();
- if (AuthToken.empty())
- {
- throw std::runtime_error(fmt::format("the json file '{}' does not contain a value for \"Token\"", Path));
- }
- return AuthToken;
- }
-
IoBuffer WriteToTempFile(CompositeBuffer&& Buffer,
const std::filesystem::path& TempFolderPath,
const IoHash& Hash,
@@ -1721,6 +1655,275 @@ namespace {
return Result;
}
+ struct BlockRangeDescriptor
+ {
+ uint32_t BlockIndex = (uint32_t)-1;
+ uint64_t RangeStart = 0;
+ uint64_t RangeLength = 0;
+ uint32_t ChunkBlockIndexStart = 0;
+ uint32_t ChunkBlockIndexCount = 0;
+ };
+
+ BlockRangeDescriptor MergeBlockRanges(std::span<const BlockRangeDescriptor> Ranges)
+ {
+ ZEN_ASSERT(Ranges.size() > 1);
+ const BlockRangeDescriptor& First = Ranges.front();
+ const BlockRangeDescriptor& Last = Ranges.back();
+
+ return BlockRangeDescriptor{
+ .BlockIndex = First.BlockIndex,
+ .RangeStart = First.RangeStart,
+ .RangeLength = Last.RangeStart + Last.RangeLength - First.RangeStart,
+ .ChunkBlockIndexStart = First.ChunkBlockIndexStart,
+ .ChunkBlockIndexCount = Last.ChunkBlockIndexStart + Last.ChunkBlockIndexCount - First.ChunkBlockIndexStart};
+ }
+
+ std::optional<std::vector<BlockRangeDescriptor>> MakeOptionalBlockRangeVector(uint64_t TotalBlockSize,
+ const BlockRangeDescriptor& Range)
+ {
+ if (Range.RangeLength == TotalBlockSize)
+ {
+ return {};
+ }
+ else
+ {
+ return std::vector<BlockRangeDescriptor>{Range};
+ }
+ };
+
+ struct BlockRangeLimit
+ {
+ uint16_t SizePercent;
+ uint16_t MaxRangeCount;
+ };
+
+ static const uint16_t FullBlockRangePercentLimit = 95;
+
+ static const std::vector<BlockRangeLimit> ForceMergeLimits = {{.SizePercent = FullBlockRangePercentLimit, .MaxRangeCount = 1},
+ {.SizePercent = 90, .MaxRangeCount = 2},
+ {.SizePercent = 85, .MaxRangeCount = 8},
+ {.SizePercent = 80, .MaxRangeCount = 16},
+ {.SizePercent = 70, .MaxRangeCount = 32},
+ {.SizePercent = 60, .MaxRangeCount = 48},
+ {.SizePercent = 2, .MaxRangeCount = 56},
+ {.SizePercent = 0, .MaxRangeCount = 64}};
+
+ const BlockRangeLimit* GetBlockRangeLimitForRange(std::span<const BlockRangeLimit> Limits,
+ uint64_t TotalBlockSize,
+ std::span<const BlockRangeDescriptor> Ranges)
+ {
+ if (Ranges.size() > 1)
+ {
+ const std::uint64_t WantedSize =
+ std::accumulate(Ranges.begin(), Ranges.end(), uint64_t(0), [](uint64_t Current, const BlockRangeDescriptor& Range) {
+ return Current + Range.RangeLength;
+ });
+
+ const double RangeRequestedPercent = (WantedSize * 100.0) / TotalBlockSize;
+
+ for (const BlockRangeLimit& Limit : Limits)
+ {
+ if (RangeRequestedPercent >= Limit.SizePercent && Ranges.size() > Limit.MaxRangeCount)
+ {
+ return &Limit;
+ }
+ }
+ }
+ return nullptr;
+ };
+
+ std::vector<BlockRangeDescriptor> CollapseBlockRanges(const uint64_t AlwaysAcceptableGap,
+ std::span<const BlockRangeDescriptor> BlockRanges)
+ {
+ ZEN_ASSERT(BlockRanges.size() > 1);
+ std::vector<BlockRangeDescriptor> CollapsedBlockRanges;
+
+ auto BlockRangesIt = BlockRanges.begin();
+ CollapsedBlockRanges.push_back(*BlockRangesIt++);
+ for (; BlockRangesIt != BlockRanges.end(); BlockRangesIt++)
+ {
+ BlockRangeDescriptor& LastRange = CollapsedBlockRanges.back();
+
+ const uint64_t BothRangeSize = BlockRangesIt->RangeLength + LastRange.RangeLength;
+
+ const uint64_t Gap = BlockRangesIt->RangeStart - (LastRange.RangeStart + LastRange.RangeLength);
+ if (Gap <= Max(BothRangeSize / 16, AlwaysAcceptableGap))
+ {
+ LastRange.ChunkBlockIndexCount =
+ (BlockRangesIt->ChunkBlockIndexStart + BlockRangesIt->ChunkBlockIndexCount) - LastRange.ChunkBlockIndexStart;
+ LastRange.RangeLength = (BlockRangesIt->RangeStart + BlockRangesIt->RangeLength) - LastRange.RangeStart;
+ }
+ else
+ {
+ CollapsedBlockRanges.push_back(*BlockRangesIt);
+ }
+ }
+
+ return CollapsedBlockRanges;
+ };
+
+ uint64_t CalculateNextGap(std::span<const BlockRangeDescriptor> BlockRanges)
+ {
+ ZEN_ASSERT(BlockRanges.size() > 1);
+ uint64_t AcceptableGap = (uint64_t)-1;
+ for (size_t RangeIndex = 0; RangeIndex < BlockRanges.size() - 1; RangeIndex++)
+ {
+ const BlockRangeDescriptor& Range = BlockRanges[RangeIndex];
+ const BlockRangeDescriptor& NextRange = BlockRanges[RangeIndex + 1];
+
+ const uint64_t Gap = NextRange.RangeStart - (Range.RangeStart + Range.RangeLength);
+ AcceptableGap = Min(Gap, AcceptableGap);
+ }
+ AcceptableGap = RoundUp(AcceptableGap, 16u * 1024u);
+ return AcceptableGap;
+ };
+
+ std::optional<std::vector<BlockRangeDescriptor>> CalculateBlockRanges(uint32_t BlockIndex,
+ const ChunkBlockDescription& BlockDescription,
+ std::span<const uint32_t> BlockChunkIndexNeeded,
+ bool LimitToSingleRange,
+ const uint64_t ChunkStartOffsetInBlock,
+ const uint64_t TotalBlockSize,
+ uint64_t& OutTotalWantedChunksSize)
+ {
+ ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis");
+
+ std::vector<BlockRangeDescriptor> BlockRanges;
+ {
+ uint64_t CurrentOffset = ChunkStartOffsetInBlock;
+ uint32_t ChunkBlockIndex = 0;
+ uint32_t NeedBlockChunkIndexOffset = 0;
+ BlockRangeDescriptor NextRange{.BlockIndex = BlockIndex};
+ while (NeedBlockChunkIndexOffset < BlockChunkIndexNeeded.size() && ChunkBlockIndex < BlockDescription.ChunkRawHashes.size())
+ {
+ const uint32_t ChunkCompressedLength = BlockDescription.ChunkCompressedLengths[ChunkBlockIndex];
+ if (ChunkBlockIndex < BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
+ {
+ if (NextRange.RangeLength > 0)
+ {
+ BlockRanges.push_back(NextRange);
+ NextRange = {.BlockIndex = BlockIndex};
+ }
+ ChunkBlockIndex++;
+ CurrentOffset += ChunkCompressedLength;
+ }
+ else if (ChunkBlockIndex == BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
+ {
+ if (NextRange.RangeLength == 0)
+ {
+ NextRange.RangeStart = CurrentOffset;
+ NextRange.ChunkBlockIndexStart = ChunkBlockIndex;
+ }
+ NextRange.RangeLength += ChunkCompressedLength;
+ NextRange.ChunkBlockIndexCount++;
+ ChunkBlockIndex++;
+ CurrentOffset += ChunkCompressedLength;
+ NeedBlockChunkIndexOffset++;
+ }
+ else
+ {
+ ZEN_ASSERT(false);
+ }
+ }
+ if (NextRange.RangeLength > 0)
+ {
+ BlockRanges.push_back(NextRange);
+ }
+ }
+ ZEN_ASSERT(!BlockRanges.empty());
+
+ OutTotalWantedChunksSize =
+ std::accumulate(BlockRanges.begin(), BlockRanges.end(), uint64_t(0), [](uint64_t Current, const BlockRangeDescriptor& Range) {
+ return Current + Range.RangeLength;
+ });
+
+ double RangeWantedPercent = (OutTotalWantedChunksSize * 100.0) / TotalBlockSize;
+
+ if (BlockRanges.size() == 1)
+ {
+ ZEN_CONSOLE_VERBOSE("Range request of {} ({:.2f}%) using single range from block {} ({}) as is",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize));
+ return BlockRanges;
+ }
+
+ if (LimitToSingleRange)
+ {
+ const BlockRangeDescriptor MergedRange = MergeBlockRanges(BlockRanges);
+ const double RangeRequestedPercent = (MergedRange.RangeLength * 100.0) / TotalBlockSize;
+ const double WastedPercent = ((MergedRange.RangeLength - OutTotalWantedChunksSize) * 100.0) / MergedRange.RangeLength;
+ ZEN_CONSOLE_VERBOSE(
+ "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) limited to single block range {} ({:.2f}%) wasting "
+ "{:.2f}% ({})",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockRanges.size(),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ NiceBytes(MergedRange.RangeLength),
+ RangeRequestedPercent,
+ WastedPercent,
+ NiceBytes(MergedRange.RangeLength - OutTotalWantedChunksSize));
+ return MakeOptionalBlockRangeVector(TotalBlockSize, MergedRange);
+ }
+
+ if (RangeWantedPercent > FullBlockRangePercentLimit)
+ {
+ const BlockRangeDescriptor MergedRange = MergeBlockRanges(BlockRanges);
+ const double RangeRequestedPercent = (MergedRange.RangeLength * 100.0) / TotalBlockSize;
+ const double WastedPercent = ((MergedRange.RangeLength - OutTotalWantedChunksSize) * 100.0) / MergedRange.RangeLength;
+ ZEN_CONSOLE_VERBOSE(
+ "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) exceeds {}%. Merged to single block range {} "
+ "({:.2f}%) wasting {:.2f}% ({})",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockRanges.size(),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ FullBlockRangePercentLimit,
+ NiceBytes(MergedRange.RangeLength),
+ RangeRequestedPercent,
+ WastedPercent,
+ NiceBytes(MergedRange.RangeLength - OutTotalWantedChunksSize));
+ return MakeOptionalBlockRangeVector(TotalBlockSize, MergedRange);
+ }
+
+ std::vector<BlockRangeDescriptor> CollapsedBlockRanges = CollapseBlockRanges(16u * 1024u, BlockRanges);
+ while (GetBlockRangeLimitForRange(ForceMergeLimits, TotalBlockSize, CollapsedBlockRanges))
+ {
+ CollapsedBlockRanges = CollapseBlockRanges(CalculateNextGap(CollapsedBlockRanges), CollapsedBlockRanges);
+ }
+
+ const std::uint64_t WantedCollapsedSize =
+ std::accumulate(CollapsedBlockRanges.begin(),
+ CollapsedBlockRanges.end(),
+ uint64_t(0),
+ [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
+
+ const double CollapsedRangeRequestedPercent = (WantedCollapsedSize * 100.0) / TotalBlockSize;
+
+ {
+ const double WastedPercent = ((WantedCollapsedSize - OutTotalWantedChunksSize) * 100.0) / WantedCollapsedSize;
+
+ ZEN_CONSOLE_VERBOSE(
+ "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) collapsed to {} {:.2f}% using {} ranges wasting {:.2f}% "
+ "({})",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockRanges.size(),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ NiceBytes(WantedCollapsedSize),
+ CollapsedRangeRequestedPercent,
+ CollapsedBlockRanges.size(),
+ WastedPercent,
+ NiceBytes(WantedCollapsedSize - OutTotalWantedChunksSize));
+ return CollapsedBlockRanges;
+ }
+ };
+
class BufferedOpenFile
{
public:
@@ -5758,17 +5961,42 @@ namespace {
return Result;
}
+ enum EPartialBlockRequestMode
+ {
+ Off,
+ ZenCacheOnly,
+ Mixed,
+ All,
+ Invalid
+ };
+ static EPartialBlockRequestMode PartialBlockRequestModeFromString(const std::string_view ModeString)
+ {
+ switch (HashStringAsLowerDjb2(ModeString))
+ {
+ case HashStringDjb2("false"):
+ return EPartialBlockRequestMode::Off;
+ case HashStringDjb2("zencacheonly"):
+ return EPartialBlockRequestMode::ZenCacheOnly;
+ case HashStringDjb2("mixed"):
+ return EPartialBlockRequestMode::Mixed;
+ case HashStringDjb2("true"):
+ return EPartialBlockRequestMode::All;
+ default:
+ return EPartialBlockRequestMode::Invalid;
+ }
+ }
+
struct UpdateOptions
{
- std::filesystem::path SystemRootDir;
- std::filesystem::path ZenFolderPath;
- std::uint64_t LargeAttachmentSize = DefaultPreferredMultipartChunkSize * 4u;
- std::uint64_t PreferredMultipartChunkSize = DefaultPreferredMultipartChunkSize;
- bool AllowPartialBlockRequests = true;
- bool WipeTargetFolder = false;
- bool PrimeCacheOnly = false;
- bool EnableOtherDownloadsScavenging = true;
- bool EnableTargetFolderScavenging = true;
+ std::filesystem::path SystemRootDir;
+ std::filesystem::path ZenFolderPath;
+ std::uint64_t LargeAttachmentSize = DefaultPreferredMultipartChunkSize * 4u;
+ std::uint64_t PreferredMultipartChunkSize = DefaultPreferredMultipartChunkSize;
+ EPartialBlockRequestMode PartialBlockRequestMode = EPartialBlockRequestMode::Mixed;
+ bool WipeTargetFolder = false;
+ bool PrimeCacheOnly = false;
+ bool EnableOtherDownloadsScavenging = true;
+ bool EnableTargetFolderScavenging = true;
};
void UpdateFolder(StorageInstance& Storage,
@@ -5788,7 +6016,8 @@ namespace {
{
ZEN_TRACE_CPU("UpdateFolder");
- ZEN_ASSERT((!Options.PrimeCacheOnly) || (Options.PrimeCacheOnly && (!Options.AllowPartialBlockRequests)));
+ ZEN_ASSERT((!Options.PrimeCacheOnly) ||
+ (Options.PrimeCacheOnly && (Options.PartialBlockRequestMode == EPartialBlockRequestMode::Off)));
Stopwatch IndexTimer;
@@ -6558,33 +6787,20 @@ namespace {
return NeededBlockChunkIndexes;
};
- std::vector<uint32_t> CachedChunkBlockIndexes;
-
- struct BlockRangeDescriptor
- {
- uint32_t BlockIndex = (uint32_t)-1;
- uint64_t RangeStart = 0;
- uint64_t RangeLength = 0;
- uint32_t ChunkBlockIndexStart = 0;
- uint32_t ChunkBlockIndexCount = 0;
- };
- std::vector<BlockRangeDescriptor> BlockRangeWorks;
-
- std::vector<uint32_t> FullBlockWorks;
+ std::vector<uint32_t> CachedChunkBlockIndexes;
+ std::vector<uint32_t> FetchBlockIndexes;
+ std::vector<std::vector<uint32_t>> AllBlockChunkIndexNeeded;
for (uint32_t BlockIndex = 0; BlockIndex < BlockCount; BlockIndex++)
{
const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
- const std::vector<uint32_t> BlockChunkIndexNeeded = GetNeededChunkBlockIndexes(BlockDescription);
+ std::vector<uint32_t> BlockChunkIndexNeeded = GetNeededChunkBlockIndexes(BlockDescription);
if (!BlockChunkIndexNeeded.empty())
{
if (Options.PrimeCacheOnly)
{
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
+ FetchBlockIndexes.push_back(BlockIndex);
}
else
{
@@ -6603,188 +6819,13 @@ namespace {
UsingCachedBlock = true;
}
}
-
if (!UsingCachedBlock)
{
- bool WantsToDoPartialBlockDownload = BlockChunkIndexNeeded.size() < BlockDescription.ChunkRawHashes.size();
- bool CanDoPartialBlockDownload =
- (BlockDescription.HeaderSize > 0) &&
- (BlockDescription.ChunkCompressedLengths.size() == BlockDescription.ChunkRawHashes.size());
- if (Options.AllowPartialBlockRequests && WantsToDoPartialBlockDownload && CanDoPartialBlockDownload)
- {
- std::vector<BlockRangeDescriptor> BlockRanges;
-
- ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis");
-
- uint32_t NeedBlockChunkIndexOffset = 0;
- uint32_t ChunkBlockIndex = 0;
- uint32_t CurrentOffset =
- gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
-
- const uint64_t TotalBlockSize = std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
- BlockDescription.ChunkCompressedLengths.end(),
- std::uint64_t(CurrentOffset));
- BlockRangeDescriptor NextRange{.BlockIndex = BlockIndex};
- while (NeedBlockChunkIndexOffset < BlockChunkIndexNeeded.size() &&
- ChunkBlockIndex < BlockDescription.ChunkRawHashes.size())
- {
- const uint32_t ChunkCompressedLength = BlockDescription.ChunkCompressedLengths[ChunkBlockIndex];
- if (ChunkBlockIndex < BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
- {
- if (NextRange.RangeLength > 0)
- {
- BlockRanges.push_back(NextRange);
- NextRange = {.BlockIndex = BlockIndex};
- }
- ChunkBlockIndex++;
- CurrentOffset += ChunkCompressedLength;
- }
- else if (ChunkBlockIndex == BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
- {
- if (NextRange.RangeLength == 0)
- {
- NextRange.RangeStart = CurrentOffset;
- NextRange.ChunkBlockIndexStart = ChunkBlockIndex;
- }
- NextRange.RangeLength += ChunkCompressedLength;
- NextRange.ChunkBlockIndexCount++;
- ChunkBlockIndex++;
- CurrentOffset += ChunkCompressedLength;
- NeedBlockChunkIndexOffset++;
- }
- else
- {
- ZEN_ASSERT(false);
- }
- }
- if (NextRange.RangeLength > 0)
- {
- BlockRanges.push_back(NextRange);
- }
-
- ZEN_ASSERT(!BlockRanges.empty());
-
- std::vector<BlockRangeDescriptor> CollapsedBlockRanges;
- auto It = BlockRanges.begin();
- CollapsedBlockRanges.push_back(*It++);
- while (It != BlockRanges.end())
- {
- BlockRangeDescriptor& LastRange = CollapsedBlockRanges.back();
- uint64_t Slack = It->RangeStart - (LastRange.RangeStart + LastRange.RangeLength);
- uint64_t BothRangeSize = It->RangeLength + LastRange.RangeLength;
- if (Slack <= Max(BothRangeSize / 8, 64u * 1024u)) // Made up heuristic - we'll see how it pans out
- {
- LastRange.ChunkBlockIndexCount =
- (It->ChunkBlockIndexStart + It->ChunkBlockIndexCount) - LastRange.ChunkBlockIndexStart;
- LastRange.RangeLength = (It->RangeStart + It->RangeLength) - LastRange.RangeStart;
- }
- else
- {
- CollapsedBlockRanges.push_back(*It);
- }
- ++It;
- }
-
- const std::uint64_t WantedSize = std::accumulate(
- CollapsedBlockRanges.begin(),
- CollapsedBlockRanges.end(),
- uint64_t(0),
- [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
- ZEN_ASSERT(WantedSize <= TotalBlockSize);
- if (WantedSize > ((TotalBlockSize * 95) / 100))
- {
- ZEN_CONSOLE_VERBOSE("Using more than 95% ({}) of block {} ({}), requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize));
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 9) / 10)) && CollapsedBlockRanges.size() > 1)
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 90% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 8) / 10)) && (CollapsedBlockRanges.size() > 16))
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 80% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 7) / 10)) && (CollapsedBlockRanges.size() > 48))
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 70% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 6) / 10)) && (CollapsedBlockRanges.size() > 64))
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 60% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else
- {
- if (WantedSize > ((TotalBlockSize * 5) / 10))
- {
- ZEN_CONSOLE_VERBOSE("Using {}% ({}) of block {} ({}) using {} requests, requesting partial block",
- (WantedSize * 100) / TotalBlockSize,
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- }
- TotalRequestCount += CollapsedBlockRanges.size();
- TotalPartWriteCount += CollapsedBlockRanges.size();
-
- BlockRangeWorks.insert(BlockRangeWorks.end(), CollapsedBlockRanges.begin(), CollapsedBlockRanges.end());
- }
- }
- else
- {
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
+ FetchBlockIndexes.push_back(BlockIndex);
}
}
}
- else
- {
- ZEN_CONSOLE_VERBOSE("Skipping block {} due to cache reuse", BlockDescriptions[BlockIndex].BlockHash);
- }
+ AllBlockChunkIndexNeeded.emplace_back(std::move(BlockChunkIndexNeeded));
}
struct BlobsExistsResult
@@ -6802,18 +6843,12 @@ namespace {
tsl::robin_set<IoHash> BlobHashesSet;
- BlobHashesSet.reserve(LooseChunkHashWorks.size() + FullBlockWorks.size());
+ BlobHashesSet.reserve(LooseChunkHashWorks.size() + FetchBlockIndexes.size());
for (LooseChunkHashWorkData& LooseChunkHashWork : LooseChunkHashWorks)
{
BlobHashesSet.insert(RemoteContent.ChunkedContent.ChunkHashes[LooseChunkHashWork.RemoteChunkIndex]);
}
- for (const BlockRangeDescriptor& BlockRange : BlockRangeWorks)
- {
- const uint32_t BlockIndex = BlockRange.BlockIndex;
- const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
- BlobHashesSet.insert(BlockDescription.BlockHash);
- }
- for (uint32_t BlockIndex : FullBlockWorks)
+ for (uint32_t BlockIndex : FetchBlockIndexes)
{
const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
BlobHashesSet.insert(BlockDescription.BlockHash);
@@ -6847,6 +6882,162 @@ namespace {
}
}
+ std::vector<BlockRangeDescriptor> BlockRangeWorks;
+ std::vector<uint32_t> FullBlockWorks;
+ {
+ Stopwatch Timer;
+
+ std::vector<uint32_t> PartialBlockIndexes;
+
+ for (uint32_t BlockIndex : FetchBlockIndexes)
+ {
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+
+ const std::vector<uint32_t> BlockChunkIndexNeeded = std::move(AllBlockChunkIndexNeeded[BlockIndex]);
+ if (!BlockChunkIndexNeeded.empty())
+ {
+ bool WantsToDoPartialBlockDownload = BlockChunkIndexNeeded.size() < BlockDescription.ChunkRawHashes.size();
+ bool CanDoPartialBlockDownload =
+ (BlockDescription.HeaderSize > 0) &&
+ (BlockDescription.ChunkCompressedLengths.size() == BlockDescription.ChunkRawHashes.size());
+
+ bool AllowedToDoPartialRequest = false;
+ bool BlockExistInCache = ExistsResult.ExistingBlobs.contains(BlockDescription.BlockHash);
+ switch (Options.PartialBlockRequestMode)
+ {
+ case EPartialBlockRequestMode::Off:
+ break;
+ case EPartialBlockRequestMode::ZenCacheOnly:
+ AllowedToDoPartialRequest = BlockExistInCache;
+ break;
+ case EPartialBlockRequestMode::Mixed:
+ case EPartialBlockRequestMode::All:
+ AllowedToDoPartialRequest = true;
+ break;
+ default:
+ ZEN_ASSERT(false);
+ break;
+ }
+
+ const uint32_t ChunkStartOffsetInBlock =
+ gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
+
+ const uint64_t TotalBlockSize = std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
+ BlockDescription.ChunkCompressedLengths.end(),
+ std::uint64_t(ChunkStartOffsetInBlock));
+
+ if (AllowedToDoPartialRequest && WantsToDoPartialBlockDownload && CanDoPartialBlockDownload)
+ {
+ ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis");
+
+ bool LimitToSingleRange =
+ BlockExistInCache ? false : Options.PartialBlockRequestMode == EPartialBlockRequestMode::Mixed;
+ uint64_t TotalWantedChunksSize = 0;
+ std::optional<std::vector<BlockRangeDescriptor>> MaybeBlockRanges =
+ CalculateBlockRanges(BlockIndex,
+ BlockDescription,
+ BlockChunkIndexNeeded,
+ LimitToSingleRange,
+ ChunkStartOffsetInBlock,
+ TotalBlockSize,
+ TotalWantedChunksSize);
+ ZEN_ASSERT(TotalWantedChunksSize <= TotalBlockSize);
+
+ if (MaybeBlockRanges.has_value())
+ {
+ const std::vector<BlockRangeDescriptor>& BlockRanges = MaybeBlockRanges.value();
+ ZEN_ASSERT(!BlockRanges.empty());
+ BlockRangeWorks.insert(BlockRangeWorks.end(), BlockRanges.begin(), BlockRanges.end());
+ TotalRequestCount += BlockRanges.size();
+ TotalPartWriteCount += BlockRanges.size();
+
+ uint64_t RequestedSize = std::accumulate(
+ BlockRanges.begin(),
+ BlockRanges.end(),
+ uint64_t(0),
+ [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
+ PartialBlockIndexes.push_back(BlockIndex);
+
+ if (RequestedSize > TotalWantedChunksSize)
+ {
+ ZEN_CONSOLE_VERBOSE("Requesting {} chunks ({}) from block {} ({}) using {} requests (extra bytes {})",
+ BlockChunkIndexNeeded.size(),
+ NiceBytes(RequestedSize),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ BlockRanges.size(),
+ NiceBytes(RequestedSize - TotalWantedChunksSize));
+ }
+ }
+ else
+ {
+ FullBlockWorks.push_back(BlockIndex);
+ TotalRequestCount++;
+ TotalPartWriteCount++;
+ }
+ }
+ else
+ {
+ FullBlockWorks.push_back(BlockIndex);
+ TotalRequestCount++;
+ TotalPartWriteCount++;
+ }
+ }
+ }
+
+ if (!PartialBlockIndexes.empty())
+ {
+ uint64_t TotalFullBlockRequestBytes = 0;
+ for (uint32_t BlockIndex : FullBlockWorks)
+ {
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+ uint32_t CurrentOffset =
+ gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
+
+ TotalFullBlockRequestBytes += std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
+ BlockDescription.ChunkCompressedLengths.end(),
+ std::uint64_t(CurrentOffset));
+ }
+
+ uint64_t TotalPartialBlockBytes = 0;
+ for (uint32_t BlockIndex : PartialBlockIndexes)
+ {
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+ uint32_t CurrentOffset =
+ gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
+
+ TotalPartialBlockBytes += std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
+ BlockDescription.ChunkCompressedLengths.end(),
+ std::uint64_t(CurrentOffset));
+ }
+
+ uint64_t NonPartialTotalBlockBytes = TotalFullBlockRequestBytes + TotalPartialBlockBytes;
+
+ const uint64_t TotalPartialBlockRequestBytes =
+ std::accumulate(BlockRangeWorks.begin(),
+ BlockRangeWorks.end(),
+ uint64_t(0),
+ [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
+ uint64_t TotalExtraPartialBlocksRequests = BlockRangeWorks.size() - PartialBlockIndexes.size();
+
+ uint64_t TotalSavedBlocksSize = TotalPartialBlockBytes - TotalPartialBlockRequestBytes;
+ double SavedSizePercent = (TotalSavedBlocksSize * 100.0) / NonPartialTotalBlockBytes;
+
+ if (!IsQuiet)
+ {
+ ZEN_CONSOLE(
+ "Analisys of partial block requests saves download of {} out of {} ({:.1f}%) using {} extra "
+ "requests. Completed in {}",
+ NiceBytes(TotalSavedBlocksSize),
+ NiceBytes(NonPartialTotalBlockBytes),
+ SavedSizePercent,
+ TotalExtraPartialBlocksRequests,
+ NiceTimeSpanMs(ExistsResult.ElapsedTimeMs));
+ }
+ // exit(0);
+ }
+ }
+
BufferedWriteFileCache WriteCache;
for (uint32_t ScavengeOpIndex = 0; ScavengeOpIndex < ScavengeCopyOperations.size(); ScavengeOpIndex++)
@@ -7204,7 +7395,7 @@ namespace {
Storage.BuildCacheStorage->PutBuildBlob(
BuildId,
ChunkHash,
- BuildBlob.GetContentType(),
+ ZenContentType::kCompressedBinary,
CompositeBuffer(SharedBuffer(BuildBlob)));
}
if (!BuildBlob)
@@ -7792,7 +7983,7 @@ namespace {
{
Storage.BuildCacheStorage->PutBuildBlob(BuildId,
BlockDescription.BlockHash,
- BlockBuffer.GetContentType(),
+ ZenContentType::kCompressedBinary,
CompositeBuffer(SharedBuffer(BlockBuffer)));
}
}
@@ -9396,17 +9587,17 @@ namespace {
struct DownloadOptions
{
- std::filesystem::path SystemRootDir;
- std::filesystem::path ZenFolderPath;
- bool AllowMultiparts = true;
- bool AllowPartialBlockRequests = true;
- bool CleanTargetFolder = false;
- bool PostDownloadVerify = false;
- bool PrimeCacheOnly = false;
- bool EnableOtherDownloadsScavenging = true;
- bool EnableTargetFolderScavenging = true;
- std::string IncludeWildcard;
- std::string ExcludeWildcard;
+ std::filesystem::path SystemRootDir;
+ std::filesystem::path ZenFolderPath;
+ bool AllowMultiparts = true;
+ EPartialBlockRequestMode PartialBlockRequestMode = EPartialBlockRequestMode::Mixed;
+ bool CleanTargetFolder = false;
+ bool PostDownloadVerify = false;
+ bool PrimeCacheOnly = false;
+ bool EnableOtherDownloadsScavenging = true;
+ bool EnableTargetFolderScavenging = true;
+ std::string IncludeWildcard;
+ std::string ExcludeWildcard;
};
void DownloadFolder(StorageInstance& Storage,
@@ -9433,7 +9624,8 @@ namespace {
auto EndProgress =
MakeGuard([&]() { ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::StepCount, TaskSteps::StepCount); });
- ZEN_ASSERT((!Options.PrimeCacheOnly) || (Options.PrimeCacheOnly && (!Options.AllowPartialBlockRequests)));
+ ZEN_ASSERT((!Options.PrimeCacheOnly) ||
+ (Options.PrimeCacheOnly && (Options.PartialBlockRequestMode == EPartialBlockRequestMode::Off)));
Stopwatch DownloadTimer;
@@ -9601,7 +9793,7 @@ namespace {
.ZenFolderPath = Options.ZenFolderPath,
.LargeAttachmentSize = LargeAttachmentSize,
.PreferredMultipartChunkSize = PreferredMultipartChunkSize,
- .AllowPartialBlockRequests = Options.AllowPartialBlockRequests,
+ .PartialBlockRequestMode = Options.PartialBlockRequestMode,
.WipeTargetFolder = Options.CleanTargetFolder,
.PrimeCacheOnly = Options.PrimeCacheOnly,
.EnableOtherDownloadsScavenging = Options.EnableOtherDownloadsScavenging,
@@ -9988,81 +10180,9 @@ BuildsCommand::BuildsCommand()
"<usesparsefiles>");
};
- auto AddAuthOptions = [this](cxxopts::Options& Ops) {
- // Direct access token (may expire)
- Ops.add_option("auth-token",
- "",
- "access-token",
- "Cloud/Builds Storage access token",
- cxxopts::value(m_AccessToken),
- "<accesstoken>");
- Ops.add_option("auth-token",
- "",
- "access-token-env",
- "Name of environment variable that holds the cloud/builds Storage access token",
- cxxopts::value(m_AccessTokenEnv)->default_value(DefaultAccessTokenEnvVariableName),
- "<envvariable>");
- Ops.add_option("auth-token",
- "",
- "access-token-path",
- "Path to json file that holds the cloud/builds Storage access token",
- cxxopts::value(m_AccessTokenPath),
- "<filepath>");
-
- // Auth manager token encryption
- Ops.add_option("security",
- "",
- "encryption-aes-key",
- "256 bit AES encryption key",
- cxxopts::value<std::string>(m_EncryptionKey),
- "");
- Ops.add_option("security",
- "",
- "encryption-aes-iv",
- "128 bit AES encryption initialization vector",
- cxxopts::value<std::string>(m_EncryptionIV),
- "");
+ auto AddCloudOptions = [this](cxxopts::Options& Ops) {
+ m_AuthOptions.AddOptions(Ops);
- // OpenId acccess token
- Ops.add_option("openid",
- "",
- "openid-provider-name",
- "Open ID provider name",
- cxxopts::value<std::string>(m_OpenIdProviderName),
- "Default");
- Ops.add_option("openid", "", "openid-provider-url", "Open ID provider url", cxxopts::value<std::string>(m_OpenIdProviderUrl), "");
- Ops.add_option("openid", "", "openid-client-id", "Open ID client id", cxxopts::value<std::string>(m_OpenIdClientId), "");
- Ops.add_option("openid",
- "",
- "openid-refresh-token",
- "Open ID refresh token",
- cxxopts::value<std::string>(m_OpenIdRefreshToken),
- "");
-
- // OAuth acccess token
- Ops.add_option("oauth", "", "oauth-url", "OAuth provier url", cxxopts::value<std::string>(m_OAuthUrl)->default_value(""), "");
- Ops.add_option("oauth",
- "",
- "oauth-clientid",
- "OAuth client id",
- cxxopts::value<std::string>(m_OAuthClientId)->default_value(""),
- "");
- Ops.add_option("oauth",
- "",
- "oauth-clientsecret",
- "OAuth client secret",
- cxxopts::value<std::string>(m_OAuthClientSecret)->default_value(""),
- "");
- Ops.add_option("auth",
- "",
- "oidctoken-exe-path",
- "Path to OidcToken executable",
- cxxopts::value<std::string>(m_OidcTokenAuthExecutablePath)->default_value(""),
- "");
- };
-
- auto AddCloudOptions = [this, &AddAuthOptions](cxxopts::Options& Ops) {
- AddAuthOptions(Ops);
Ops.add_option("cloud build", "", "override-host", "Cloud Builds URL", cxxopts::value(m_OverrideHost), "<override-host>");
Ops.add_option("cloud build",
"",
@@ -10154,6 +10274,31 @@ BuildsCommand::BuildsCommand()
"<excludewildcard>");
};
+ auto AddMultipartOptions = [this](cxxopts::Options& Ops) {
+ Ops.add_option("",
+ "",
+ "allow-multipart",
+ "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
+ cxxopts::value(m_AllowMultiparts),
+ "<allowmultipart>");
+ };
+
+ auto AddPartialBlockRequestOptions = [this](cxxopts::Options& Ops) {
+ Ops.add_option("",
+ "",
+ "allow-partial-block-requests",
+ "Allow request for partial chunk blocks.\n"
+ " false = only full block requests allowed\n"
+ " mixed = multiple partial block ranges requests per block allowed to zen cache, single partial block range "
+ "request per block to host\n"
+ " zencacheonly = multiple partial block ranges requests per block allowed to zen cache, only full block requests "
+ "allowed to host\n"
+ " true = multiple partial block ranges requests per block allowed to zen cache and host\n"
+ "Defaults to 'mixed'.",
+ cxxopts::value(m_AllowPartialBlockRequests),
+ "<allowpartialblockrequests>");
+ };
+
m_Options.add_option("",
"v",
"verb",
@@ -10256,12 +10401,9 @@ BuildsCommand::BuildsCommand()
"Percent of an existing block that must be relevant for it to be resused. Defaults to 85.",
cxxopts::value(m_BlockReuseMinPercentLimit),
"<minreuse>");
- m_UploadOptions.add_option("",
- "",
- "allow-multipart",
- "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
- cxxopts::value(m_AllowMultiparts),
- "<allowmultipart>");
+
+ AddMultipartOptions(m_UploadOptions);
+
m_UploadOptions.add_option("",
"",
"manifest-path",
@@ -10326,18 +10468,9 @@ BuildsCommand::BuildsCommand()
"Force download of all content by ignoring any existing local content",
cxxopts::value(m_Force),
"<force>");
- m_DownloadOptions.add_option("",
- "",
- "allow-multipart",
- "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
- cxxopts::value(m_AllowMultiparts),
- "<allowmultipart>");
- m_DownloadOptions.add_option("",
- "",
- "allow-partial-block-requests",
- "Allow request for partial chunk blocks. Defaults to true.",
- cxxopts::value(m_AllowPartialBlockRequests),
- "<allowpartialblockrequests>");
+ AddMultipartOptions(m_DownloadOptions);
+
+ AddPartialBlockRequestOptions(m_DownloadOptions);
m_DownloadOptions
.add_option("", "", "verify", "Enable post download verify of all tracked files", cxxopts::value(m_PostDownloadVerify), "<verify>");
@@ -10404,18 +10537,8 @@ BuildsCommand::BuildsCommand()
AddWorkerOptions(m_TestOptions);
m_TestOptions.add_options()("h,help", "Print help");
m_TestOptions.add_option("", "l", "local-path", "Root file system folder used as base", cxxopts::value(m_Path), "<local-path>");
- m_TestOptions.add_option("",
- "",
- "allow-multipart",
- "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
- cxxopts::value(m_AllowMultiparts),
- "<allowmultipart>");
- m_TestOptions.add_option("",
- "",
- "allow-partial-block-requests",
- "Allow request for partial chunk blocks. Defaults to true.",
- cxxopts::value(m_AllowPartialBlockRequests),
- "<allowpartialblockrequests>");
+ AddMultipartOptions(m_TestOptions);
+ AddPartialBlockRequestOptions(m_TestOptions);
m_TestOptions.add_option("",
"",
"enable-scavenge",
@@ -10529,13 +10652,12 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
return;
}
- std::filesystem::path SystemRootDir;
- auto ParseSystemOptions = [&]() {
- if (m_SystemRootDir.empty())
- {
- m_SystemRootDir = PickDefaultSystemRootDirectory();
- }
- MakeSafeAbsolutePathÍnPlace(m_SystemRootDir);
+ auto ParseSystemOptions = [&]() {
+ if (m_SystemRootDir.empty())
+ {
+ m_SystemRootDir = PickDefaultSystemRootDirectory();
+ }
+ MakeSafeAbsolutePathÍnPlace(m_SystemRootDir);
};
ParseSystemOptions();
@@ -10556,7 +10678,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
throw OptionParseException(fmt::format("'--buildid' ('{}') conflicts with '--url' ('{}')", m_BuildId, m_Url),
SubOption->help());
}
- if (!ParseCloudUrl(m_Url, m_Host, m_Namespace, m_Bucket, m_BuildId))
+ if (!ParseBuildStorageUrl(m_Url, m_Host, m_Namespace, m_Bucket, m_BuildId))
{
throw OptionParseException("'--url' ('{}') is malformed, it does not match the Cloud Artifact URL format",
SubOption->help());
@@ -10587,130 +10709,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
MakeSafeAbsolutePathÍnPlace(m_StoragePath);
};
- std::unique_ptr<AuthMgr> Auth;
- HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient",
- .AssumeHttp2 = m_AssumeHttp2,
- .AllowResume = true,
- .RetryCount = 2};
-
- auto CreateAuthMgr = [&]() {
- ZEN_ASSERT(!m_SystemRootDir.empty());
- if (!Auth)
- {
- if (m_EncryptionKey.empty())
- {
- m_EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456";
- if (!IsQuiet)
- {
- ZEN_CONSOLE_WARN("Using default encryption key");
- }
- }
-
- if (m_EncryptionIV.empty())
- {
- m_EncryptionIV = "0123456789abcdef";
- if (!IsQuiet)
- {
- ZEN_CONSOLE_WARN("Using default encryption initialization vector");
- }
- }
-
- AuthConfig AuthMgrConfig = {.RootDirectory = m_SystemRootDir / "auth",
- .EncryptionKey = AesKey256Bit::FromString(m_EncryptionKey),
- .EncryptionIV = AesIV128Bit::FromString(m_EncryptionIV)};
- if (!AuthMgrConfig.EncryptionKey.IsValid())
- {
- throw OptionParseException(fmt::format("'--encryption-aes-key' ('{}') is malformed", m_EncryptionKey), SubOption->help());
- }
- if (!AuthMgrConfig.EncryptionIV.IsValid())
- {
- throw OptionParseException(fmt::format("'--encryption-aes-iv' ('{}') is malformed", m_EncryptionIV), SubOption->help());
- }
- Auth = AuthMgr::Create(AuthMgrConfig);
- }
- };
-
- auto ParseAuthOptions = [&]() {
- if (!m_OpenIdProviderUrl.empty() && !m_OpenIdClientId.empty())
- {
- CreateAuthMgr();
- std::string ProviderName = m_OpenIdProviderName.empty() ? "Default" : m_OpenIdProviderName;
- Auth->AddOpenIdProvider({.Name = ProviderName, .Url = m_OpenIdProviderUrl, .ClientId = m_OpenIdClientId});
- if (!m_OpenIdRefreshToken.empty())
- {
- Auth->AddOpenIdToken({.ProviderName = ProviderName, .RefreshToken = m_OpenIdRefreshToken});
- }
- }
-
- auto GetEnvAccessToken = [](const std::string& AccessTokenEnv) -> std::string {
- if (!AccessTokenEnv.empty())
- {
- return GetEnvVariable(AccessTokenEnv);
- }
- return {};
- };
-
- auto FindOidcTokenExePath = [](const std::string& OidcTokenAuthExecutablePath) -> std::filesystem::path {
- if (OidcTokenAuthExecutablePath.empty())
- {
- const std::string OidcExecutableName = "OidcToken" ZEN_EXE_SUFFIX_LITERAL;
- std::filesystem::path OidcTokenPath = (GetRunningExecutablePath().parent_path() / OidcExecutableName).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- else
- {
- std::filesystem::path OidcTokenPath = std::filesystem::absolute(StringToPath(OidcTokenAuthExecutablePath)).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- return {};
- };
-
- if (!m_AccessToken.empty())
- {
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(m_AccessToken);
- }
- else if (!m_AccessTokenPath.empty())
- {
- MakeSafeAbsolutePathÍnPlace(m_AccessTokenPath);
- std::string ResolvedAccessToken = ReadAccessTokenFromFile(m_AccessTokenPath);
- if (!ResolvedAccessToken.empty())
- {
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
- }
- }
- else if (!m_OAuthUrl.empty())
- {
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOAuthClientCredentials(
- {.Url = m_OAuthUrl, .ClientId = m_OAuthClientId, .ClientSecret = m_OAuthClientSecret});
- }
- else if (!m_OpenIdProviderName.empty())
- {
- CreateAuthMgr();
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOpenIdProvider(*Auth, m_OpenIdProviderName);
- }
- else if (std::string ResolvedAccessToken = GetEnvAccessToken(m_AccessTokenEnv); !ResolvedAccessToken.empty())
- {
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
- }
- else if (std::filesystem::path OidcTokenExePath = FindOidcTokenExePath(m_OidcTokenAuthExecutablePath); !OidcTokenExePath.empty())
- {
- const std::string& CloudHost = m_OverrideHost.empty() ? m_Host : m_OverrideHost;
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOidcTokenExecutable(OidcTokenExePath, CloudHost, IsQuiet);
- }
-
- if (!ClientSettings.AccessTokenProvider)
- {
- CreateAuthMgr();
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromDefaultOpenIdProvider(*Auth);
- }
- };
-
auto ParseOutputOptions = [&]() {
if (m_Verbose && m_Quiet)
{
@@ -10749,6 +10747,12 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
};
ParseOutputOptions();
+ std::unique_ptr<AuthMgr> Auth;
+ HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient",
+ .AssumeHttp2 = m_AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 2};
+
auto CreateBuildStorage = [&](BuildStorage::Statistics& StorageStats,
BuildStorageCache::Statistics& StorageCacheStats,
const std::filesystem::path& TempPath,
@@ -10766,123 +10770,73 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!m_Host.empty() || !m_OverrideHost.empty())
{
- ParseAuthOptions();
+ m_AuthOptions
+ .ParseOptions(*SubOption, m_SystemRootDir, ClientSettings, m_OverrideHost.empty() ? m_Host : m_OverrideHost, Auth, IsQuiet);
}
std::string CloudHost;
- auto TestCacheEndpoint = [](std::string_view BaseUrl, const bool AssumeHttp2) -> std::pair<bool, std::string> {
- HttpClientSettings TestClientSettings{.LogCategory = "httpcacheclient",
- .ConnectTimeout = std::chrono::milliseconds{1000},
- .Timeout = std::chrono::milliseconds{2000},
- .AssumeHttp2 = AssumeHttp2,
- .AllowResume = true,
- .RetryCount = 0};
- HttpClient TestHttpClient(BaseUrl, TestClientSettings);
- HttpClient::Response TestResponse = TestHttpClient.Get("/status/builds");
- if (TestResponse.IsSuccess())
- {
- return {true, ""};
- }
- return {false, TestResponse.ErrorMessage("")};
- };
-
if (!m_Host.empty())
{
if (m_OverrideHost.empty() || m_ZenCacheHost.empty())
{
- HttpClient DiscoveryHttpClient(m_Host, ClientSettings);
- HttpClient::Response ServerInfoResponse =
- DiscoveryHttpClient.Get("/api/v1/status/servers", HttpClient::Accept(HttpContentType::kJSON));
- if (!ServerInfoResponse.IsSuccess())
- {
- ServerInfoResponse.ThrowError(fmt::format("Failed to get list of servers from discovery url '{}'", m_Host));
- }
-
- std::string_view JsonResponse = ServerInfoResponse.AsText();
- CbObject ResponseObjectView = LoadCompactBinaryFromJson(JsonResponse).AsObject();
-
- auto TestHostEndpoint = [](std::string_view BaseUrl, const bool AssumeHttp2) -> std::pair<bool, std::string> {
- HttpClientSettings TestClientSettings{.LogCategory = "httpbuildsclient",
- .ConnectTimeout = std::chrono::milliseconds{1000},
- .Timeout = std::chrono::milliseconds{2000},
- .AssumeHttp2 = AssumeHttp2,
- .AllowResume = true,
- .RetryCount = 0};
-
- HttpClient TestHttpClient(BaseUrl, TestClientSettings);
- HttpClient::Response TestResponse = TestHttpClient.Get("/health/live");
- if (TestResponse.IsSuccess())
- {
- return {true, ""};
- }
- return {false, TestResponse.ErrorMessage("")};
- };
+ JupiterServerDiscovery Response = DiscoverJupiterEndpoints(m_Host, ClientSettings);
if (m_OverrideHost.empty())
{
- CbArrayView ServerEndpointsArray = ResponseObjectView["serverEndpoints"sv].AsArrayView();
- std::uint64_t ServerCount = ServerEndpointsArray.Num();
- if (ServerCount == 0)
+ if (Response.ServerEndPoints.empty())
{
throw std::runtime_error(fmt::format("Failed to find any builds hosts at {}", m_Host));
}
- for (CbFieldView ServerEndpointView : ServerEndpointsArray)
+ for (const JupiterServerDiscovery::EndPoint& ServerEndpoint : Response.ServerEndPoints)
{
- CbObjectView ServerEndpointObject = ServerEndpointView.AsObjectView();
-
- std::string_view BaseUrl = ServerEndpointObject["baseUrl"sv].AsString();
- if (!BaseUrl.empty())
+ if (!ServerEndpoint.BaseUrl.empty())
{
- const bool AssumeHttp2 = ServerEndpointObject["assumeHttp2"sv].AsBool(false);
- std::string_view Name = ServerEndpointObject["name"sv].AsString();
- if (auto TestResult = TestHostEndpoint(BaseUrl, AssumeHttp2); TestResult.first)
+ if (JupiterEndpointTestResult TestResult =
+ TestJupiterEndpoint(ServerEndpoint.BaseUrl, ServerEndpoint.AssumeHttp2);
+ TestResult.Success)
{
- CloudHost = BaseUrl;
- m_AssumeHttp2 = AssumeHttp2;
- BuildStorageName = Name;
+ CloudHost = ServerEndpoint.BaseUrl;
+ m_AssumeHttp2 = ServerEndpoint.AssumeHttp2;
+ BuildStorageName = ServerEndpoint.Name;
break;
}
else
{
- ZEN_DEBUG("Unable to reach host {}. Reason: {}", BaseUrl, TestResult.second);
+ ZEN_DEBUG("Unable to reach host {}. Reason: {}", ServerEndpoint.BaseUrl, TestResult.FailureReason);
}
}
}
if (CloudHost.empty())
{
- throw std::runtime_error(
- fmt::format("Failed to find any usable builds hosts out of {} using {}", ServerCount, m_Host));
+ throw std::runtime_error(fmt::format("Failed to find any usable builds hosts out of {} using {}",
+ Response.ServerEndPoints.size(),
+ m_Host));
}
}
- else if (auto TestResult = TestHostEndpoint(m_OverrideHost, m_AssumeHttp2); TestResult.first)
+ else if (JupiterEndpointTestResult TestResult = TestJupiterEndpoint(m_OverrideHost, m_AssumeHttp2); TestResult.Success)
{
CloudHost = m_OverrideHost;
}
else
{
- throw std::runtime_error(fmt::format("Host {} could not be reached. Reason: {}", m_OverrideHost, TestResult.second));
+ throw std::runtime_error(
+ fmt::format("Host {} could not be reached. Reason: {}", m_OverrideHost, TestResult.FailureReason));
}
if (m_ZenCacheHost.empty())
{
- CbArrayView CacheEndpointsArray = ResponseObjectView["cacheEndpoints"sv].AsArrayView();
- std::uint64_t CacheCount = CacheEndpointsArray.Num();
- for (CbFieldView CacheEndpointView : CacheEndpointsArray)
+ for (const JupiterServerDiscovery::EndPoint& CacheEndpoint : Response.CacheEndPoints)
{
- CbObjectView CacheEndpointObject = CacheEndpointView.AsObjectView();
-
- std::string_view BaseUrl = CacheEndpointObject["baseUrl"sv].AsString();
- if (!BaseUrl.empty())
+ if (!CacheEndpoint.BaseUrl.empty())
{
- const bool AssumeHttp2 = CacheEndpointObject["assumeHttp2"sv].AsBool(false);
- std::string_view Name = CacheEndpointObject["name"sv].AsString();
-
- if (auto TestResult = TestCacheEndpoint(BaseUrl, AssumeHttp2); TestResult.first)
+ if (ZenCacheEndpointTestResult TestResult =
+ TestZenCacheEndpoint(CacheEndpoint.BaseUrl, CacheEndpoint.AssumeHttp2);
+ TestResult.Success)
{
- m_ZenCacheHost = BaseUrl;
- CacheAssumeHttp2 = AssumeHttp2;
- BuildCacheName = Name;
+ m_ZenCacheHost = CacheEndpoint.BaseUrl;
+ CacheAssumeHttp2 = CacheEndpoint.AssumeHttp2;
+ BuildCacheName = CacheEndpoint.Name;
break;
}
}
@@ -10897,7 +10851,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
std::string ZenServerLocalHostUrl =
fmt::format("http://127.0.0.1:{}", Entry.EffectiveListenPort.load());
- if (auto TestResult = TestCacheEndpoint(ZenServerLocalHostUrl, false); TestResult.first)
+ if (ZenCacheEndpointTestResult TestResult = TestZenCacheEndpoint(ZenServerLocalHostUrl, false);
+ TestResult.Success)
{
m_ZenCacheHost = ZenServerLocalHostUrl;
CacheAssumeHttp2 = false;
@@ -10908,11 +10863,13 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
if (m_ZenCacheHost.empty() && !IsQuiet)
{
- ZEN_CONSOLE_WARN("Failed to find any usable cache hosts out of {} using {}", CacheCount, m_Host);
+ ZEN_CONSOLE_WARN("Failed to find any usable cache hosts out of {} using {}",
+ Response.CacheEndPoints.size(),
+ m_Host);
}
}
}
- else if (auto TestResult = TestCacheEndpoint(m_ZenCacheHost, false); TestResult.first)
+ else if (ZenCacheEndpointTestResult TestResult = TestZenCacheEndpoint(m_ZenCacheHost, false); TestResult.Success)
{
std::string::size_type HostnameStart = 0;
std::string::size_type HostnameLength = std::string::npos;
@@ -10928,9 +10885,14 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else
{
- ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.second);
+ ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.FailureReason);
+ m_ZenCacheHost = "";
}
}
+ else if (!m_OverrideHost.empty())
+ {
+ CloudHost = m_OverrideHost;
+ }
}
else
{
@@ -10967,33 +10929,41 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
if (!m_ZenCacheHost.empty())
{
- Result.CacheHttp = std::make_unique<HttpClient>(m_ZenCacheHost,
- HttpClientSettings{.LogCategory = "httpcacheclient",
- .ConnectTimeout = std::chrono::milliseconds{3000},
- .Timeout = std::chrono::milliseconds{30000},
- .AssumeHttp2 = CacheAssumeHttp2,
- .AllowResume = true,
- .RetryCount = 0});
- Result.BuildCacheStorage = CreateZenBuildStorageCache(*Result.CacheHttp,
- StorageCacheStats,
- m_Namespace,
- m_Bucket,
- TempPath / "zencache",
- m_PrimeCacheOnly);
- CacheDescription = fmt::format("Zen {}{}. SessionId: '{}'",
- BuildCacheName.empty() ? "" : fmt::format("{}, ", BuildCacheName),
- m_ZenCacheHost,
- Result.CacheHttp->GetSessionId());
-
- if (!m_Namespace.empty())
- {
- CacheDescription += fmt::format(". Namespace '{}'", m_Namespace);
+ if (ZenCacheEndpointTestResult TestResult = TestZenCacheEndpoint(m_ZenCacheHost, false); TestResult.Success)
+ {
+ Result.CacheHttp = std::make_unique<HttpClient>(m_ZenCacheHost,
+ HttpClientSettings{.LogCategory = "httpcacheclient",
+ .ConnectTimeout = std::chrono::milliseconds{3000},
+ .Timeout = std::chrono::milliseconds{30000},
+ .AssumeHttp2 = CacheAssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0});
+ Result.BuildCacheStorage = CreateZenBuildStorageCache(*Result.CacheHttp,
+ StorageCacheStats,
+ m_Namespace,
+ m_Bucket,
+ TempPath / "zencache",
+ m_PrimeCacheOnly);
+ CacheDescription = fmt::format("Zen {}{}. SessionId: '{}'",
+ BuildCacheName.empty() ? "" : fmt::format("{}, ", BuildCacheName),
+ m_ZenCacheHost,
+ Result.CacheHttp->GetSessionId());
+
+ if (!m_Namespace.empty())
+ {
+ CacheDescription += fmt::format(". Namespace '{}'", m_Namespace);
+ }
+ if (!m_Bucket.empty())
+ {
+ CacheDescription += fmt::format(" Bucket '{}'", m_Bucket);
+ }
+ Result.CacheName = BuildCacheName.empty() ? m_ZenCacheHost : BuildCacheName;
}
- if (!m_Bucket.empty())
+ else
{
- CacheDescription += fmt::format(" Bucket '{}'", m_Bucket);
+ ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.FailureReason);
+ m_ZenCacheHost = "";
}
- Result.CacheName = BuildCacheName.empty() ? m_ZenCacheHost : BuildCacheName;
}
if (!IsQuiet)
{
@@ -11433,6 +11403,20 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
}
+ auto ParseAllowPartialBlockRequests = [&]() -> EPartialBlockRequestMode {
+ if (m_PrimeCacheOnly)
+ {
+ return EPartialBlockRequestMode::Off;
+ }
+ EPartialBlockRequestMode Mode = PartialBlockRequestModeFromString(m_AllowPartialBlockRequests);
+ if (Mode == EPartialBlockRequestMode::Invalid)
+ {
+ throw OptionParseException(fmt::format("'--allow-partial-block-requests' ('{}') is invalid", m_AllowPartialBlockRequests),
+ SubOption->help());
+ }
+ return Mode;
+ };
+
if (SubOption == &m_DownloadOptions)
{
if (!IsQuiet)
@@ -11477,7 +11461,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ZEN_CONSOLE_WARN("Ignoring '--force' option when '--cache-prime-only' is enabled");
}
- if (m_AllowPartialBlockRequests && m_PrimeCacheOnly)
+ if (m_AllowPartialBlockRequests != "false" && m_PrimeCacheOnly)
{
ZEN_CONSOLE_WARN("Ignoring '--allow-partial-block-requests' option when '--cache-prime-only' is enabled");
}
@@ -11485,6 +11469,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::vector<Oid> BuildPartIds = ParseBuildPartIds();
std::vector<std::string> BuildPartNames = ParseBuildPartNames();
+ EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests();
+
DownloadFolder(Storage,
BuildId,
BuildPartIds,
@@ -11493,7 +11479,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = m_ZenFolderPath,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests && !m_PrimeCacheOnly,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = m_Clean,
.PostDownloadVerify = m_PostDownloadVerify,
.PrimeCacheOnly = m_PrimeCacheOnly,
@@ -11680,6 +11666,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
MakeSafeAbsolutePathÍnPlace(m_ZenFolderPath);
+ EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests();
+
BuildStorage::Statistics StorageStats;
BuildStorageCache::Statistics StorageCacheStats;
@@ -11705,7 +11693,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = m_ZenFolderPath,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = BuildIdString == m_BuildIds.front(),
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
@@ -11791,6 +11779,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
});
+ EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests();
+
BuildStorage::Statistics StorageStats;
BuildStorageCache::Statistics StorageCacheStats;
@@ -11877,7 +11867,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = DownloadPath / ZenFolderName,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = true,
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
@@ -11898,7 +11888,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = DownloadPath / ZenFolderName,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = false,
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
@@ -12011,7 +12001,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = DownloadPath / ZenFolderName,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = false,
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
@@ -12063,7 +12053,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = DownloadPath / ZenFolderName,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = false,
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
@@ -12083,7 +12073,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = DownloadPath / ZenFolderName,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = false,
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
@@ -12103,7 +12093,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = DownloadPath / ZenFolderName,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = false,
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
@@ -12123,7 +12113,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadOptions{.SystemRootDir = m_SystemRootDir,
.ZenFolderPath = DownloadPath2 / ZenFolderName,
.AllowMultiparts = m_AllowMultiparts,
- .AllowPartialBlockRequests = m_AllowPartialBlockRequests,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
.CleanTargetFolder = false,
.PostDownloadVerify = true,
.PrimeCacheOnly = false,
diff --git a/src/zen/cmds/builds_cmd.h b/src/zen/cmds/builds_cmd.h
index 26f804fa5..27a178c6f 100644
--- a/src/zen/cmds/builds_cmd.h
+++ b/src/zen/cmds/builds_cmd.h
@@ -2,6 +2,7 @@
#pragma once
+#include "../authutils.h"
#include "../zen.h"
#include <zenhttp/auth/authmgr.h>
@@ -64,30 +65,10 @@ private:
bool m_Force = false;
uint8_t m_BlockReuseMinPercentLimit = 85;
bool m_AllowMultiparts = true;
- bool m_AllowPartialBlockRequests = true;
+ std::string m_AllowPartialBlockRequests = "mixed";
std::string m_ManifestPath; // Not a std::filesystem::path since it can be relative to m_Path
- // Direct access token (may expire)
- std::string m_AccessToken;
- std::string m_AccessTokenEnv;
- std::filesystem::path m_AccessTokenPath;
-
- // Auth manager token encryption
- std::string m_EncryptionKey; // 256 bit AES encryption key
- std::string m_EncryptionIV; // 128 bit AES initialization vector
-
- // OpenId acccess token
- std::string m_OpenIdProviderName;
- std::string m_OpenIdProviderUrl;
- std::string m_OpenIdClientId;
- std::string m_OpenIdRefreshToken;
-
- // OAuth acccess token
- std::string m_OAuthUrl;
- std::string m_OAuthClientId;
- std::string m_OAuthClientSecret;
-
- std::string m_OidcTokenAuthExecutablePath;
+ AuthCommandLineOptions m_AuthOptions;
std::string m_Verb; // list, upload, download
diff --git a/src/zen/cmds/print_cmd.cpp b/src/zen/cmds/print_cmd.cpp
index c3c11a0ea..557808ba7 100644
--- a/src/zen/cmds/print_cmd.cpp
+++ b/src/zen/cmds/print_cmd.cpp
@@ -19,7 +19,10 @@ PrintCbObject(CbObject Object, bool AddTypeComment)
{
ExtendableStringBuilder<1024> ObjStr;
CompactBinaryToJson(Object, ObjStr, AddTypeComment);
- ZEN_CONSOLE("{}", ObjStr);
+ ForEachStrTok(ObjStr.ToView(), '\n', [](std::string_view Row) {
+ ZEN_CONSOLE("{}", Row);
+ return true;
+ });
}
static void
@@ -27,7 +30,10 @@ PrintCompactBinary(IoBuffer Data, bool AddTypeComment)
{
ExtendableStringBuilder<1024> StreamString;
CompactBinaryToJson(Data.GetView(), StreamString, AddTypeComment);
- ZEN_CONSOLE("{}", StreamString);
+ ForEachStrTok(StreamString.ToView(), '\n', [](std::string_view Row) {
+ ZEN_CONSOLE("{}", Row);
+ return true;
+ });
}
static CbValidateError
diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp
index 8ed52c764..b738d16aa 100644
--- a/src/zen/cmds/projectstore_cmd.cpp
+++ b/src/zen/cmds/projectstore_cmd.cpp
@@ -4,24 +4,33 @@
#include <zencore/basicfile.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compress.h>
+#include <zencore/config.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
+#include <zencore/process.h>
#include <zencore/scopeguard.h>
#include <zencore/stream.h>
#include <zencore/timer.h>
#include <zencore/workthreadpool.h>
+#include <zenhttp/auth/authmgr.h>
#include <zenhttp/formatters.h>
#include <zenhttp/httpclient.h>
+#include <zenhttp/httpclientauth.h>
#include <zenhttp/httpcommon.h>
+#include <zenutil/jupiter/jupiterbuildstorage.h>
+#include <zenutil/jupiter/jupiterhost.h>
+
ZEN_THIRD_PARTY_INCLUDES_START
#include <cpr/cpr.h>
#include <json11.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
#include <signal.h>
+#include <iostream>
namespace zen {
@@ -29,64 +38,7 @@ namespace {
using namespace std::literals;
- const std::string DefaultJupiterAccessTokenEnvVariableName(
-#if ZEN_PLATFORM_WINDOWS
- "UE-CloudDataCacheAccessToken"sv
-#endif
-#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- "UE_CloudDataCacheAccessToken"sv
-#endif
-
- );
-
- std::string ReadJupiterAccessTokenFromFile(const std::filesystem::path& Path)
- {
- if (!IsFile(Path))
- {
- throw std::runtime_error(fmt::format("the file '{}' does not exist", Path));
- }
- IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
- std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
- std::string JsonError;
- json11::Json TokenInfo = json11::Json::parse(JsonText, JsonError);
- if (!JsonError.empty())
- {
- throw std::runtime_error(fmt::format("failed parsing json file '{}'. Reason: '{}'", Path, JsonError));
- }
- const std::string AuthToken = TokenInfo["Token"].string_value();
- if (AuthToken.empty())
- {
- throw std::runtime_error(fmt::format("the json file '{}' does not contain a value for \"Token\"", Path));
- }
- return AuthToken;
- }
-
- std::filesystem::path FindOidcTokenExePath(std::string_view OidcTokenAuthExecutablePath)
- {
- if (OidcTokenAuthExecutablePath.empty())
- {
- const std::string OidcExecutableName = "OidcToken" ZEN_EXE_SUFFIX_LITERAL;
- std::filesystem::path OidcTokenPath = (GetRunningExecutablePath().parent_path() / OidcExecutableName).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- OidcTokenPath = (std::filesystem::current_path() / OidcExecutableName).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- else
- {
- std::filesystem::path OidcTokenPath = std::filesystem::absolute(StringToPath(OidcTokenAuthExecutablePath)).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- return {};
- };
+#define ZEN_CLOUD_STORAGE "Cloud Storage"
void WriteAuthOptions(CbObjectWriter& Writer,
std::string_view JupiterOpenIdProvider,
@@ -105,7 +57,7 @@ namespace {
}
if (!JupiterAccessTokenPath.empty())
{
- std::string ResolvedCloudAccessToken = ReadJupiterAccessTokenFromFile(JupiterAccessTokenPath);
+ std::string ResolvedCloudAccessToken = ReadAccessTokenFromJsonFile(JupiterAccessTokenPath);
if (!ResolvedCloudAccessToken.empty())
{
Writer.AddString("access-token"sv, ResolvedCloudAccessToken);
@@ -910,7 +862,7 @@ ExportOplogCommand::ExportOplogCommand()
"",
"access-token-env",
"Name of environment variable that holds the cloud/builds Storage access token",
- cxxopts::value(m_JupiterAccessTokenEnv)->default_value(DefaultJupiterAccessTokenEnvVariableName),
+ cxxopts::value(m_JupiterAccessTokenEnv)->default_value(std::string(GetDefaultAccessTokenEnvVariableName())),
"<envvariable>");
m_Options.add_option("",
"",
@@ -1387,7 +1339,7 @@ ImportOplogCommand::ImportOplogCommand()
"",
"access-token-env",
"Name of environment variable that holds the cloud/builds Storage access token",
- cxxopts::value(m_JupiterAccessTokenEnv)->default_value(DefaultJupiterAccessTokenEnvVariableName),
+ cxxopts::value(m_JupiterAccessTokenEnv)->default_value(std::string(GetDefaultAccessTokenEnvVariableName())),
"<envvariable>");
m_Options.add_option("",
"",
@@ -2236,4 +2188,327 @@ OplogValidateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
}
}
+////////////////////////////
+
+OplogDownloadCommand::OplogDownloadCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+
+ m_Options.add_option("", "", "system-dir", "Specify system root", cxxopts::value(m_SystemRootDir), "<systemdir>");
+
+ m_Options.add_option("output", "", "quiet", "Suppress non-essential output", cxxopts::value(m_Quiet), "<quiet>");
+ m_Options.add_option("", "y", "yes", "Don't query for confirmation", cxxopts::value(m_Yes), "<yes>");
+
+ auto AddCloudOptions = [this](cxxopts::Options& Ops) {
+ m_AuthOptions.AddOptions(Ops);
+
+ Ops.add_option("cloud build", "", "override-host", "Cloud Builds URL", cxxopts::value(m_OverrideHost), "<override-host>");
+ Ops.add_option("cloud build", "", "cloud-url", "Cloud Artifact URL", cxxopts::value(m_Url), "<cloud-url>");
+ Ops.add_option("cloud build", "", "host", "Cloud Builds host", cxxopts::value(m_Host), "<host>");
+ Ops.add_option("cloud build",
+ "",
+ "assume-http2",
+ "Assume that the builds endpoint is a HTTP/2 endpoint skipping HTTP/1.1 upgrade handshake",
+ cxxopts::value(m_AssumeHttp2),
+ "<assumehttp2>");
+
+ Ops.add_option("cloud build", "", "namespace", "Builds Storage namespace", cxxopts::value(m_Namespace), "<namespace>");
+ Ops.add_option("cloud build", "", "bucket", "Builds Storage bucket", cxxopts::value(m_Bucket), "<bucket>");
+ };
+
+ AddCloudOptions(m_Options);
+
+ m_Options.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), "<id>");
+ m_Options.add_option("",
+ "",
+ "output-path",
+ "Path to oplog output, extension .json or .cb (compact binary). Default is output to console",
+ cxxopts::value(m_OutputPath),
+ "<path>");
+
+ m_Options.parse_positional({"cloud-url", "output-path"});
+ m_Options.positional_help("[<cloud-url> <output-path>]");
+}
+
+OplogDownloadCommand::~OplogDownloadCommand()
+{
+}
+
+void
+OplogDownloadCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ if (!ParseOptions(argc, argv))
+ {
+ return;
+ }
+
+ if (!m_Quiet)
+ {
+ ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId());
+ }
+
+ auto ParseSystemOptions = [&]() {
+ if (m_SystemRootDir.empty())
+ {
+ m_SystemRootDir = PickDefaultSystemRootDirectory();
+ }
+ MakeSafeAbsolutePathÍnPlace(m_SystemRootDir);
+ };
+ ParseSystemOptions();
+
+ auto ParseStorageOptions = [&](bool RequireNamespace, bool RequireBucket) {
+ if (!m_Url.empty())
+ {
+ if (!m_Host.empty())
+ {
+ throw OptionParseException(fmt::format("'--host' ('{}') conflicts with '--url' ('{}')", m_Host, m_Url), m_Options.help());
+ }
+ if (!m_Bucket.empty())
+ {
+ throw OptionParseException(fmt::format("'--bucket' ('{}') conflicts with '--url' ('{}')", m_Bucket, m_Url),
+ m_Options.help());
+ }
+ if (!m_BuildId.empty())
+ {
+ throw OptionParseException(fmt::format("'--buildid' ('{}') conflicts with '--url' ('{}')", m_BuildId, m_Url),
+ m_Options.help());
+ }
+ if (!ParseBuildStorageUrl(m_Url, m_Host, m_Namespace, m_Bucket, m_BuildId))
+ {
+ throw OptionParseException("'--url' ('{}') is malformed, it does not match the Cloud Artifact URL format",
+ m_Options.help());
+ }
+ }
+
+ if (!m_OverrideHost.empty() || !m_Host.empty())
+ {
+ if (RequireNamespace && m_Namespace.empty())
+ {
+ throw OptionParseException("'--namespace' is required", m_Options.help());
+ }
+ if (RequireBucket && m_Bucket.empty())
+ {
+ throw OptionParseException("'--bucket' is required", m_Options.help());
+ }
+ }
+
+ if (m_OverrideHost.empty() && m_Host.empty())
+ {
+ throw OptionParseException("'--host' or '--overridehost' is required", m_Options.help());
+ }
+ };
+
+ std::unique_ptr<AuthMgr> Auth;
+ HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient",
+ .AssumeHttp2 = m_AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 2};
+
+ ParseStorageOptions(/*RequireNamespace*/ true, /*RequireBucket*/ true);
+
+ m_AuthOptions.ParseOptions(m_Options, m_SystemRootDir, ClientSettings, m_OverrideHost.empty() ? m_Host : m_OverrideHost, Auth, m_Quiet);
+
+ std::string BuildStorageName = ZEN_CLOUD_STORAGE;
+
+ std::string CloudHost;
+
+ auto TestHostEndpoint = [](std::string_view BaseUrl, const bool AssumeHttp2) -> std::pair<bool, std::string> {
+ HttpClientSettings TestClientSettings{.LogCategory = "httpbuildsclient",
+ .ConnectTimeout = std::chrono::milliseconds{1000},
+ .Timeout = std::chrono::milliseconds{2000},
+ .AssumeHttp2 = AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0};
+
+ HttpClient TestHttpClient(BaseUrl, TestClientSettings);
+ HttpClient::Response TestResponse = TestHttpClient.Get("/health/live");
+ if (TestResponse.IsSuccess())
+ {
+ return {true, ""};
+ }
+ return {false, TestResponse.ErrorMessage("")};
+ };
+
+ if (m_OverrideHost.empty())
+ {
+ JupiterServerDiscovery Response = DiscoverJupiterEndpoints(m_Host, ClientSettings);
+
+ if (Response.ServerEndPoints.empty())
+ {
+ throw std::runtime_error(fmt::format("Failed to find any builds hosts at {}", m_Host));
+ }
+ for (const JupiterServerDiscovery::EndPoint& ServerEndpoint : Response.ServerEndPoints)
+ {
+ if (!ServerEndpoint.BaseUrl.empty())
+ {
+ if (JupiterEndpointTestResult TestResult = TestJupiterEndpoint(ServerEndpoint.BaseUrl, ServerEndpoint.AssumeHttp2);
+ TestResult.Success)
+ {
+ CloudHost = ServerEndpoint.BaseUrl;
+ m_AssumeHttp2 = ServerEndpoint.AssumeHttp2;
+ BuildStorageName = ServerEndpoint.Name;
+ break;
+ }
+ else
+ {
+ ZEN_DEBUG("Unable to reach host {}. Reason: {}", ServerEndpoint.BaseUrl, TestResult.FailureReason);
+ }
+ }
+ }
+ if (CloudHost.empty())
+ {
+ throw std::runtime_error(
+ fmt::format("Failed to find any usable builds hosts out of {} using {}", Response.ServerEndPoints.size(), m_Host));
+ }
+ }
+ else if (JupiterEndpointTestResult TestResult = TestJupiterEndpoint(m_OverrideHost, m_AssumeHttp2); TestResult.Success)
+ {
+ CloudHost = m_OverrideHost;
+ }
+ else
+ {
+ throw std::runtime_error(fmt::format("Host {} could not be reached. Reason: {}", m_OverrideHost, TestResult.FailureReason));
+ }
+
+ Oid BuildId = Oid::TryFromHexString(m_BuildId);
+ if (BuildId == Oid::Zero)
+ {
+ throw OptionParseException("'--buildid' is malformed", m_Options.help());
+ }
+
+ BuildStorage::Statistics StorageStats;
+ HttpClient BuildStorageHttp(CloudHost, ClientSettings);
+
+ if (!m_Quiet)
+ {
+ std::string StorageDescription = fmt::format("Cloud {}{}. SessionId: '{}'. Namespace '{}', Bucket '{}'",
+ BuildStorageName.empty() ? "" : fmt::format("{}, ", BuildStorageName),
+ CloudHost,
+ BuildStorageHttp.GetSessionId(),
+ m_Namespace,
+ m_Bucket);
+
+ ZEN_CONSOLE("Remote: {}", StorageDescription);
+ }
+
+ std::filesystem::path StorageTempPath = std::filesystem::temp_directory_path() / ("zen_" + Oid::NewOid().ToString());
+
+ std::unique_ptr<BuildStorage> BuildStorage =
+ CreateJupiterBuildStorage(Log(), BuildStorageHttp, StorageStats, m_Namespace, m_Bucket, m_AllowRedirect, StorageTempPath);
+
+ Stopwatch Timer;
+ CbObject BuildObject = BuildStorage->GetBuild(BuildId);
+ if (!m_Quiet)
+ {
+ ZEN_CONSOLE("Fetched {}/{}/{}/{} in {}", m_Url, m_Namespace, m_Bucket, BuildId, NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+ }
+
+ Timer.Reset();
+
+ CbObjectView PartsObject = BuildObject["parts"sv].AsObjectView();
+ if (!PartsObject)
+ {
+ throw std::runtime_error(
+ fmt::format("The build {}/{}/{}/{} payload does not contain a 'parts' object"sv, m_Url, m_Namespace, m_Bucket, m_BuildId));
+ }
+
+ static const std::string_view OplogContainerPartName = "oplogcontainer"sv;
+
+ const Oid OplogBuildPartId = PartsObject[OplogContainerPartName].AsObjectId();
+ if (OplogBuildPartId == Oid::Zero)
+ {
+ throw std::runtime_error(fmt::format("The build {}/{}/{}/{} payload 'parts' object does not contain a '{}' entry"sv,
+ m_Url,
+ m_Namespace,
+ m_Bucket,
+ m_BuildId,
+ OplogContainerPartName));
+ }
+
+ CbObject ContainerObject = BuildStorage->GetBuildPart(BuildId, OplogBuildPartId);
+
+ MemoryView OpsSection = ContainerObject["ops"sv].AsBinaryView();
+ IoBuffer OpsBuffer(IoBuffer::Wrap, OpsSection.GetData(), OpsSection.GetSize());
+ IoBuffer SectionPayload = CompressedBuffer::FromCompressedNoValidate(std::move(OpsBuffer)).Decompress().AsIoBuffer();
+
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject SectionObject = ValidateAndReadCompactBinaryObject(std::move(SectionPayload), ValidateResult);
+ ValidateResult == CbValidateError::None && ContainerObject)
+ {
+ if (!m_Quiet)
+ {
+ ZEN_CONSOLE("Decompressed and validated oplog payload {} -> {} in {}",
+ NiceBytes(OpsSection.GetSize()),
+ NiceBytes(SectionObject.GetSize()),
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+ }
+
+ if (m_OutputPath.empty())
+ {
+ if (!m_Yes)
+ {
+ if (OpsSection.GetSize() > 8u * 1024u * 1024u)
+ {
+ while (!m_Yes)
+ {
+ const std::string Prompt = fmt::format("Do you want to output an oplog of size {} to console? (yes/no) ",
+ NiceBytes(SectionObject.GetSize()));
+ printf("%s", Prompt.c_str());
+ std::string Reponse;
+ std::getline(std::cin, Reponse);
+ Reponse = ToLower(Reponse);
+ if (Reponse == "y" || Reponse == "yes")
+ {
+ m_Yes = true;
+ }
+ else if (Reponse == "n" || Reponse == "no")
+ {
+ return;
+ }
+ }
+ }
+ }
+ ExtendableStringBuilder<1024> SB;
+ SectionObject.ToJson(SB);
+ ForEachStrTok(SB.ToView(), '\n', [](std::string_view Row) {
+ ZEN_CONSOLE("{}", Row);
+ return true;
+ });
+ }
+ else
+ {
+ Timer.Reset();
+ const std::string Extension = ToLower(m_OutputPath.extension().string());
+ if (Extension == ".cb" || Extension == ".cbo")
+ {
+ WriteFile(m_OutputPath, IoBuffer(IoBuffer::Wrap, SectionObject.GetView().GetData(), SectionObject.GetSize()));
+ }
+ else if (Extension == ".json")
+ {
+ ExtendableStringBuilder<1024> SB;
+ SectionObject.ToJson(SB);
+ WriteFile(m_OutputPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size()));
+ }
+ else
+ {
+ throw std::runtime_error(fmt::format("Unsupported output extension type '{}'", Extension));
+ }
+ if (!m_Quiet)
+ {
+ ZEN_CONSOLE("Wrote {} to '{}' in {}",
+ NiceBytes(FileSizeFromPath(m_OutputPath)),
+ m_OutputPath,
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+ }
+ }
+ }
+ else
+ {
+ throw std::runtime_error(
+ fmt::format("Failed to parse oplog container: '{}' ('{}')", "Section has unexpected data type", ToString(ValidateResult)));
+ }
+}
+
} // namespace zen
diff --git a/src/zen/cmds/projectstore_cmd.h b/src/zen/cmds/projectstore_cmd.h
index 70b336650..136319aa3 100644
--- a/src/zen/cmds/projectstore_cmd.h
+++ b/src/zen/cmds/projectstore_cmd.h
@@ -4,6 +4,8 @@
#include "../zen.h"
+#include "../authutils.h"
+
namespace zen {
class ProjectStoreCommand : public ZenCmdBase
@@ -162,6 +164,7 @@ private:
std::string m_JupiterNamespace;
std::string m_JupiterBucket;
+
std::string m_JupiterOpenIdProvider;
std::string m_JupiterAccessToken;
std::string m_JupiterAccessTokenEnv;
@@ -268,4 +271,38 @@ private:
std::string m_OplogName;
};
+class OplogDownloadCommand : public ProjectStoreCommand
+{
+public:
+ static constexpr char Name[] = "oplog-download";
+ static constexpr char Description[] = "Download an cloud storage oplog";
+
+ OplogDownloadCommand();
+ ~OplogDownloadCommand();
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+
+private:
+ cxxopts::Options m_Options{Name, Description};
+
+ std::filesystem::path m_SystemRootDir;
+
+ bool m_Quiet = false;
+ bool m_Yes = false;
+
+ AuthCommandLineOptions m_AuthOptions;
+
+ // cloud builds
+ std::string m_OverrideHost;
+ std::string m_Host;
+ std::string m_Url;
+ bool m_AssumeHttp2 = false;
+ bool m_AllowRedirect = false;
+ std::string m_Namespace;
+ std::string m_Bucket;
+ std::string m_BuildId;
+
+ std::filesystem::path m_OutputPath;
+};
+
} // namespace zen
diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp
index 200c8e4b4..0381dd15c 100644
--- a/src/zen/zen.cpp
+++ b/src/zen/zen.cpp
@@ -72,6 +72,88 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
+#if ZEN_PLATFORM_WINDOWS
+static HANDLE
+GetConsoleHandle()
+{
+ static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
+ return hStdOut;
+}
+#endif
+
+static bool
+CheckStdoutTty()
+{
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hStdOut = GetConsoleHandle();
+ DWORD dwMode = 0;
+ static bool IsConsole = ::GetConsoleMode(hStdOut, &dwMode);
+ return IsConsole;
+#else
+ return isatty(fileno(stdout));
+#endif
+}
+
+static bool
+IsStdoutTty()
+{
+ static bool StdoutIsTty = CheckStdoutTty();
+ return StdoutIsTty;
+}
+
+static void
+OutputToConsoleRaw(const char* String, size_t Length)
+{
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hStdOut = GetConsoleHandle();
+#endif
+
+#if ZEN_PLATFORM_WINDOWS
+ if (IsStdoutTty())
+ {
+ WriteConsoleA(hStdOut, String, (DWORD)Length, 0, 0);
+ }
+ else
+ {
+ ::WriteFile(hStdOut, (LPCVOID)String, (DWORD)Length, 0, 0);
+ }
+#else
+ fwrite(String, 1, Length, stdout);
+#endif
+}
+
+static void
+OutputToConsoleRaw(const std::string& String)
+{
+ OutputToConsoleRaw(String.c_str(), String.length());
+}
+
+static void
+OutputToConsoleRaw(const StringBuilderBase& SB)
+{
+ OutputToConsoleRaw(SB.c_str(), SB.Size());
+}
+
+static uint32_t
+GetConsoleColumns(uint32_t Default)
+{
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hStdOut = GetConsoleHandle();
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ if (GetConsoleScreenBufferInfo(hStdOut, &csbi) == TRUE)
+ {
+ return (uint32_t)(csbi.srWindow.Right - csbi.srWindow.Left + 1);
+ }
+#else
+ struct winsize w;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0)
+ {
+ return (uint32_t)w.ws_col;
+ }
+#endif
+ return Default;
+}
+
enum class ReturnCode : std::int8_t
{
kSuccess = 0,
@@ -122,6 +204,8 @@ ZenCmdBase::ParseOptions(int argc, char** argv)
bool
ZenCmdBase::ParseOptions(cxxopts::Options& CmdOptions, int argc, char** argv)
{
+ CmdOptions.set_width(GetConsoleColumns(80));
+
cxxopts::ParseResult Result;
try
@@ -308,88 +392,6 @@ ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec)
return ResolveTargetHostSpec(InHostSpec, /* out */ Dummy);
}
-#if ZEN_PLATFORM_WINDOWS
-static HANDLE
-GetConsoleHandle()
-{
- static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
- return hStdOut;
-}
-#endif
-
-static bool
-CheckStdoutTty()
-{
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hStdOut = GetConsoleHandle();
- DWORD dwMode = 0;
- static bool IsConsole = ::GetConsoleMode(hStdOut, &dwMode);
- return IsConsole;
-#else
- return isatty(fileno(stdout));
-#endif
-}
-
-static bool
-IsStdoutTty()
-{
- static bool StdoutIsTty = CheckStdoutTty();
- return StdoutIsTty;
-}
-
-static void
-OutputToConsoleRaw(const char* String, size_t Length)
-{
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hStdOut = GetConsoleHandle();
-#endif
-
-#if ZEN_PLATFORM_WINDOWS
- if (IsStdoutTty())
- {
- WriteConsoleA(hStdOut, String, (DWORD)Length, 0, 0);
- }
- else
- {
- ::WriteFile(hStdOut, (LPCVOID)String, (DWORD)Length, 0, 0);
- }
-#else
- fwrite(String, 1, Length, stdout);
-#endif
-}
-
-static void
-OutputToConsoleRaw(const std::string& String)
-{
- OutputToConsoleRaw(String.c_str(), String.length());
-}
-
-static void
-OutputToConsoleRaw(const StringBuilderBase& SB)
-{
- OutputToConsoleRaw(SB.c_str(), SB.Size());
-}
-
-static uint32_t
-GetConsoleColumns()
-{
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hStdOut = GetConsoleHandle();
- CONSOLE_SCREEN_BUFFER_INFO csbi;
- if (GetConsoleScreenBufferInfo(hStdOut, &csbi) == TRUE)
- {
- return (uint32_t)(csbi.srWindow.Right - csbi.srWindow.Left + 1);
- }
-#else
- struct winsize w;
- if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0)
- {
- return (uint32_t)w.ws_col;
- }
-#endif
- return 1024;
-}
-
void
ProgressBar::SetLogOperationName(Mode InMode, std::string_view Name)
{
@@ -504,7 +506,7 @@ ProgressBar::UpdateState(const State& NewState, bool DoLinebreak)
uint64_t ETAMS =
(NewState.Status == State::EStatus::Running) && (PercentDone > 5) ? (ETAElapsedMS * NewState.RemainingCount) / Completed : 0;
- uint32_t ConsoleColumns = GetConsoleColumns();
+ uint32_t ConsoleColumns = GetConsoleColumns(1024);
const std::string PercentString = fmt::format("{:#3}%", PercentDone);
@@ -708,6 +710,7 @@ main(int argc, char** argv)
JobCommand JobCmd;
OplogMirrorCommand OplogMirrorCmd;
SnapshotOplogCommand SnapshotOplogCmd;
+ OplogDownloadCommand OplogDownload;
OplogValidateCommand OplogValidateCmd;
PrintCommand PrintCmd;
PrintPackageCommand PrintPkgCmd;
@@ -764,6 +767,7 @@ main(int argc, char** argv)
{"oplog-import", &ImportOplogCmd, "Import project store oplog"},
{"oplog-mirror", &OplogMirrorCmd, "Mirror project store oplog to file system"},
{"oplog-snapshot", &SnapshotOplogCmd, "Snapshot project store oplog"},
+ {OplogDownloadCommand::Name, &OplogDownload, OplogDownloadCommand::Description},
{"oplog-validate", &OplogValidateCmd, "Validate oplog for missing references"},
{"print", &PrintCmd, "Print compact binary object"},
{"printpackage", &PrintPkgCmd, "Print compact binary package"},
diff --git a/src/zencore/sentryintegration.cpp b/src/zencore/sentryintegration.cpp
index 118c4158a..fef4cd8ed 100644
--- a/src/zencore/sentryintegration.cpp
+++ b/src/zencore/sentryintegration.cpp
@@ -32,7 +32,7 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace sentry {
namespace {
- static const std::string DefaultDsn("https://[email protected]/5919284");
+ static const std::string DefaultDsn("https://[email protected]/5919284");
}
struct SentryAssertImpl : zen::AssertImpl
diff --git a/src/zenhttp/httpclientauth.cpp b/src/zenhttp/httpclientauth.cpp
index 62e1b77bc..4bbc6405b 100644
--- a/src/zenhttp/httpclientauth.cpp
+++ b/src/zenhttp/httpclientauth.cpp
@@ -176,9 +176,10 @@ namespace zen { namespace httpclientauth {
std::optional<std::function<HttpClientAccessToken()>> CreateFromOidcTokenExecutable(const std::filesystem::path& OidcExecutablePath,
std::string_view CloudHost,
- bool Quiet)
+ bool Quiet,
+ bool Unattended)
{
- HttpClientAccessToken InitialToken = GetOidcTokenFromExe(OidcExecutablePath, CloudHost, /* Unattended */ false, Quiet);
+ HttpClientAccessToken InitialToken = GetOidcTokenFromExe(OidcExecutablePath, CloudHost, Unattended, Quiet);
if (InitialToken.IsValid())
{
return [OidcExecutablePath = std::filesystem::path(OidcExecutablePath),
diff --git a/src/zenhttp/include/zenhttp/httpclientauth.h b/src/zenhttp/include/zenhttp/httpclientauth.h
index 32d00f87f..f44cb9b0d 100644
--- a/src/zenhttp/include/zenhttp/httpclientauth.h
+++ b/src/zenhttp/include/zenhttp/httpclientauth.h
@@ -28,7 +28,8 @@ namespace httpclientauth {
std::optional<std::function<HttpClientAccessToken()>> CreateFromOidcTokenExecutable(const std::filesystem::path& OidcExecutablePath,
std::string_view CloudHost,
- bool Quiet);
+ bool Quiet,
+ bool Unattended);
} // namespace httpclientauth
} // namespace zen
diff --git a/src/zenserver/projectstore/buildsremoteprojectstore.cpp b/src/zenserver/projectstore/buildsremoteprojectstore.cpp
index a9dd48510..0b0d8ccd0 100644
--- a/src/zenserver/projectstore/buildsremoteprojectstore.cpp
+++ b/src/zenserver/projectstore/buildsremoteprojectstore.cpp
@@ -470,7 +470,10 @@ private:
};
std::shared_ptr<RemoteProjectStore>
-CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options, const std::filesystem::path& TempFilePath, bool Quiet)
+CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options,
+ const std::filesystem::path& TempFilePath,
+ bool Quiet,
+ bool Unattended)
{
std::string Url = Options.Url;
if (Url.find("://"sv) == std::string::npos)
@@ -495,7 +498,8 @@ CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options, const st
}
else if (!Options.OidcExePath.empty())
{
- if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet); TokenProviderMaybe)
+ if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet, Unattended);
+ TokenProviderMaybe)
{
TokenProvider = TokenProviderMaybe.value();
}
diff --git a/src/zenserver/projectstore/buildsremoteprojectstore.h b/src/zenserver/projectstore/buildsremoteprojectstore.h
index 60b6caef7..24ab9678d 100644
--- a/src/zenserver/projectstore/buildsremoteprojectstore.h
+++ b/src/zenserver/projectstore/buildsremoteprojectstore.h
@@ -26,6 +26,7 @@ struct BuildsRemoteStoreOptions : RemoteStoreOptions
std::shared_ptr<RemoteProjectStore> CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options,
const std::filesystem::path& TempFilePath,
- bool Quiet);
+ bool Quiet,
+ bool Unattended);
} // namespace zen
diff --git a/src/zenserver/projectstore/jupiterremoteprojectstore.cpp b/src/zenserver/projectstore/jupiterremoteprojectstore.cpp
index a8d486e2a..da3f92353 100644
--- a/src/zenserver/projectstore/jupiterremoteprojectstore.cpp
+++ b/src/zenserver/projectstore/jupiterremoteprojectstore.cpp
@@ -343,7 +343,7 @@ private:
};
std::shared_ptr<RemoteProjectStore>
-CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options, const std::filesystem::path& TempFilePath, bool Quiet)
+CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options, const std::filesystem::path& TempFilePath, bool Quiet, bool Unattended)
{
std::string Url = Options.Url;
if (Url.find("://"sv) == std::string::npos)
@@ -374,7 +374,8 @@ CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options, const std::fi
}
else if (!Options.OidcExePath.empty())
{
- if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet); TokenProviderMaybe)
+ if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet, Unattended);
+ TokenProviderMaybe)
{
TokenProvider = TokenProviderMaybe.value();
}
diff --git a/src/zenserver/projectstore/jupiterremoteprojectstore.h b/src/zenserver/projectstore/jupiterremoteprojectstore.h
index ac2d25b47..d7a22700a 100644
--- a/src/zenserver/projectstore/jupiterremoteprojectstore.h
+++ b/src/zenserver/projectstore/jupiterremoteprojectstore.h
@@ -26,6 +26,7 @@ struct JupiterRemoteStoreOptions : RemoteStoreOptions
std::shared_ptr<RemoteProjectStore> CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options,
const std::filesystem::path& TempFilePath,
- bool Quiet);
+ bool Quiet,
+ bool Unattended);
} // namespace zen
diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp
index 97175da23..78e014346 100644
--- a/src/zenserver/projectstore/projectstore.cpp
+++ b/src/zenserver/projectstore/projectstore.cpp
@@ -268,7 +268,7 @@ namespace {
ForceDisableBlocks,
ForceDisableTempBlocks,
AssumeHttp2};
- RemoteStore = CreateJupiterRemoteStore(Options, TempFilePath, /*Quiet*/ false);
+ RemoteStore = CreateJupiterRemoteStore(Options, TempFilePath, /*Quiet*/ false, /*Unattended*/ false);
}
if (CbObjectView Zen = Params["zen"sv].AsObjectView(); Zen)
@@ -365,7 +365,7 @@ namespace {
ForceDisableTempBlocks,
AssumeHttp2,
MetaData};
- RemoteStore = CreateJupiterBuildsRemoteStore(Options, TempFilePath, /*Quiet*/ false);
+ RemoteStore = CreateJupiterBuildsRemoteStore(Options, TempFilePath, /*Quiet*/ false, /*Unattended*/ false);
}
if (!RemoteStore)
@@ -4812,11 +4812,11 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
{
if (WantsRawSizeField)
{
- RawSizes.resize(Hashes.size(), 0u);
+ RawSizes.resize(Hashes.size(), (uint64_t)-1);
}
if (WantsSizeField)
{
- Sizes.resize(Hashes.size(), 0u);
+ Sizes.resize(Hashes.size(), (uint64_t)-1);
}
WorkerThreadPool& WorkerPool = GetSmallWorkerPool(EWorkloadType::Burst); // GetSyncWorkerPool();
@@ -4832,11 +4832,13 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
{
if (WantsRawSizeField)
{
+ ZEN_ASSERT_SLOW(RawSizes[Index] == (uint64_t)-1);
IoHash _;
if (CompressedBuffer::ValidateCompressedHeader(Payload, _, RawSizes[Index]))
{
if (WantsSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
Sizes[Index] = Payload.GetSize();
}
}
@@ -4850,6 +4852,7 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
}
else if (WantsSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
Sizes[Index] = Payload.GetSize();
}
}
@@ -4857,10 +4860,12 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
{
if (WantsSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
Sizes[Index] = Payload.GetSize();
}
if (WantsRawSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
RawSizes[Index] = Payload.GetSize();
}
}
diff --git a/src/zenserver/projectstore/remoteprojectstore.cpp b/src/zenserver/projectstore/remoteprojectstore.cpp
index 911628386..e61ccd917 100644
--- a/src/zenserver/projectstore/remoteprojectstore.cpp
+++ b/src/zenserver/projectstore/remoteprojectstore.cpp
@@ -2743,6 +2743,12 @@ ParseOplogContainer(const CbObject& ContainerObject,
NiceBytes(Chunked.RawSize),
Chunked.ChunkHashes.size());
}
+ if (remotestore_impl::IsCancelled(OptionalContext))
+ {
+ return RemoteProjectStore::Result{.ErrorCode = gsl::narrow<int>(HttpResponseCode::OK),
+ .ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0,
+ .Reason = "Operation cancelled"};
+ }
}
size_t NeedBlockCount = 0;
@@ -2787,6 +2793,12 @@ ParseOplogContainer(const CbObject& ContainerObject,
NeedBlockCount++;
}
}
+ if (remotestore_impl::IsCancelled(OptionalContext))
+ {
+ return RemoteProjectStore::Result{.ErrorCode = gsl::narrow<int>(HttpResponseCode::OK),
+ .ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0,
+ .Reason = "Operation cancelled"};
+ }
}
remotestore_impl::ReportMessage(OptionalContext,
fmt::format("Requesting {} of {} attachment blocks", NeedBlockCount, BlocksArray.Num()));
@@ -2801,6 +2813,12 @@ ParseOplogContainer(const CbObject& ContainerObject,
OnNeedAttachment(AttachmentHash);
NeedAttachmentCount++;
}
+ if (remotestore_impl::IsCancelled(OptionalContext))
+ {
+ return RemoteProjectStore::Result{.ErrorCode = gsl::narrow<int>(HttpResponseCode::OK),
+ .ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0,
+ .Reason = "Operation cancelled"};
+ }
};
remotestore_impl::ReportMessage(OptionalContext,
fmt::format("Requesting {} of {} large attachments", NeedAttachmentCount, LargeChunksArray.Num()));
diff --git a/src/zenstore/blockstore.cpp b/src/zenstore/blockstore.cpp
index c50f2bb13..b8fa03305 100644
--- a/src/zenstore/blockstore.cpp
+++ b/src/zenstore/blockstore.cpp
@@ -121,6 +121,12 @@ BlockStoreFile::FileSize() const
{
return 0;
}
+ uint64_t Expected = 0;
+ if (!m_CachedFileSize.compare_exchange_strong(Expected, Size))
+ {
+ // Force a new check next time file size is fetched
+ m_CachedFileSize.store(0);
+ }
return Size;
}
return m_CachedFileSize;
@@ -153,13 +159,8 @@ void
BlockStoreFile::Write(const void* Data, uint64_t Size, uint64_t FileOffset)
{
ZEN_TRACE_CPU("BlockStoreFile::Write");
-#if ZEN_BUILD_DEBUG
- if (uint64_t CachedFileSize = m_CachedFileSize.load(); CachedFileSize > 0)
- {
- ZEN_ASSERT(FileOffset + Size <= CachedFileSize);
- }
-#endif // ZEN_BUILD_DEBUG
m_File.Write(Data, Size, FileOffset);
+ m_CachedFileSize.store(0);
}
void
diff --git a/src/zenstore/gc.cpp b/src/zenstore/gc.cpp
index 1ddb25364..185bc2118 100644
--- a/src/zenstore/gc.cpp
+++ b/src/zenstore/gc.cpp
@@ -2858,7 +2858,7 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime,
{
ZEN_WARN("writing gc scheduler state ran out of disk space: '{}'", SystemError.what());
}
- else if (RetryCount == 0)
+ else if (RetryCount == 2)
{
ZEN_ERROR("writing gc scheduler state failed with system error exception: '{}' ({})",
SystemError.what(),
@@ -2877,7 +2877,7 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime,
}
catch (const std::exception& Ex)
{
- if (RetryCount == 0)
+ if (RetryCount == 2)
{
ZEN_ERROR("writing gc scheduler state failed with: '{}'", Ex.what());
}
diff --git a/src/zenstore/include/zenstore/blockstore.h b/src/zenstore/include/zenstore/blockstore.h
index fce05766f..4006f4275 100644
--- a/src/zenstore/include/zenstore/blockstore.h
+++ b/src/zenstore/include/zenstore/blockstore.h
@@ -102,12 +102,12 @@ struct BlockStoreFile : public RefCounted
IoBuffer GetMetaData() const;
private:
- std::filesystem::path GetMetaPath() const;
- void RemoveMeta();
- const std::filesystem::path m_Path;
- IoBuffer m_IoBuffer;
- BasicFile m_File;
- std::atomic<uint64_t> m_CachedFileSize = 0;
+ std::filesystem::path GetMetaPath() const;
+ void RemoveMeta();
+ const std::filesystem::path m_Path;
+ IoBuffer m_IoBuffer;
+ BasicFile m_File;
+ mutable std::atomic<uint64_t> m_CachedFileSize = 0;
};
class BlockStoreCompactState;
diff --git a/src/zenutil/buildstoragecache.cpp b/src/zenutil/buildstoragecache.cpp
index e5e8db8d2..376d967d1 100644
--- a/src/zenutil/buildstoragecache.cpp
+++ b/src/zenutil/buildstoragecache.cpp
@@ -413,4 +413,22 @@ CreateZenBuildStorageCache(HttpClient& HttpClient,
return std::make_unique<ZenBuildStorageCache>(HttpClient, Stats, Namespace, Bucket, TempFolderPath, BoostBackgroundThreadCount);
}
+ZenCacheEndpointTestResult
+TestZenCacheEndpoint(std::string_view BaseUrl, const bool AssumeHttp2)
+{
+ HttpClientSettings TestClientSettings{.LogCategory = "httpcacheclient",
+ .ConnectTimeout = std::chrono::milliseconds{1000},
+ .Timeout = std::chrono::milliseconds{2000},
+ .AssumeHttp2 = AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0};
+ HttpClient TestHttpClient(BaseUrl, TestClientSettings);
+ HttpClient::Response TestResponse = TestHttpClient.Get("/status/builds");
+ if (TestResponse.IsSuccess())
+ {
+ return {.Success = true};
+ }
+ return {.Success = false, .FailureReason = TestResponse.ErrorMessage("")};
+};
+
} // namespace zen
diff --git a/src/zenutil/include/zenutil/buildstoragecache.h b/src/zenutil/include/zenutil/buildstoragecache.h
index a0690a16a..e6ca2c5e4 100644
--- a/src/zenutil/include/zenutil/buildstoragecache.h
+++ b/src/zenutil/include/zenutil/buildstoragecache.h
@@ -57,4 +57,13 @@ std::unique_ptr<BuildStorageCache> CreateZenBuildStorageCache(HttpClient& H
std::string_view Bucket,
const std::filesystem::path& TempFolderPath,
bool BoostBackgroundThreadCount);
+
+struct ZenCacheEndpointTestResult
+{
+ bool Success = false;
+ std::string FailureReason;
+};
+
+ZenCacheEndpointTestResult TestZenCacheEndpoint(std::string_view BaseUrl, const bool AssumeHttp2);
+
} // namespace zen
diff --git a/src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h b/src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h
index bbf070993..f25d8933b 100644
--- a/src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h
+++ b/src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h
@@ -15,4 +15,11 @@ std::unique_ptr<BuildStorage> CreateJupiterBuildStorage(LoggerRef InLog,
std::string_view Bucket,
bool AllowRedirect,
const std::filesystem::path& TempFolderPath);
+
+bool ParseBuildStorageUrl(std::string_view InUrl,
+ std::string& OutHost,
+ std::string& OutNamespace,
+ std::string& OutBucket,
+ std::string& OutBuildId);
+
} // namespace zen
diff --git a/src/zenutil/include/zenutil/jupiter/jupiterhost.h b/src/zenutil/include/zenutil/jupiter/jupiterhost.h
new file mode 100644
index 000000000..3bbc700b8
--- /dev/null
+++ b/src/zenutil/include/zenutil/jupiter/jupiterhost.h
@@ -0,0 +1,35 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace zen {
+
+struct HttpClientSettings;
+
+struct JupiterServerDiscovery
+{
+ struct EndPoint
+ {
+ std::string Name;
+ std::string BaseUrl;
+ bool AssumeHttp2 = false;
+ };
+ std::vector<EndPoint> ServerEndPoints;
+ std::vector<EndPoint> CacheEndPoints;
+};
+
+JupiterServerDiscovery DiscoverJupiterEndpoints(std::string_view Host, const HttpClientSettings& ClientSettings);
+
+struct JupiterEndpointTestResult
+{
+ bool Success = false;
+ std::string FailureReason;
+};
+
+JupiterEndpointTestResult TestJupiterEndpoint(std::string_view BaseUrl, const bool AssumeHttp2);
+
+} // namespace zen
diff --git a/src/zenutil/jupiter/jupiterbuildstorage.cpp b/src/zenutil/jupiter/jupiterbuildstorage.cpp
index 386a91cb3..6eb3489dc 100644
--- a/src/zenutil/jupiter/jupiterbuildstorage.cpp
+++ b/src/zenutil/jupiter/jupiterbuildstorage.cpp
@@ -14,6 +14,8 @@ ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
ZEN_THIRD_PARTY_INCLUDES_END
+#include <regex>
+
namespace zen {
using namespace std::literals;
@@ -511,4 +513,49 @@ CreateJupiterBuildStorage(LoggerRef InLog,
return std::make_unique<JupiterBuildStorage>(InLog, InHttpClient, Stats, Namespace, Bucket, AllowRedirect, TempFolderPath);
}
+bool
+ParseBuildStorageUrl(std::string_view InUrl,
+ std::string& OutHost,
+ std::string& OutNamespace,
+ std::string& OutBucket,
+ std::string& OutBuildId)
+{
+ std::string Url(InUrl);
+ const std::string_view ExtendedApiString = "api/v2/builds/";
+ if (auto ApiString = ToLower(Url).find(ExtendedApiString); ApiString != std::string::npos)
+ {
+ Url.erase(ApiString, ExtendedApiString.length());
+ }
+
+ const std::string ArtifactURLRegExString = R"((http[s]?:\/\/.*?)\/(.*?)\/(.*?)\/(.*))";
+ const std::regex ArtifactURLRegEx(ArtifactURLRegExString, std::regex::ECMAScript | std::regex::icase);
+ std::match_results<std::string_view::const_iterator> MatchResults;
+ std::string_view UrlToParse(Url);
+ if (regex_match(begin(UrlToParse), end(UrlToParse), MatchResults, ArtifactURLRegEx) && MatchResults.size() == 5)
+ {
+ auto GetMatch = [&MatchResults](uint32_t Index) -> std::string_view {
+ ZEN_ASSERT(Index < MatchResults.size());
+
+ const auto& Match = MatchResults[Index];
+
+ return std::string_view(&*Match.first, Match.second - Match.first);
+ };
+
+ const std::string_view Host = GetMatch(1);
+ const std::string_view Namespace = GetMatch(2);
+ const std::string_view Bucket = GetMatch(3);
+ const std::string_view BuildId = GetMatch(4);
+
+ OutHost = Host;
+ OutNamespace = Namespace;
+ OutBucket = Bucket;
+ OutBuildId = BuildId;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
} // namespace zen
diff --git a/src/zenutil/jupiter/jupiterhost.cpp b/src/zenutil/jupiter/jupiterhost.cpp
new file mode 100644
index 000000000..d06229cbf
--- /dev/null
+++ b/src/zenutil/jupiter/jupiterhost.cpp
@@ -0,0 +1,66 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenutil/jupiter/jupiterhost.h>
+
+#include <zencore/compactbinary.h>
+#include <zencore/fmtutils.h>
+#include <zenhttp/httpclient.h>
+
+namespace zen {
+
+JupiterServerDiscovery
+DiscoverJupiterEndpoints(std::string_view Host, const HttpClientSettings& ClientSettings)
+{
+ JupiterServerDiscovery Result;
+
+ HttpClient DiscoveryHttpClient(Host, ClientSettings);
+ HttpClient::Response ServerInfoResponse = DiscoveryHttpClient.Get("/api/v1/status/servers", HttpClient::Accept(HttpContentType::kJSON));
+ if (!ServerInfoResponse.IsSuccess())
+ {
+ ServerInfoResponse.ThrowError(fmt::format("Failed to get list of servers from discovery url '{}'", Host));
+ }
+ std::string_view JsonResponse = ServerInfoResponse.AsText();
+ CbObject CbPayload = LoadCompactBinaryFromJson(JsonResponse).AsObject();
+ CbArrayView ServerEndpoints = CbPayload["serverEndpoints"].AsArrayView();
+ Result.ServerEndPoints.reserve(ServerEndpoints.Num());
+
+ auto ParseEndPoints = [](CbArrayView ServerEndpoints) -> std::vector<JupiterServerDiscovery::EndPoint> {
+ std::vector<JupiterServerDiscovery::EndPoint> Result;
+
+ Result.reserve(ServerEndpoints.Num());
+ for (CbFieldView ServerEndpointView : ServerEndpoints)
+ {
+ CbObjectView ServerEndPoint = ServerEndpointView.AsObjectView();
+ Result.push_back(JupiterServerDiscovery::EndPoint{.Name = std::string(ServerEndPoint["name"].AsString()),
+ .BaseUrl = std::string(ServerEndPoint["baseUrl"].AsString()),
+ .AssumeHttp2 = ServerEndPoint["baseUrl"].AsBool(false)});
+ }
+ return Result;
+ };
+
+ Result.ServerEndPoints = ParseEndPoints(CbPayload["serverEndpoints"].AsArrayView());
+ Result.CacheEndPoints = ParseEndPoints(CbPayload["cacheEndpoints"].AsArrayView());
+
+ return Result;
+}
+
+JupiterEndpointTestResult
+TestJupiterEndpoint(std::string_view BaseUrl, const bool AssumeHttp2)
+{
+ HttpClientSettings TestClientSettings{.LogCategory = "httpbuildsclient",
+ .ConnectTimeout = std::chrono::milliseconds{1000},
+ .Timeout = std::chrono::milliseconds{2000},
+ .AssumeHttp2 = AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0};
+
+ HttpClient TestHttpClient(BaseUrl, TestClientSettings);
+ HttpClient::Response TestResponse = TestHttpClient.Get("/health/live");
+ if (TestResponse.IsSuccess())
+ {
+ return {.Success = true};
+ }
+ return {.Success = false, .FailureReason = TestResponse.ErrorMessage("")};
+}
+
+} // namespace zen