From 39a5bb35be1eddd3b91606c93a4bfdcd68137027 Mon Sep 17 00:00:00 2001 From: Dimitris-Ilias Gkanatsios Date: Mon, 25 Nov 2024 16:39:56 -0800 Subject: [PATCH] adding GSDK metrics (#470) Co-authored-by: Dimitris Gkanatsios --- cmd/e2e/gameserverapi_test.go | 14 +++---- cmd/e2e/utilities_test.go | 3 +- cmd/gameserverapi/main_test.go | 16 ++++---- cmd/initcontainer/main_test.go | 4 +- cmd/nodeagent/main.go | 1 + cmd/nodeagent/nodeagentmanager.go | 24 ++++++++++++ cmd/nodeagent/nodeagentmanager_test.go | 38 +++++++++++++------ cmd/nodeagent/types.go | 8 ++++ .../controllers/allocation_api_server_test.go | 16 ++++---- 9 files changed, 86 insertions(+), 38 deletions(-) diff --git a/cmd/e2e/gameserverapi_test.go b/cmd/e2e/gameserverapi_test.go index a83ab1ec..488de8f4 100644 --- a/cmd/e2e/gameserverapi_test.go +++ b/cmd/e2e/gameserverapi_test.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io" "net" "net/http" "time" @@ -87,7 +87,7 @@ var _ = Describe("GameServerAPI tests", func() { g.Expect(r.StatusCode).To(Equal(http.StatusOK)) defer r.Body.Close() var l mpsv1alpha1.GameServerBuildList - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) g.Expect(err).ToNot(HaveOccurred()) err = json.Unmarshal(body, &l) g.Expect(err).ToNot(HaveOccurred()) @@ -108,7 +108,7 @@ var _ = Describe("GameServerAPI tests", func() { defer r.Body.Close() g.Expect(r.StatusCode).To(Equal(http.StatusOK)) var bu mpsv1alpha1.GameServerBuild - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) g.Expect(err).ToNot(HaveOccurred()) err = json.Unmarshal(body, &bu) g.Expect(err).ToNot(HaveOccurred()) @@ -121,7 +121,7 @@ var _ = Describe("GameServerAPI tests", func() { r, err := client.Get(fmt.Sprintf("%s/gameserverbuilds/%s/%s/gameservers", url, testNamespace, buildName)) g.Expect(err).ToNot(HaveOccurred()) defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) g.Expect(err).ToNot(HaveOccurred()) err = json.Unmarshal(body, &gsList) g.Expect(err).ToNot(HaveOccurred()) @@ -135,7 +135,7 @@ var _ = Describe("GameServerAPI tests", func() { g.Expect(err).ToNot(HaveOccurred()) defer r.Body.Close() var gs mpsv1alpha1.GameServer - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) g.Expect(err).ToNot(HaveOccurred()) err = json.Unmarshal(body, &gs) g.Expect(err).ToNot(HaveOccurred()) @@ -166,7 +166,7 @@ var _ = Describe("GameServerAPI tests", func() { Expect(err).ToNot(HaveOccurred()) defer r.Body.Close() var gsList mpsv1alpha1.GameServerList - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) Expect(err).ToNot(HaveOccurred()) err = json.Unmarshal(body, &gsList) Expect(err).ToNot(HaveOccurred()) @@ -196,7 +196,7 @@ var _ = Describe("GameServerAPI tests", func() { g.Expect(err).ToNot(HaveOccurred()) defer r.Body.Close() g.Expect(r.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) g.Expect(err).ToNot(HaveOccurred()) var bu mpsv1alpha1.GameServerBuild err = json.Unmarshal(body, &bu) diff --git a/cmd/e2e/utilities_test.go b/cmd/e2e/utilities_test.go index 28008f04..ee812a1b 100644 --- a/cmd/e2e/utilities_test.go +++ b/cmd/e2e/utilities_test.go @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "math/rand" "net/http" "strings" @@ -287,7 +286,7 @@ func allocate(buildID, sessionID string, cert tls.Certificate) error { return fmt.Errorf("%s %d", invalidStatusCode, resp.StatusCode) } //Read the response body - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return err } diff --git a/cmd/gameserverapi/main_test.go b/cmd/gameserverapi/main_test.go index 1d887155..5ce0e2b7 100644 --- a/cmd/gameserverapi/main_test.go +++ b/cmd/gameserverapi/main_test.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -53,7 +53,7 @@ var _ = Describe("GameServer API service tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var l mpsv1alpha1.GameServerBuildList err = json.Unmarshal(body, &l) @@ -77,7 +77,7 @@ var _ = Describe("GameServer API service tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var b mpsv1alpha1.GameServerBuild err = json.Unmarshal(body, &b) @@ -134,7 +134,7 @@ var _ = Describe("GameServer API service tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var b mpsv1alpha1.GameServerBuild err = json.Unmarshal(body, &b) @@ -149,7 +149,7 @@ var _ = Describe("GameServer API service tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var l mpsv1alpha1.GameServerList err = json.Unmarshal(body, &l) @@ -174,7 +174,7 @@ var _ = Describe("GameServer API service tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var g mpsv1alpha1.GameServer err = json.Unmarshal(body, &g) @@ -189,7 +189,7 @@ var _ = Describe("GameServer API service tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var l mpsv1alpha1.GameServerDetailList err = json.Unmarshal(body, &l) @@ -214,7 +214,7 @@ var _ = Describe("GameServer API service tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var g mpsv1alpha1.GameServerDetail err = json.Unmarshal(body, &g) diff --git a/cmd/initcontainer/main_test.go b/cmd/initcontainer/main_test.go index 4247d46e..01cbbb58 100644 --- a/cmd/initcontainer/main_test.go +++ b/cmd/initcontainer/main_test.go @@ -3,7 +3,7 @@ package main import ( "encoding/json" "fmt" - "io/ioutil" + "io" "log" "os" "testing" @@ -50,7 +50,7 @@ func (suite *initContainerTestSuite) TestInitContainer() { jsonFile, err := os.Open(testGsdkConfigFile) assert.NoError(suite.T(), err) defer jsonFile.Close() - byteValue, err := ioutil.ReadAll(jsonFile) + byteValue, err := io.ReadAll(jsonFile) assert.NoError(suite.T(), err) var gsdkConfig *GsdkConfig err = json.Unmarshal(byteValue, &gsdkConfig) diff --git a/cmd/nodeagent/main.go b/cmd/nodeagent/main.go index 7587c396..65812ddc 100644 --- a/cmd/nodeagent/main.go +++ b/cmd/nodeagent/main.go @@ -31,6 +31,7 @@ func main() { n := NewNodeAgentManager(dynamicClient, nodeName, logEveryHeartbeat, ignoreHealthFromHeartbeat, time.Now, true) log.Debug("Starting HTTP server") http.HandleFunc("/v1/sessionHosts/", n.heartbeatHandler) + http.HandleFunc("/v1/metrics/", n.metricsHandler) http.HandleFunc("/healthz", healthzHandler) http.Handle("/metrics", promhttp.Handler()) diff --git a/cmd/nodeagent/nodeagentmanager.go b/cmd/nodeagent/nodeagentmanager.go index d5922566..882e3fe5 100644 --- a/cmd/nodeagent/nodeagentmanager.go +++ b/cmd/nodeagent/nodeagentmanager.go @@ -315,6 +315,30 @@ func (n *NodeAgentManager) gameServerDeleted(objUnstructured interface{}) { n.gameServerMap.Delete(gameServerName) } +// we want to log the GSDK version/flavor once, this variable is used to track that +// it's OK if we log it multiple times, but we want to avoid spamming the logs +var gsdkMetricsLogged = false + +// this endpoint is used to receive GSDK info from the gameserver +// v1/metrics/{sessionHostId}/gsdkinfo +func (n *NodeAgentManager) metricsHandler(w http.ResponseWriter, r *http.Request) { + re := regexp.MustCompile(`.*/v1/metrics\/(.*?)(/gsdkinfo|$)`) + match := re.FindStringSubmatch(r.RequestURI) + gameServerName := match[1] + var gi GsdkVersionInfo + err := json.NewDecoder(r.Body).Decode(&gi) + if err != nil { + badRequest(w, err, "cannot deserialize json") + return + } + if !gsdkMetricsLogged { + log.Infof("GSDK metrics received from gameserver %s, GSDK flavor and version: %s-%s", gameServerName, gi.Flavor, gi.Version) + gsdkMetricsLogged = true + } + + w.WriteHeader(http.StatusOK) +} + // heartbeatHandler is the http handler handling heartbeats from the GameServer Pods running on this Node // it responds by sending instructions/signal for the next operation // on Thundernetes, the only operation that NodeAgent can signal to the GameServer is that the GameServer has been allocated (its state has transitioned to Active) diff --git a/cmd/nodeagent/nodeagentmanager_test.go b/cmd/nodeagent/nodeagentmanager_test.go index 2d2438bb..a5b9e3db 100644 --- a/cmd/nodeagent/nodeagentmanager_test.go +++ b/cmd/nodeagent/nodeagentmanager_test.go @@ -5,7 +5,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "math/rand" "net/http" "net/http/httptest" @@ -46,7 +46,7 @@ var _ = Describe("nodeagent tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusBadRequest)) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) }) It("heartbeat with empty fields should return error", func() { @@ -59,7 +59,7 @@ var _ = Describe("nodeagent tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusBadRequest)) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) }) It("heartbeat with body should work", func() { @@ -90,7 +90,7 @@ var _ = Describe("nodeagent tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err := ioutil.ReadAll(res.Body) + resBody, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr := HeartbeatResponse{} _ = json.Unmarshal(resBody, &hbr) @@ -149,7 +149,7 @@ var _ = Describe("nodeagent tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err := ioutil.ReadAll(res.Body) + resBody, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr := HeartbeatResponse{} _ = json.Unmarshal(resBody, &hbr) @@ -167,7 +167,7 @@ var _ = Describe("nodeagent tests", func() { res = w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err = ioutil.ReadAll(res.Body) + resBody, err = io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr = HeartbeatResponse{} err = json.Unmarshal(resBody, &hbr) @@ -204,7 +204,7 @@ var _ = Describe("nodeagent tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err := ioutil.ReadAll(res.Body) + resBody, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr := HeartbeatResponse{} _ = json.Unmarshal(resBody, &hbr) @@ -400,7 +400,7 @@ var _ = Describe("nodeagent tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err := ioutil.ReadAll(res.Body) + resBody, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr := HeartbeatResponse{} _ = json.Unmarshal(resBody, &hbr) @@ -435,7 +435,7 @@ var _ = Describe("nodeagent tests", func() { res = w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err = ioutil.ReadAll(res.Body) + resBody, err = io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr = HeartbeatResponse{} err = json.Unmarshal(resBody, &hbr) @@ -519,7 +519,7 @@ var _ = Describe("nodeagent tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err := ioutil.ReadAll(res.Body) + resBody, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr := HeartbeatResponse{} _ = json.Unmarshal(resBody, &hbr) @@ -537,7 +537,7 @@ var _ = Describe("nodeagent tests", func() { res = w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - resBody, err = ioutil.ReadAll(res.Body) + resBody, err = io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) hbr = HeartbeatResponse{} err = json.Unmarshal(resBody, &hbr) @@ -836,6 +836,22 @@ var _ = Describe("nodeagent tests", func() { g.Expect(gameServerHealth).To(Equal("Healthy")) }, "3s").Should(Succeed()) }) + It("GSDK metrics should work", func() { + gi := &GsdkVersionInfo{ + Version: "1.0.0", + Flavor: "CustomGameEngine", + } + b, _ := json.Marshal(gi) + req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/v1/metrics/%s/gsdkinfo", testGameServerName), bytes.NewReader(b)) + w := httptest.NewRecorder() + dynamicClient := newDynamicInterface() + + n := NewNodeAgentManager(dynamicClient, testNodeName, false, false, time.Now, true) + n.metricsHandler(w, req) + res := w.Result() + defer res.Body.Close() + Expect(res.StatusCode).To(Equal(http.StatusOK)) + }) }) var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") diff --git a/cmd/nodeagent/types.go b/cmd/nodeagent/types.go index d09592c4..aac160bb 100644 --- a/cmd/nodeagent/types.go +++ b/cmd/nodeagent/types.go @@ -78,6 +78,14 @@ var ( ) ) +// GsdkVersionInfo contains details about the GSDK version of the game server +type GsdkVersionInfo struct { + // Flavor is the engine of GSDK (Unreal/Unity/C++ etc.) + Flavor string `json:"Flavor"` + // Version is the version of GSDK + Version string `json:"Version"` +} + // HeartbeatRequest contains data for the heartbeat request coming from the GSDK running alongside GameServer type HeartbeatRequest struct { // CurrentGameState is the current state of the game server diff --git a/pkg/operator/controllers/allocation_api_server_test.go b/pkg/operator/controllers/allocation_api_server_test.go index 6ad4e9fc..47483fac 100644 --- a/pkg/operator/controllers/allocation_api_server_test.go +++ b/pkg/operator/controllers/allocation_api_server_test.go @@ -5,7 +5,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" @@ -32,7 +32,7 @@ var _ = Describe("allocation API service input validation tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusBadRequest)) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) }) It("GET method should return error", func() { @@ -43,7 +43,7 @@ var _ = Describe("allocation API service input validation tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusBadRequest)) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) }) It("bad body should return error", func() { @@ -54,7 +54,7 @@ var _ = Describe("allocation API service input validation tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusBadRequest)) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) }) It("buildID should be a GUID", func() { @@ -65,7 +65,7 @@ var _ = Describe("allocation API service input validation tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusBadRequest)) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) }) It("should return NotFound on an empty list", func() { @@ -76,7 +76,7 @@ var _ = Describe("allocation API service input validation tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusNotFound)) - _, err := ioutil.ReadAll(res.Body) + _, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) }) It("should return existing game server when given an existing sessionID", func() { @@ -90,7 +90,7 @@ var _ = Describe("allocation API service input validation tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var rm = RequestMultiplayerServerResponse{} err = json.Unmarshal(body, &rm) @@ -117,7 +117,7 @@ var _ = Describe("allocation API service input validation tests", func() { res := w.Result() defer res.Body.Close() Expect(res.StatusCode).To(Equal(http.StatusOK)) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) Expect(err).ToNot(HaveOccurred()) var rm = RequestMultiplayerServerResponse{} err = json.Unmarshal(body, &rm)