// Copyright Epic Games, Inc. All Rights Reserved. #include "httphubservice.h" #include "hub.h" #include "storageserverinstance.h" #include #include #include #include namespace zen { 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, HttpStatsService& StatsService, HttpStatusService& StatusService) : m_Hub(Hub) , m_StatsService(StatsService) , m_StatusService(StatusService) { using namespace std::literals; m_Router.AddMatcher("moduleid", [](std::string_view Str) -> bool { for (const auto C : Str) { if (std::isalnum(C) || C == '-') { // fine } else { // not fine return false; } } return true; }); m_Router.RegisterRoute( "status", [this](HttpRouterRequest& Req) { CbObjectWriter Obj; Obj.BeginArray("modules"); m_Hub.EnumerateModules([&Obj](std::string_view ModuleId, const Hub::InstanceInfo& Info) { Obj.BeginObject(); { 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(); Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Obj.Save()); }, HttpVerb::kGet); m_Router.RegisterRoute( "modules/{moduleid}", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); if (Req.ServerRequest().RequestVerb() == HttpVerb::kDelete) { HandleModuleDelete(Req.ServerRequest(), ModuleId); } else { HandleModuleGet(Req.ServerRequest(), ModuleId); } }, HttpVerb::kGet | HttpVerb::kDelete); m_Router.RegisterRoute( "modules/{moduleid}/provision", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); try { HubProvisionedInstanceInfo Info; Hub::Response Resp = m_Hub.Provision(ModuleId, Info); if (HandleFailureResults(Req.ServerRequest(), Resp)) { 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()); throw; } }, HttpVerb::kPost); m_Router.RegisterRoute( "modules/{moduleid}/deprovision", [this](HttpRouterRequest& Req) { std::string_view ModuleId = Req.GetCapture(1); try { Hub::Response Resp = m_Hub.Deprovision(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 deprovisioning module '{}': {}", ModuleId, Ex.what()); throw; } }, HttpVerb::kPost); 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; } 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_StatsService.RegisterHandler("hub", *this); m_StatusService.RegisterHandler("hub", *this); } HttpHubService::~HttpHubService() { m_StatusService.UnregisterHandler("hub", *this); m_StatsService.UnregisterHandler("hub", *this); } const char* HttpHubService::BaseUri() const { return "/hub/"; } void HttpHubService::SetNotificationEndpoint(std::string_view UpstreamNotificationEndpoint, std::string_view InstanceId) { ZEN_UNUSED(UpstreamNotificationEndpoint, InstanceId); // TODO: store these for use in notifications, on some interval/criteria which is currently TBD } void 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) { 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) { Hub::InstanceInfo InstanceInfo; if (!m_Hub.Find(ModuleId, &InstanceInfo)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } CbObjectWriter Obj; Obj << "moduleId" << ModuleId; Obj << "state" << ToString(InstanceInfo.State); Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } void HttpHubService::HandleModuleDelete(HttpServerRequest& Request, std::string_view ModuleId) { Hub::InstanceInfo InstanceInfo; if (!m_Hub.Find(ModuleId, &InstanceInfo)) { Request.WriteResponse(HttpResponseCode::NotFound); return; } 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" << ModuleId; Request.WriteResponse(HttpResponseCode::OK, Obj.Save()); } } // namespace zen