aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver-test/hub-tests.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-01-21 09:38:16 +0100
committerGitHub Enterprise <[email protected]>2026-01-21 09:38:16 +0100
commite8d162c293fbdf9a40a1369b60b80fa286aceb0f (patch)
tree0cdb2a913e5f4d6f2c151f36edba8fd5b2ca4f89 /src/zenserver-test/hub-tests.cpp
parentbuilds multipart upload (#722) (diff)
downloadzen-e8d162c293fbdf9a40a1369b60b80fa286aceb0f.tar.xz
zen-e8d162c293fbdf9a40a1369b60b80fa286aceb0f.zip
zen hub (#574)
Initial implementation of zenserver "hub" mode. This is an experimental feature. zenserver can be started in hub mode by specifying `hub` as the first argument to zenserver
Diffstat (limited to 'src/zenserver-test/hub-tests.cpp')
-rw-r--r--src/zenserver-test/hub-tests.cpp252
1 files changed, 252 insertions, 0 deletions
diff --git a/src/zenserver-test/hub-tests.cpp b/src/zenserver-test/hub-tests.cpp
new file mode 100644
index 000000000..42a5dcae4
--- /dev/null
+++ b/src/zenserver-test/hub-tests.cpp
@@ -0,0 +1,252 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#if ZEN_WITH_TESTS
+# include "zenserver-test.h"
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <zencore/workthreadpool.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/compress.h>
+# include <zencore/filesystem.h>
+# include <zencore/stream.h>
+# include <zencore/string.h>
+# include <zencore/fmtutils.h>
+# include <zencore/scopeguard.h>
+# include <zenhttp/packageformat.h>
+# include <zenremotestore/builds/buildstoragecache.h>
+# include <zenutil/workerpools.h>
+# include <zenutil/zenserverprocess.h>
+# include <zenhttp/httpclient.h>
+# include <zenutil/consul.h>
+
+namespace zen::tests::hub {
+
+using namespace std::literals;
+
+TEST_SUITE_BEGIN("hub.lifecycle");
+
+TEST_CASE("hub.lifecycle.basic")
+{
+ {
+ ZenServerInstance Instance(TestEnv, ZenServerInstance::ServerMode::kHubServer);
+
+ const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady();
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri() + "/hub/");
+
+ HttpClient::Response Result = Client.Get("status");
+ CHECK(Result);
+ }
+}
+
+TEST_CASE("hub.lifecycle.children")
+{
+ ZenServerInstance Instance(TestEnv, ZenServerInstance::ServerMode::kHubServer);
+
+ const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady();
+ REQUIRE(PortNumber != 0);
+
+ SUBCASE("spawn")
+ {
+ HttpClient Client(Instance.GetBaseUri() + "/hub/");
+
+ HttpClient::Response Result = Client.Get("status");
+ REQUIRE(Result);
+
+ {
+ Result = Client.Post("modules/abc/provision");
+ REQUIRE(Result);
+
+ CbObject AbcResult = Result.AsObject();
+ CHECK(AbcResult["moduleId"].AsString() == "abc"sv);
+ const uint16_t AbcPort = AbcResult["port"].AsUInt16(0);
+ CHECK_NE(AbcPort, 0);
+
+ // This should be a fresh instance with no contents
+
+ HttpClient AbcClient(fmt::format("http://localhost:{}", AbcPort));
+
+ Result = AbcClient.Get("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::NotFound);
+
+ Result = AbcClient.Put("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567",
+ IoBufferBuilder::MakeFromMemory(MakeMemoryView("abcdef"sv)));
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::Created);
+ }
+
+ {
+ Result = Client.Post("modules/def/provision");
+ REQUIRE(Result);
+
+ CbObject DefResult = Result.AsObject();
+ CHECK(DefResult["moduleId"].AsString() == "def"sv);
+ const uint16_t DefPort = DefResult["port"].AsUInt16(0);
+ REQUIRE_NE(DefPort, 0);
+
+ // This should be a fresh instance with no contents
+
+ HttpClient DefClient(fmt::format("http://localhost:{}", DefPort));
+
+ Result = DefClient.Get("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::NotFound);
+
+ Result = DefClient.Put("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567",
+ IoBufferBuilder::MakeFromMemory(MakeMemoryView("AbcDef"sv)));
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::Created);
+ }
+
+ // this should be rejected because of the invalid module id
+ Result = Client.Post("modules/!!!!!/provision");
+ CHECK(!Result);
+
+ Result = Client.Post("modules/ghi/provision");
+ REQUIRE(Result);
+
+ // Tear down instances
+
+ Result = Client.Post("modules/abc/deprovision");
+ REQUIRE(Result);
+
+ Result = Client.Post("modules/def/deprovision");
+ REQUIRE(Result);
+
+ Result = Client.Post("modules/ghi/deprovision");
+ REQUIRE(Result);
+
+ // re-provision to verify that (de)hydration preserved state
+ {
+ Result = Client.Post("modules/abc/provision");
+ REQUIRE(Result);
+
+ CbObject AbcResult = Result.AsObject();
+ CHECK(AbcResult["moduleId"].AsString() == "abc"sv);
+ const uint16_t AbcPort = AbcResult["port"].AsUInt16(0);
+ REQUIRE_NE(AbcPort, 0);
+
+ // This should contain the content from the previous run
+
+ HttpClient AbcClient(fmt::format("http://localhost:{}", AbcPort));
+
+ Result = AbcClient.Get("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::OK);
+
+ CHECK_EQ(Result.AsText(), "abcdef"sv);
+
+ Result = AbcClient.Put("/z$/ns1/b/1123456789abcdef0123456789abcdef01234567",
+ IoBufferBuilder::MakeFromMemory(MakeMemoryView("ghijklmnop"sv)));
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::Created);
+ }
+
+ {
+ Result = Client.Post("modules/def/provision");
+ REQUIRE(Result);
+
+ CbObject DefResult = Result.AsObject();
+ CHECK(DefResult["moduleId"].AsString() == "def"sv);
+ const uint16_t DefPort = DefResult["port"].AsUInt16(0);
+ REQUIRE_NE(DefPort, 0);
+
+ // This should contain the content from the previous run
+
+ HttpClient DefClient(fmt::format("http://localhost:{}", DefPort));
+
+ Result = DefClient.Get("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::OK);
+
+ CHECK_EQ(Result.AsText(), "AbcDef"sv);
+
+ Result = DefClient.Put("/z$/ns1/b/1123456789abcdef0123456789abcdef01234567",
+ IoBufferBuilder::MakeFromMemory(MakeMemoryView("GhijklmNop"sv)));
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::Created);
+ }
+
+ Result = Client.Post("modules/abc/deprovision");
+ REQUIRE(Result);
+
+ Result = Client.Post("modules/def/deprovision");
+ REQUIRE(Result);
+
+ // re-provision to verify that (de)hydration preserved state, including
+ // state which was generated after the very first dehydration
+ {
+ Result = Client.Post("modules/abc/provision");
+ REQUIRE(Result);
+
+ CbObject AbcResult = Result.AsObject();
+ CHECK(AbcResult["moduleId"].AsString() == "abc"sv);
+ const uint16_t AbcPort = AbcResult["port"].AsUInt16(0);
+ REQUIRE_NE(AbcPort, 0);
+
+ // This should contain the content from the previous two runs
+
+ HttpClient AbcClient(fmt::format("http://localhost:{}", AbcPort));
+
+ Result = AbcClient.Get("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::OK);
+
+ CHECK_EQ(Result.AsText(), "abcdef"sv);
+
+ Result = AbcClient.Get("/z$/ns1/b/1123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::OK);
+
+ CHECK_EQ(Result.AsText(), "ghijklmnop"sv);
+ }
+
+ {
+ Result = Client.Post("modules/def/provision");
+ REQUIRE(Result);
+
+ CbObject DefResult = Result.AsObject();
+ REQUIRE(DefResult["moduleId"].AsString() == "def"sv);
+ const uint16_t DefPort = DefResult["port"].AsUInt16(0);
+ REQUIRE_NE(DefPort, 0);
+
+ // This should contain the content from the previous two runs
+
+ HttpClient DefClient(fmt::format("http://localhost:{}", DefPort));
+
+ Result = DefClient.Get("/z$/ns1/b/0123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::OK);
+
+ CHECK_EQ(Result.AsText(), "AbcDef"sv);
+
+ Result = DefClient.Get("/z$/ns1/b/1123456789abcdef0123456789abcdef01234567");
+ CHECK_EQ(Result.StatusCode, HttpResponseCode::OK);
+
+ CHECK_EQ(Result.AsText(), "GhijklmNop"sv);
+ }
+
+ Result = Client.Post("modules/abc/deprovision");
+ REQUIRE(Result);
+
+ Result = Client.Post("modules/def/deprovision");
+ REQUIRE(Result);
+
+ // final sanity check that the hub is still responsive
+ Result = Client.Get("status");
+ CHECK(Result);
+ }
+}
+
+TEST_SUITE_END();
+
+TEST_CASE("hub.consul.lifecycle")
+{
+ zen::consul::ConsulProcess ConsulProc;
+ ConsulProc.SpawnConsulAgent();
+
+ zen::consul::ConsulClient Client("http://localhost:8500/");
+ Client.SetKeyValue("zen/hub/testkey", "testvalue");
+
+ std::string RetrievedValue = Client.GetKeyValue("zen/hub/testkey");
+ CHECK_EQ(RetrievedValue, "testvalue");
+
+ Client.DeleteKey("zen/hub/testkey");
+
+ ConsulProc.StopConsulAgent();
+}
+
+} // namespace zen::tests::hub
+#endif