diff options
| author | Dan Engelbrecht <[email protected]> | 2023-08-18 11:21:12 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-08-18 11:21:12 +0200 |
| commit | 3975db7eb6ab9457c90a32c2744843d9e757b370 (patch) | |
| tree | bfcebcf91f5c86fd80d0f48c52b123ed8eabfca8 /src | |
| parent | check oplog op attachments when gathering references for GC (#363) (diff) | |
| download | zen-3975db7eb6ab9457c90a32c2744843d9e757b370.tar.xz zen-3975db7eb6ab9457c90a32c2744843d9e757b370.zip | |
add update/delete endpoint for project and oplog (#353)
* add update endpoint for project store project
* add update endpoint for oplog
* changelog
* Zen command line tool `project-update`
Zen command line tool `project-delete`
Zen command line tool `oplog-update`
Zen command line tool `oplog-delete`
* add --force-update option to project/oplog create
remove project/oplog update commnad
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/projectstore.cpp | 139 | ||||
| -rw-r--r-- | src/zen/cmds/projectstore.h | 37 | ||||
| -rw-r--r-- | src/zen/zen.cpp | 4 | ||||
| -rw-r--r-- | src/zenserver/projectstore/httpprojectstore.cpp | 91 | ||||
| -rw-r--r-- | src/zenserver/projectstore/projectstore.cpp | 40 | ||||
| -rw-r--r-- | src/zenserver/projectstore/projectstore.h | 6 |
6 files changed, 307 insertions, 10 deletions
diff --git a/src/zen/cmds/projectstore.cpp b/src/zen/cmds/projectstore.cpp index f7dc82569..b2f1cda5d 100644 --- a/src/zen/cmds/projectstore.cpp +++ b/src/zen/cmds/projectstore.cpp @@ -189,6 +189,7 @@ CreateProjectCommand::CreateProjectCommand() m_Options.add_option("", "", "enginedir", "Absolute path to engine root directory", cxxopts::value(m_EngineRootDir), "<engineroot>"); m_Options.add_option("", "", "projectdir", "Absolute path to project directory", cxxopts::value(m_ProjectRootDir), "<projectroot>"); m_Options.add_option("", "", "projectfile", "Absolute path to .uproject file", cxxopts::value(m_ProjectFile), "<projectfile>"); + m_Options.add_option("", "f", "force-update", "Force update of existing project", cxxopts::value(m_ForceUpdate), "<force-update>"); m_Options.parse_positional({"project", "rootdir", "enginedir", "projectdir", "projectfile"}); } @@ -224,13 +225,13 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a Session.SetUrl({fmt::format("{}/prj/{}", m_HostName, m_ProjectId)}); cpr::Response Response = Session.Get(); - if (zen::IsHttpSuccessCode(Response.status_code)) + if (zen::IsHttpSuccessCode(Response.status_code) && !m_ForceUpdate) { ZEN_CONSOLE("Project already exists.\n{}", Response.text); return 1; } - if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound)) + if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound) || m_ForceUpdate) { zen::CbObjectWriter Project; Project.AddString("id"sv, m_ProjectId); @@ -241,9 +242,68 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a zen::IoBuffer ProjectPayload = Project.Save().GetBuffer().AsIoBuffer(); Session.SetBody(cpr::Body{(const char*)ProjectPayload.GetData(), ProjectPayload.GetSize()}); Session.SetHeader(cpr::Header{{"Accept", "text"}}); - Response = Session.Post(); + Response = m_ForceUpdate ? Session.Post() : Session.Put(); + } + + ZEN_CONSOLE("{}", FormatHttpResponse(Response)); + return MapHttpToCommandReturnCode(Response); +} + +/////////////////////////////////////// + +DeleteProjectCommand::DeleteProjectCommand() +{ + 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_ProjectId), "<projectid>"); +} + +DeleteProjectCommand::~DeleteProjectCommand() = default; + +int +DeleteProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) +{ + ZEN_UNUSED(GlobalOptions); + + using namespace std::literals; + + if (!ParseOptions(argc, argv)) + { + return 0; + } + + m_HostName = ResolveTargetHostSpec(m_HostName); + + if (m_HostName.empty()) + { + throw zen::OptionParseException("unable to resolve server specification"); } + cpr::Session Session; + Session.SetHeader(cpr::Header{{"Accept", "application/json"}}); + + if (m_ProjectId.empty()) + { + ZEN_ERROR("Project name must be given"); + return 1; + } + + Session.SetUrl({fmt::format("{}/prj/{}", m_HostName, m_ProjectId)}); + cpr::Response Response = Session.Get(); + if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound)) + { + ZEN_CONSOLE("Project does not exist.\n{}", Response.text); + return 1; + } + if (!zen::IsHttpSuccessCode(Response.status_code)) + { + ZEN_CONSOLE("{}", FormatHttpResponse(Response)); + return 1; + } + + Session.SetHeader(cpr::Header{{"Accept", "text"}}); + Response = Session.Delete(); + ZEN_CONSOLE("{}", FormatHttpResponse(Response)); return MapHttpToCommandReturnCode(Response); } @@ -257,6 +317,7 @@ CreateOplogCommand::CreateOplogCommand() m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), "<oplogid>"); m_Options.add_option("", "", "gcpath", "Absolute path to oplog lifetime marker file", cxxopts::value(m_GcPath), "<path>"); + m_Options.add_option("", "f", "force-update", "Force update of existing oplog", cxxopts::value(m_ForceUpdate), "<force-update>"); m_Options.parse_positional({"project", "oplog", "gcpath"}); } @@ -296,13 +357,13 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg Session.SetUrl({fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectId, m_OplogId)}); cpr::Response Response = Session.Get(); - if (zen::IsHttpSuccessCode(Response.status_code)) + if (zen::IsHttpSuccessCode(Response.status_code) && !m_ForceUpdate) { ZEN_CONSOLE("Oplog already exists.\n{}", Response.text); return 1; } - if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound)) + if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound) || m_ForceUpdate) { Session.SetHeader(cpr::Header{{"Accept", "text"}}); if (!m_GcPath.empty()) @@ -314,7 +375,7 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg Session.SetHeader(cpr::Header{{"Accept", "text"}, {"Content-Type", std::string(ToString(zen::HttpContentType::kCbObject))}}); } - Response = Session.Post(); + Response = m_ForceUpdate ? Session.Post() : Session.Put(); } ZEN_CONSOLE("{}", FormatHttpResponse(Response)); @@ -324,6 +385,72 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg /////////////////////////////////////// +DeleteOplogCommand::DeleteOplogCommand() +{ + 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_ProjectId), "<projectid>"); + m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), "<oplogid>"); + m_Options.parse_positional({"project", "oplog", "gcpath"}); +} + +DeleteOplogCommand::~DeleteOplogCommand() = default; + +int +DeleteOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) +{ + ZEN_UNUSED(GlobalOptions); + + using namespace std::literals; + + if (!ParseOptions(argc, argv)) + { + return 0; + } + + m_HostName = ResolveTargetHostSpec(m_HostName); + + if (m_HostName.empty()) + { + throw zen::OptionParseException("unable to resolve server specification"); + } + + cpr::Session Session; + Session.SetHeader(cpr::Header{{"Accept", "application/json"}}); + + if (m_ProjectId.empty()) + { + throw zen::OptionParseException("project name must be specified"); + } + + if (m_OplogId.empty()) + { + throw zen::OptionParseException("oplog name must be specified"); + } + + Session.SetUrl({fmt::format("{}/prj/{}/oplog/{}", m_HostName, m_ProjectId, m_OplogId)}); + cpr::Response Response = Session.Get(); + if (Response.status_code == static_cast<long>(zen::HttpResponseCode::NotFound)) + { + ZEN_CONSOLE("Oplog does not exist.\n{}", Response.text); + return 1; + } + if (!zen::IsHttpSuccessCode(Response.status_code)) + { + ZEN_CONSOLE("{}", FormatHttpResponse(Response)); + return 1; + } + + Session.SetHeader(cpr::Header{{"Accept", "text"}}); + Response = Session.Delete(); + + ZEN_CONSOLE("{}", FormatHttpResponse(Response)); + + return MapHttpToCommandReturnCode(Response); +} + +/////////////////////////////////////// + ExportOplogCommand::ExportOplogCommand() { m_Options.add_options()("h,help", "Print help"); diff --git a/src/zen/cmds/projectstore.h b/src/zen/cmds/projectstore.h index 01675e885..5b1cd05db 100644 --- a/src/zen/cmds/projectstore.h +++ b/src/zen/cmds/projectstore.h @@ -45,13 +45,29 @@ public: virtual cxxopts::Options& Options() override { return m_Options; } private: - cxxopts::Options m_Options{"project-create", "Create project"}; + cxxopts::Options m_Options{"project-create", "Create project, the project must not already exist."}; std::string m_HostName; std::string m_ProjectId; std::string m_RootDir; std::string m_EngineRootDir; std::string m_ProjectRootDir; std::string m_ProjectFile; + bool m_ForceUpdate = false; +}; + +class DeleteProjectCommand : public ZenCmdBase +{ +public: + DeleteProjectCommand(); + ~DeleteProjectCommand(); + + virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override; + virtual cxxopts::Options& Options() override { return m_Options; } + +private: + cxxopts::Options m_Options{"project-delete", "Delete project and all its oplogs"}; + std::string m_HostName; + std::string m_ProjectId; }; class CreateOplogCommand : public ZenCmdBase @@ -64,11 +80,28 @@ public: virtual cxxopts::Options& Options() override { return m_Options; } private: - cxxopts::Options m_Options{"oplog-create", "Create oplog"}; + cxxopts::Options m_Options{"oplog-create", "Create oplog in an existing project, the oplog must not already exist."}; std::string m_HostName; std::string m_ProjectId; std::string m_OplogId; std::string m_GcPath; + bool m_ForceUpdate = false; +}; + +class DeleteOplogCommand : public ZenCmdBase +{ +public: + DeleteOplogCommand(); + ~DeleteOplogCommand(); + + virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override; + virtual cxxopts::Options& Options() override { return m_Options; } + +private: + cxxopts::Options m_Options{"oplog-delete", "Delete oplog and all its data"}; + std::string m_HostName; + std::string m_ProjectId; + std::string m_OplogId; }; class ExportOplogCommand : public ZenCmdBase diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp index 6031c8be6..6772e7ab4 100644 --- a/src/zen/zen.cpp +++ b/src/zen/zen.cpp @@ -188,7 +188,9 @@ main(int argc, char** argv) CacheStatsCommand CacheStatsCmd; CopyCommand CopyCmd; CreateOplogCommand CreateOplogCmd; + DeleteOplogCommand DeleteOplogCmd; CreateProjectCommand CreateProjectCmd; + DeleteProjectCommand DeleteProjectCmd; DedupCommand DedupCmd; DownCommand DownCmd; DropCommand DropCmd; @@ -238,12 +240,14 @@ main(int argc, char** argv) {"gc", &GcCmd, "Garbage collect zen storage"}, {"hash", &HashCmd, "Compute file hashes"}, {"oplog-create", &CreateOplogCmd, "Create a project oplog"}, + {"oplog-delete", &DeleteOplogCmd, "Delete a project oplog"}, {"oplog-export", &ExportOplogCmd, "Export project store oplog"}, {"oplog-import", &ImportOplogCmd, "Import project store oplog"}, {"oplog-snapshot", &SnapshotOplogCmd, "Snapshot project store oplog"}, {"print", &PrintCmd, "Print compact binary object"}, {"printpackage", &PrintPkgCmd, "Print compact binary package"}, {"project-create", &CreateProjectCmd, "Create a project"}, + {"project-delete", &DeleteProjectCmd, "Delete a project"}, {"project-details", &ProjectDetailsCmd, "Details on project store"}, {"project-drop", &ProjectDropCmd, "Drop project or project oplog"}, {"project-info", &ProjectInfoCmd, "Info on project or project oplog"}, diff --git a/src/zenserver/projectstore/httpprojectstore.cpp b/src/zenserver/projectstore/httpprojectstore.cpp index f52473d99..124f26692 100644 --- a/src/zenserver/projectstore/httpprojectstore.cpp +++ b/src/zenserver/projectstore/httpprojectstore.cpp @@ -1035,6 +1035,45 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects, } break; + case HttpVerb::kPut: + { + if (!m_ProjectStore->AreDiskWritesAllowed()) + { + return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage); + } + + std::filesystem::path OplogMarkerPath; + if (CbObject Params = HttpReq.ReadPayloadObject()) + { + OplogMarkerPath = Params["gcpath"sv].AsString(); + } + + ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId); + if (!FoundLog) + { + if (!Project->NewOplog(OplogId, OplogMarkerPath)) + { + // TODO: indicate why the operation failed! + return HttpReq.WriteResponse(HttpResponseCode::InternalServerError); + } + Project->TouchOplog(OplogId); + + m_ProjectStats.OpLogWriteCount++; + ZEN_INFO("established oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath); + + return HttpReq.WriteResponse(HttpResponseCode::Created); + } + Project->TouchOplog(OplogId); + + FoundLog->Update(OplogMarkerPath); + + m_ProjectStats.OpLogWriteCount++; + ZEN_INFO("updated oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath); + + return HttpReq.WriteResponse(HttpResponseCode::OK); + } + break; + case HttpVerb::kDelete: { ZEN_INFO("deleting oplog '{}/{}'", ProjectId, OplogId); @@ -1050,7 +1089,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects, break; } }, - HttpVerb::kPost | HttpVerb::kGet | HttpVerb::kDelete); + HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete); m_Router.RegisterRoute( "{project}/oplog/{log}/entries", @@ -1150,6 +1189,54 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects, } break; + case HttpVerb::kPut: + { + if (!m_ProjectStore->AreDiskWritesAllowed()) + { + return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage); + } + + IoBuffer Payload = HttpReq.ReadPayload(); + CbObject Params = LoadCompactBinaryObject(Payload); + std::string_view Root = Params["root"sv].AsString(); // Workspace root (i.e `D:/UE5/`) + std::string_view EngineRoot = Params["engine"sv].AsString(); // Engine root (i.e `D:/UE5/Engine`) + std::string_view ProjectRoot = + Params["project"sv].AsString(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`) + std::string_view ProjectFilePath = + Params["projectfile"sv].AsString(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`) + + if (m_ProjectStore->UpdateProject(ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath)) + { + m_ProjectStats.ProjectWriteCount++; + ZEN_INFO("updated project (id: '{}', roots: '{}', '{}', '{}', '{}'{})", + ProjectId, + Root, + EngineRoot, + ProjectRoot, + ProjectFilePath, + ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : ""); + + HttpReq.WriteResponse(HttpResponseCode::OK); + } + else + { + const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId; + m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath); + + m_ProjectStats.ProjectWriteCount++; + ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})", + ProjectId, + Root, + EngineRoot, + ProjectRoot, + ProjectFilePath, + ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : ""); + + HttpReq.WriteResponse(HttpResponseCode::Created); + } + } + break; + case HttpVerb::kGet: { Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId); @@ -1212,7 +1299,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects, break; } }, - HttpVerb::kGet | HttpVerb::kPost | HttpVerb::kDelete); + HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kPost | HttpVerb::kDelete); // Push a oplog container m_Router.RegisterRoute( diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp index 38674d28d..d20466161 100644 --- a/src/zenserver/projectstore/projectstore.cpp +++ b/src/zenserver/projectstore/projectstore.cpp @@ -559,6 +559,16 @@ ProjectStore::Oplog::Write() } void +ProjectStore::Oplog::Update(const std::filesystem::path& MarkerPath) +{ + if (m_MarkerPath == MarkerPath) + { + return; + } + Write(); +} + +void ProjectStore::Oplog::ReplayLog() { RwLock::ExclusiveLockScope OplogLock(m_OplogLock); @@ -1814,6 +1824,36 @@ ProjectStore::NewProject(const std::filesystem::path& BasePath, } bool +ProjectStore::UpdateProject(std::string_view ProjectId, + std::string_view RootDir, + std::string_view EngineRootDir, + std::string_view ProjectRootDir, + std::string_view ProjectFilePath) +{ + ZEN_TRACE_CPU("ProjectStore::UpdateProject"); + + ZEN_INFO("updating project {}", ProjectId); + + RwLock::ExclusiveLockScope ProjectsLock(m_ProjectsLock); + + auto ProjIt = m_Projects.find(std::string{ProjectId}); + + if (ProjIt == m_Projects.end()) + { + return false; + } + Ref<ProjectStore::Project> Prj = ProjIt->second; + + Prj->RootDir = RootDir; + Prj->EngineRootDir = EngineRootDir; + Prj->ProjectRootDir = ProjectRootDir; + Prj->ProjectFilePath = ProjectFilePath; + Prj->Write(); + + return true; +} + +bool ProjectStore::DeleteProject(std::string_view ProjectId) { ZEN_TRACE_CPU("ProjectStore::DeleteProject"); diff --git a/src/zenserver/projectstore/projectstore.h b/src/zenserver/projectstore/projectstore.h index eef1b15e0..dcbe8cdab 100644 --- a/src/zenserver/projectstore/projectstore.h +++ b/src/zenserver/projectstore/projectstore.h @@ -82,6 +82,7 @@ public: void Read(); void Write(); + void Update(const std::filesystem::path& MarkerPath); void IterateFileMap(std::function<void(const Oid&, const std::string_view& ServerPath, const std::string_view& ClientPath)>&& Fn); void IterateOplog(std::function<void(CbObject)>&& Fn); @@ -257,6 +258,11 @@ public: std::string_view EngineRootDir, std::string_view ProjectRootDir, std::string_view ProjectFilePath); + bool UpdateProject(std::string_view ProjectId, + std::string_view RootDir, + std::string_view EngineRootDir, + std::string_view ProjectRootDir, + std::string_view ProjectFilePath); bool DeleteProject(std::string_view ProjectId); bool Exists(std::string_view ProjectId); void Flush(); |