aboutsummaryrefslogtreecommitdiff
path: root/src/zen/cmds/serve_cmd.cpp
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2023-09-22 08:22:06 -0400
committerGitHub <[email protected]>2023-09-22 14:22:06 +0200
commitc7d4dc6a4d13881028d566f5ce501335e47e48bf (patch)
tree493110da583a8e5d97fe05e14f23469ee6244d2b /src/zen/cmds/serve_cmd.cpp
parentadd trace command to enable/disable tracing at runtime (#416) (diff)
downloadarchived-zen-c7d4dc6a4d13881028d566f5ce501335e47e48bf.tar.xz
archived-zen-c7d4dc6a4d13881028d566f5ce501335e47e48bf.zip
Collect all zen admin-related commands into admin.h/.cpp (#418)
* move commands in scrub.h/cpp to admin_cmd.h/cpp * move job command into admin_cmd.h/.cpp * admin -> admin_cmd * bench -> bench_cmd * cache -> cache_cmd * copy -> copy_cmd * dedup -> dedup_cmd * hash -> hash_cmd * print -> print_cmd * projectstore -> projectstore_cmd * rpcreplay -> rpcreplay_cmd * serve -> serve_cmd * status -> status_cmd * top -> top_cmd * trace -> trace_cmd * up -> up_cmd * version -> version_cmd
Diffstat (limited to 'src/zen/cmds/serve_cmd.cpp')
-rw-r--r--src/zen/cmds/serve_cmd.cpp242
1 files changed, 242 insertions, 0 deletions
diff --git a/src/zen/cmds/serve_cmd.cpp b/src/zen/cmds/serve_cmd.cpp
new file mode 100644
index 000000000..c8117774b
--- /dev/null
+++ b/src/zen/cmds/serve_cmd.cpp
@@ -0,0 +1,242 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "serve_cmd.h"
+
+#include <zencore/blake3.h>
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinarypackage.h>
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zenhttp/httpclient.h>
+#include <zenutil/zenserverprocess.h>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <conio.h> // TEMPORARY HACK
+#endif
+
+namespace zen {
+
+using namespace std::literals;
+
+ServeCommand::ServeCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+ m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>");
+ m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>");
+ m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>");
+ m_Options.add_option("", "", "path", "Root path to directory", cxxopts::value(m_RootPath), "<rootpath>");
+
+ m_Options.parse_positional({"project", "path"});
+ m_Options.positional_help("[<projectid> <rootpath>]");
+}
+
+ServeCommand::~ServeCommand()
+{
+}
+
+int
+ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ if (!ParseOptions(argc, argv))
+ {
+ return 0;
+ }
+
+ if (m_ProjectName.empty())
+ {
+ throw zen::OptionParseException("command requires a project");
+ }
+
+ if (m_OplogName.empty())
+ {
+ if (auto pos = m_ProjectName.find_first_of('/'); pos != std::string::npos)
+ {
+ m_OplogName = m_ProjectName.substr(pos + 1);
+ m_ProjectName = m_ProjectName.substr(0, pos);
+ }
+ else
+ {
+ throw zen::OptionParseException("command requires an oplog");
+ }
+ }
+
+ if (m_RootPath.empty())
+ {
+ throw zen::OptionParseException("command requires a root path");
+ }
+
+ if (!std::filesystem::exists(m_RootPath) || !std::filesystem::is_directory(m_RootPath))
+ {
+ throw zen::OptionParseException(fmt::format("path must exist and must be a directory: '{}'", m_RootPath));
+ }
+
+ uint16_t ServerPort = 0;
+ m_HostName = ResolveTargetHostSpec(m_HostName, ServerPort);
+
+ ZenServerEnvironment ServerEnvironment;
+ std::optional<ZenServerInstance> ServerInstance;
+
+ if (m_HostName.empty())
+ {
+ // Spawn a server
+
+ try
+ {
+ std::filesystem::path ExePath = zen::GetRunningExecutablePath();
+
+ ServerEnvironment.Initialize(ExePath.parent_path());
+ ServerInstance.emplace(ServerEnvironment);
+ ServerInstance->SetOwnerPid(zen::GetCurrentProcessId());
+ ServerInstance->SpawnServerAndWait(ServerPort);
+ }
+ catch (std::exception& Ex)
+ {
+ ZEN_CONSOLE("failed to spawn server on port {}: '{}'", ServerPort, Ex.what());
+
+ throw zen::OptionParseException("unable to resolve server specification (even after spawning server)");
+ }
+ }
+ else
+ {
+ std::filesystem::path ExePath = zen::GetRunningExecutablePath();
+
+ ServerEnvironment.Initialize(ExePath.parent_path());
+ ServerInstance.emplace(ServerEnvironment);
+ ServerInstance->DisableShutdownOnDestroy();
+ ServerInstance->AttachToRunningServer();
+ }
+
+ if (ServerInstance)
+ {
+ m_HostName = ServerInstance->GetBaseUri();
+ ZEN_CONSOLE("base uri: {}", m_HostName);
+ }
+
+ // Generate manifest for tree
+
+ FileSystemTraversal Traversal;
+
+ struct FsVisitor : public FileSystemTraversal::TreeVisitor
+ {
+ virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize) override
+ {
+ std::filesystem::path ServerPath = std::filesystem::relative(Parent / File, RootPath);
+ std::string ServerPathString = reinterpret_cast<const char*>(ServerPath.generic_u8string().c_str());
+
+ if (ServerPathString.starts_with("./"))
+ {
+ ServerPathString = ServerPathString.substr(2);
+ }
+
+ Files.emplace_back(FileEntry{ServerPathString, ServerPathString, FileSize});
+ }
+
+ virtual bool VisitDirectory(const std::filesystem::path&, const path_view&) override { return true; }
+
+ struct FileEntry
+ {
+ std::string FilePath;
+ std::string ClientFilePath;
+ uint64_t FileSize;
+ };
+
+ std::filesystem::path RootPath;
+ std::vector<FileEntry> Files;
+ };
+
+ FsVisitor Visitor;
+ Visitor.RootPath = m_RootPath;
+ Traversal.TraverseFileSystem(m_RootPath, Visitor);
+
+ CbObjectWriter Cbo;
+
+ Cbo << "key"
+ << "file_manifest";
+
+ Cbo.BeginArray("files");
+
+ for (const FsVisitor::FileEntry& Entry : Visitor.Files)
+ {
+ ZEN_CONSOLE("file: {}", Entry.FilePath);
+
+ Cbo.BeginObject();
+
+ BLAKE3 Hash = BLAKE3::HashMemory(Entry.ClientFilePath.data(), Entry.ClientFilePath.size());
+ Hash.Hash[11] = 7; // FIoChunkType::ExternalFile
+ Oid FileChunkId = Oid::FromMemory(Hash.Hash);
+
+ Cbo << "id"sv << FileChunkId;
+ Cbo << "serverpath"sv << Entry.FilePath;
+ Cbo << "clientpath"sv << Entry.ClientFilePath;
+
+ Cbo.EndObject();
+ }
+
+ Cbo.EndArray();
+
+ CbObject Manifest = Cbo.Save();
+
+ // Persist manifest
+
+ const std::string ProjectUri = fmt::format("/prj/{}", m_ProjectName);
+ const std::string ProjectOplogUri = fmt::format("/prj/{}/oplog/{}", m_ProjectName, m_OplogName);
+
+ HttpClient Client(m_HostName);
+
+ // Ensure project exists
+
+ if (HttpClient::Response ProjectResponse = Client.Get(ProjectUri); !ProjectResponse)
+ {
+ // Create project
+
+ CbObjectWriter Project;
+
+ Project << "root" << m_RootPath;
+
+ if (auto NewProjectResponse = Client.Post(ProjectUri, Project.Save()); !NewProjectResponse)
+ {
+ // TODO: include details
+ throw std::runtime_error("failed to create project");
+ }
+ }
+
+ // Ensure oplog exists
+
+ if (HttpClient::Response OplogResponse = Client.Get(ProjectOplogUri); !OplogResponse)
+ {
+ // Create oplog
+
+ CbObjectWriter Oplog;
+
+ if (auto NewOplogResponse = Client.Post(ProjectOplogUri, Oplog.Save()); !NewOplogResponse)
+ {
+ // TODO: include details
+ throw std::runtime_error("failed to create oplog");
+ }
+ }
+
+ // Append manifest
+
+ const std::string Uri = fmt::format("/prj/{}/oplog/{}/new", m_ProjectName, m_OplogName);
+
+ HttpClient::Response HttpResponse = Client.Post(Uri, Manifest);
+
+ if (!HttpResponse)
+ {
+ ZEN_CONSOLE("error: failed to append manifest!");
+
+ return 1;
+ }
+
+ ZEN_CONSOLE("ok serving files now");
+
+#if ZEN_PLATFORM_WINDOWS
+ _getch(); // TEMPORARY HACK
+#endif
+
+ return 0;
+}
+
+} // namespace zen