diff options
Diffstat (limited to 'src/zenserver/hub/httphubservice.cpp')
| -rw-r--r-- | src/zenserver/hub/httphubservice.cpp | 356 |
1 files changed, 297 insertions, 59 deletions
diff --git a/src/zenserver/hub/httphubservice.cpp b/src/zenserver/hub/httphubservice.cpp index 67ed0cfd8..eba816793 100644 --- a/src/zenserver/hub/httphubservice.cpp +++ b/src/zenserver/hub/httphubservice.cpp @@ -2,16 +2,53 @@ #include "httphubservice.h" +#include "httpproxyhandler.h" #include "hub.h" #include "storageserverinstance.h" #include <zencore/compactbinarybuilder.h> #include <zencore/fmtutils.h> #include <zencore/logging.h> +#include <zenhttp/httpstats.h> namespace zen { -HttpHubService::HttpHubService(Hub& Hub) : m_Hub(Hub) +namespace { + bool HandleFailureResults(HttpServerRequest& Request, const Hub::Response& Resp) + { + if (Resp.ResponseCode == Hub::EResponseCode::Rejected) + { + if (Resp.Message.empty()) + { + Request.WriteResponse(HttpResponseCode::Conflict); + } + else + { + Request.WriteResponse(HttpResponseCode::Conflict, HttpContentType::kText, Resp.Message); + } + return true; + } + if (Resp.ResponseCode == Hub::EResponseCode::NotFound) + { + if (Resp.Message.empty()) + { + Request.WriteResponse(HttpResponseCode::NotFound); + } + else + { + Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, Resp.Message); + } + return true; + } + return false; + } +} // namespace + +HttpHubService::HttpHubService(Hub& Hub, HttpProxyHandler& Proxy, HttpStatsService& StatsService, HttpStatusService& StatusService) +: m_Hub(Hub) +, m_StatsService(StatsService) +, m_StatusService(StatusService) +, m_Proxy(Proxy) { using namespace std::literals; @@ -32,15 +69,50 @@ HttpHubService::HttpHubService(Hub& Hub) : m_Hub(Hub) return true; }); + m_Router.AddMatcher("port", [](std::string_view Str) -> bool { + if (Str.empty()) + { + return false; + } + for (const auto C : Str) + { + if (!std::isdigit(C)) + { + return false; + } + } + return true; + }); + + m_Router.AddMatcher("proxypath", [](std::string_view Str) -> bool { return !Str.empty(); }); + m_Router.RegisterRoute( "status", [this](HttpRouterRequest& Req) { CbObjectWriter Obj; Obj.BeginArray("modules"); - m_Hub.EnumerateModules([&Obj](StorageServerInstance& Instance) { + m_Hub.EnumerateModules([&Obj](std::string_view ModuleId, const Hub::InstanceInfo& Info) { Obj.BeginObject(); - Obj << "moduleId" << Instance.GetModuleId(); - Obj << "provisioned" << Instance.IsProvisioned(); + { + Obj << "moduleId" << ModuleId; + Obj << "state" << ToString(Info.State); + Obj << "port" << Info.Port; + if (Info.StateChangeTime != std::chrono::system_clock::time_point::min()) + { + Obj << "state_change_time" << ToDateTime(Info.StateChangeTime); + } + Obj.BeginObject("process_metrics"); + { + Obj << "MemoryBytes" << Info.Metrics.MemoryBytes; + Obj << "KernelTimeMs" << Info.Metrics.KernelTimeMs; + Obj << "UserTimeMs" << Info.Metrics.UserTimeMs; + Obj << "WorkingSetSize" << Info.Metrics.WorkingSetSize; + Obj << "PeakWorkingSetSize" << Info.Metrics.PeakWorkingSetSize; + Obj << "PagefileUsage" << Info.Metrics.PagefileUsage; + Obj << "PeakPagefileUsage" << Info.Metrics.PeakPagefileUsage; + } + Obj.EndObject(); + } Obj.EndObject(); }); Obj.EndArray(); @@ -69,89 +141,143 @@ HttpHubService::HttpHubService(Hub& Hub) : m_Hub(Hub) [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); - std::string FailureReason = "unknown"; - HttpResponseCode ResponseCode = HttpResponseCode::OK; - try { HubProvisionedInstanceInfo Info; - if (m_Hub.Provision(ModuleId, /* out */ Info, /* out */ FailureReason)) - { - CbObjectWriter Obj; - Obj << "moduleId" << ModuleId; - Obj << "baseUri" << Info.BaseUri; - Obj << "port" << Info.Port; - Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); + Hub::Response Resp = m_Hub.Provision(ModuleId, Info); - return; - } - else + if (HandleFailureResults(Req.ServerRequest(), Resp)) { - ResponseCode = HttpResponseCode::BadRequest; + return; } + + const HttpResponseCode HttpCode = + (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; + CbObjectWriter Obj; + Obj << "moduleId" << ModuleId; + Obj << "baseUri" << Info.BaseUri; + Obj << "port" << Info.Port; + return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while provisioning module '{}': {}", ModuleId, Ex.what()); - - FailureReason = Ex.what(); - ResponseCode = HttpResponseCode::InternalServerError; + throw; } - - Req.ServerRequest().WriteResponse(ResponseCode, HttpContentType::kText, FailureReason); }, HttpVerb::kPost); m_Router.RegisterRoute( "modules/{moduleid}/deprovision", [this](HttpRouterRequest& Req) { - std::string_view ModuleId = Req.GetCapture(1); - std::string FailureReason = "unknown"; + std::string_view ModuleId = Req.GetCapture(1); try { - if (!m_Hub.Deprovision(std::string(ModuleId), /* out */ FailureReason)) + Hub::Response Resp = m_Hub.Deprovision(std::string(ModuleId)); + + if (HandleFailureResults(Req.ServerRequest(), Resp)) { - if (FailureReason.empty()) - { - return Req.ServerRequest().WriteResponse(HttpResponseCode::NotFound); - } - else - { - return Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, FailureReason); - } + return; } + const HttpResponseCode HttpCode = + (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; CbObjectWriter Obj; Obj << "moduleId" << ModuleId; - - return Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); + return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); } catch (const std::exception& Ex) { ZEN_ERROR("Exception while deprovisioning module '{}': {}", ModuleId, Ex.what()); + throw; + } + }, + HttpVerb::kPost); - FailureReason = Ex.what(); + m_Router.RegisterRoute( + "modules/{moduleid}/hibernate", + [this](HttpRouterRequest& Req) { + std::string_view ModuleId = Req.GetCapture(1); + + try + { + Hub::Response Resp = m_Hub.Hibernate(std::string(ModuleId)); + + if (HandleFailureResults(Req.ServerRequest(), Resp)) + { + return; + } + + const HttpResponseCode HttpCode = + (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; + CbObjectWriter Obj; + Obj << "moduleId" << ModuleId; + return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); } + catch (const std::exception& Ex) + { + ZEN_ERROR("Exception while hibernating module '{}': {}", ModuleId, Ex.what()); + throw; + } + }, + HttpVerb::kPost); + + m_Router.RegisterRoute( + "modules/{moduleid}/wake", + [this](HttpRouterRequest& Req) { + std::string_view ModuleId = Req.GetCapture(1); + + try + { + Hub::Response Resp = m_Hub.Wake(std::string(ModuleId)); + + if (HandleFailureResults(Req.ServerRequest(), Resp)) + { + return; + } - Req.ServerRequest().WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, FailureReason); + const HttpResponseCode HttpCode = + (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; + CbObjectWriter Obj; + Obj << "moduleId" << ModuleId; + return Req.ServerRequest().WriteResponse(HttpCode, Obj.Save()); + } + catch (const std::exception& Ex) + { + ZEN_ERROR("Exception while waking module '{}': {}", ModuleId, Ex.what()); + throw; + } }, HttpVerb::kPost); m_Router.RegisterRoute( - "stats", + "proxy/{port}/{proxypath}", [this](HttpRouterRequest& Req) { - CbObjectWriter Obj; - Obj << "currentInstanceCount" << m_Hub.GetInstanceCount(); - Obj << "maxInstanceCount" << m_Hub.GetMaxInstanceCount(); - Obj << "instanceLimit" << m_Hub.GetConfig().InstanceLimit; - Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); + std::string_view PortStr = Req.GetCapture(1); + + // Use RelativeUriWithExtension to preserve the file extension that the + // router's URI parser strips (e.g. ".css", ".js") - the upstream server + // needs the full path including the extension. + std::string_view FullUri = Req.ServerRequest().RelativeUriWithExtension(); + std::string_view Prefix = "proxy/"; + + // FullUri is "proxy/{port}/{path...}" - skip past "proxy/{port}/" + size_t PathStart = Prefix.size() + PortStr.size() + 1; + std::string_view PathTail = (PathStart < FullUri.size()) ? FullUri.substr(PathStart) : std::string_view{}; + + m_Proxy.HandleProxyRequest(Req.ServerRequest(), PortStr, PathTail); }, - HttpVerb::kGet); + HttpVerb::kGet | HttpVerb::kPost | HttpVerb::kPut | HttpVerb::kDelete | HttpVerb::kHead); + + m_StatsService.RegisterHandler("hub", *this); + m_StatusService.RegisterHandler("hub", *this); } HttpHubService::~HttpHubService() { + m_StatusService.UnregisterHandler("hub", *this); + m_StatsService.UnregisterHandler("hub", *this); } const char* @@ -168,46 +294,158 @@ HttpHubService::SetNotificationEndpoint(std::string_view UpstreamNotificationEnd } void -HttpHubService::HandleRequest(zen::HttpServerRequest& Request) +HttpHubService::HandleRequest(HttpServerRequest& Request) +{ + using namespace std::literals; + + metrics::OperationTiming::Scope $(m_HttpRequests); + if (m_Router.HandleRequest(Request) == false) + { + ZEN_WARN("No route found for {0}", Request.RelativeUri()); + return Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, "Not found"sv); + } +} + +void +HttpHubService::HandleStatusRequest(HttpServerRequest& Request) { - m_Router.HandleRequest(Request); + CbObjectWriter Cbo; + Cbo << "ok" << true; + Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); +} + +void +HttpHubService::HandleStatsRequest(HttpServerRequest& Request) +{ + CbObjectWriter Cbo; + + EmitSnapshot("requests", m_HttpRequests, Cbo); + + Cbo << "currentInstanceCount" << m_Hub.GetInstanceCount(); + Cbo << "maxInstanceCount" << m_Hub.GetMaxInstanceCount(); + Cbo << "instanceLimit" << m_Hub.GetConfig().InstanceLimit; + + SystemMetrics SysMetrics; + DiskSpace Disk; + m_Hub.GetMachineMetrics(SysMetrics, Disk); + Cbo.BeginObject("machine"); + { + Cbo << "disk_free_bytes" << Disk.Free; + Cbo << "disk_total_bytes" << Disk.Total; + Cbo << "memory_avail_mib" << SysMetrics.AvailSystemMemoryMiB; + Cbo << "memory_total_mib" << SysMetrics.SystemMemoryMiB; + Cbo << "virtual_memory_avail_mib" << SysMetrics.AvailVirtualMemoryMiB; + Cbo << "virtual_memory_total_mib" << SysMetrics.VirtualMemoryMiB; + } + Cbo.EndObject(); + + const ResourceMetrics& Limits = m_Hub.GetConfig().ResourceLimits; + Cbo.BeginObject("resource_limits"); + { + Cbo << "disk_bytes" << Limits.DiskUsageBytes; + Cbo << "memory_bytes" << Limits.MemoryUsageBytes; + } + Cbo.EndObject(); + + Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); +} + +CbObject +HttpHubService::CollectStats() +{ + CbObjectWriter Cbo; + + EmitSnapshot("requests", m_HttpRequests, Cbo); + + Cbo << "currentInstanceCount" << m_Hub.GetInstanceCount(); + Cbo << "maxInstanceCount" << m_Hub.GetMaxInstanceCount(); + Cbo << "instanceLimit" << m_Hub.GetConfig().InstanceLimit; + + return Cbo.Save(); +} + +uint64_t +HttpHubService::GetActivityCounter() +{ + return m_HttpRequests.Count(); } void HttpHubService::HandleModuleGet(HttpServerRequest& Request, std::string_view ModuleId) { - StorageServerInstance* Instance = nullptr; - if (!m_Hub.Find(ModuleId, &Instance)) + Hub::InstanceInfo InstanceInfo; + if (!m_Hub.Find(ModuleId, &InstanceInfo)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } - // TODO: A separate http request for the modules/{moduleid}/deprovision endpoint can be called and deprovision the instance leaving us - // with a dangling pointer... - CbObjectWriter Obj; - Obj << "moduleId" << Instance->GetModuleId(); - Obj << "provisioned" << Instance->IsProvisioned(); + Obj << "moduleId" << ModuleId; + Obj << "state" << ToString(InstanceInfo.State); Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } void HttpHubService::HandleModuleDelete(HttpServerRequest& Request, std::string_view ModuleId) { - StorageServerInstance* Instance = nullptr; - if (!m_Hub.Find(ModuleId, &Instance)) + Hub::InstanceInfo InstanceInfo; + if (!m_Hub.Find(ModuleId, &InstanceInfo)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } - // TODO: deprovision and nuke all related storage + if (InstanceInfo.State == HubInstanceState::Provisioned || InstanceInfo.State == HubInstanceState::Hibernated || + InstanceInfo.State == HubInstanceState::Crashed) + { + try + { + Hub::Response Resp = m_Hub.Deprovision(std::string(ModuleId)); + + if (HandleFailureResults(Request, Resp)) + { + return; + } + + // TODO: nuke all related storage + + const HttpResponseCode HttpCode = + (Resp.ResponseCode == Hub::EResponseCode::Accepted) ? HttpResponseCode::Accepted : HttpResponseCode::OK; + CbObjectWriter Obj; + Obj << "moduleId" << ModuleId; + return Request.WriteResponse(HttpCode, Obj.Save()); + } + catch (const std::exception& Ex) + { + ZEN_ERROR("Exception while deprovisioning module '{}': {}", ModuleId, Ex.what()); + throw; + } + } + + // TODO: nuke all related storage CbObjectWriter Obj; - Obj << "moduleId" << Instance->GetModuleId(); - Obj << "provisioned" << Instance->IsProvisioned(); + Obj << "moduleId" << ModuleId; Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } +void +HttpHubService::OnWebSocketOpen(Ref<WebSocketConnection> Connection, std::string_view RelativeUri) +{ + m_Proxy.OnWebSocketOpen(std::move(Connection), RelativeUri); +} + +void +HttpHubService::OnWebSocketMessage(WebSocketConnection& Conn, const WebSocketMessage& Msg) +{ + m_Proxy.OnWebSocketMessage(Conn, Msg); +} + +void +HttpHubService::OnWebSocketClose(WebSocketConnection& Conn, uint16_t Code, std::string_view Reason) +{ + m_Proxy.OnWebSocketClose(Conn, Code, Reason); +} + } // namespace zen |