mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-19 09:34:12 +00:00
(experimental feature) Before a worker can start, it should have a configuration file. In case if such a file is not found it may request configuration from the coordinator to which it connected. Added example logic if a worker is needed to be blocked until a successful packet exchange with a coordinator is being made. * Add error return for config loader * Add config loaded flag to worker * Add zone flag * Add a custom mutex lock with timout * Refactor worker runtime * Refactor internal api * Extract monitoring server config * Extract worker HTTP(S) server * Add generic sub-server interface * Add internal coordinator API * Add internal routes and handlers to worker * Add internal worker API * Refactor worker run * Migrate serverId call to new API * Add packet handler to cws * Extract handlers for internal worker routes in coordinator * Pass worker to the worker internal heandlers * Cleanup worker handlers in coordinator * Add closeRoom packet handler to the API * Add GetRoom packet handler to the API * Add RegisterRoom packet handler to the API * Add IceCandidate packet handler to the API (internal and browser) * Add Heartbeat packet handler to the API (internal and browser) * Rename worker routes init function * Extract worker/coordinator internal ws handlers * Update timed locker * Allow sequential timed locks * Add config request from workers * Add nil check for the route registration functions
516 lines
14 KiB
Go
516 lines
14 KiB
Go
package e2e
|
|
|
|
// import (
|
|
// "fmt"
|
|
// "log"
|
|
// "net/http"
|
|
// "net/http/httptest"
|
|
// "os"
|
|
// "strings"
|
|
// "testing"
|
|
// "time"
|
|
//
|
|
// "github.com/giongto35/cloud-game/pkg/cws"
|
|
// "github.com/giongto35/cloud-game/pkg/overlord"
|
|
// "github.com/giongto35/cloud-game/pkg/util"
|
|
// gamertc "github.com/giongto35/cloud-game/pkg/webrtc"
|
|
// "github.com/giongto35/cloud-game/pkg/worker"
|
|
// "github.com/gorilla/websocket"
|
|
// "github.com/pion/webrtc/v2"
|
|
// )
|
|
//
|
|
// var host = "http://localhost:8000"
|
|
//
|
|
// // Test is in cmd, so gamePath is in parent path
|
|
// var testGamePath = "../games"
|
|
// var webrtcconfig = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{
|
|
// URLs: []string{"stun:stun.l.google.com:19302"},
|
|
// }}}
|
|
//
|
|
// func initCoordinator() (*httptest.Server, *httptest.Server) {
|
|
// server := overlord.NewServer()
|
|
// overlordWorker := httptest.NewServer(http.HandlerFunc(server.WSO))
|
|
// overlordBrowser := httptest.NewServer(http.HandlerFunc(server.WS))
|
|
// return overlordWorker, overlordBrowser
|
|
// }
|
|
//
|
|
// func initWorker(t *testing.T, overlordURL string) *worker.Handler {
|
|
// fmt.Println("Spawn new worker")
|
|
// if overlordURL == "" {
|
|
// return nil
|
|
// } else {
|
|
// overlordURL = "ws" + strings.TrimPrefix(overlordURL, "http")
|
|
// fmt.Println("connecting to overlord: ", overlordURL)
|
|
// }
|
|
//
|
|
// handler := worker.NewHandler(overlordURL, testGamePath)
|
|
//
|
|
// go handler.Run()
|
|
// time.Sleep(time.Second)
|
|
// return handler
|
|
// }
|
|
//
|
|
// func initClient(t *testing.T, host string) (client *cws.Client) {
|
|
// // Convert http://127.0.0.1 to ws://127.0.0.
|
|
// u := "ws" + strings.TrimPrefix(host, "http")
|
|
//
|
|
// fmt.Println("Connecting to", u)
|
|
// ws, _, err := websocket.DefaultDialer.Dial(u, nil)
|
|
// if err != nil {
|
|
// t.Fatalf("%v", err)
|
|
// }
|
|
//
|
|
// handshakedone := make(chan struct{})
|
|
// // Simulate peerconnection initialization from client
|
|
// fmt.Println("Simulating PeerConnection")
|
|
// peerConnection, err := webrtc.NewPeerConnection(webrtcconfig)
|
|
// if err != nil {
|
|
// t.Fatalf("%v", err)
|
|
// }
|
|
//
|
|
// offer, err := peerConnection.CreateOffer(nil)
|
|
// if err != nil {
|
|
// t.Fatalf("%v", err)
|
|
// }
|
|
//
|
|
// // Sets the LocalDescription, and starts our UDP listeners
|
|
// err = peerConnection.SetLocalDescription(offer)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
// // Send offer to overlord
|
|
// log.Println("Browser Client")
|
|
// client = cws.NewClient(ws)
|
|
// go client.Listen()
|
|
//
|
|
// fmt.Println("Sending offer...")
|
|
// client.Send(cws.WSPacket{
|
|
// ID: "init_webrtc",
|
|
// Data: gamertc.Encode(offer),
|
|
// }, nil)
|
|
// fmt.Println("Waiting sdp...")
|
|
//
|
|
// client.Receive("sdp", func(resp cws.WSPacket) cws.WSPacket {
|
|
// log.Println("Received SDP", resp.Data, "client: ", client)
|
|
// answer := webrtc.SessionDescription{}
|
|
// gamertc.Decode(resp.Data, &answer)
|
|
// // Apply the answer as the remote description
|
|
// err = peerConnection.SetRemoteDescription(answer)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
//
|
|
// // TODO: may block in the second call
|
|
// handshakedone <- struct{}{}
|
|
//
|
|
// return cws.EmptyPacket
|
|
// })
|
|
//
|
|
// // Request Offer routing
|
|
// client.Receive("requestOffer", func(resp cws.WSPacket) cws.WSPacket {
|
|
// log.Println("Frontend received requestOffer")
|
|
// peerConnection, err = webrtc.NewPeerConnection(webrtcconfig)
|
|
// if err != nil {
|
|
// t.Fatalf("%v", err)
|
|
// }
|
|
//
|
|
// log.Println("Recreating offer")
|
|
// offer, err := peerConnection.CreateOffer(nil)
|
|
// if err != nil {
|
|
// t.Fatalf("%v", err)
|
|
// }
|
|
//
|
|
// log.Println("Set localDesc")
|
|
// err = peerConnection.SetLocalDescription(offer)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
// log.Println("return offer")
|
|
// return cws.WSPacket{
|
|
// ID: "init_webrtc",
|
|
// Data: gamertc.Encode(offer),
|
|
// }
|
|
// })
|
|
//
|
|
// <-handshakedone
|
|
// return client
|
|
// // If receive roomID, the server is running correctly
|
|
// }
|
|
//
|
|
// func TestSingleServerOneCoordinator(t *testing.T) {
|
|
// /*
|
|
// Case scenario:
|
|
// - A server X are initilized
|
|
// - Client join room with coordinator
|
|
// Expected behavior:
|
|
// - Room received not empty.
|
|
// */
|
|
//
|
|
// oworker, obrowser := initCoordinator()
|
|
// defer obrowser.Close()
|
|
// defer oworker.Close()
|
|
//
|
|
// // Init worker
|
|
// worker := initWorker(t, oworker.URL)
|
|
// defer worker.Close()
|
|
//
|
|
// // connect overlord
|
|
// client := initClient(t, obrowser.URL)
|
|
// defer client.Close()
|
|
//
|
|
// fmt.Println("Sending start...")
|
|
// roomID := make(chan string)
|
|
// client.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: "",
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// roomID <- resp.RoomID
|
|
// })
|
|
//
|
|
// respRoomID := <-roomID
|
|
// if respRoomID == "" {
|
|
// fmt.Println("RoomID should not be empty")
|
|
// t.Fail()
|
|
// }
|
|
// time.Sleep(time.Second)
|
|
// fmt.Println("Done")
|
|
// }
|
|
//
|
|
// func TestTwoServerOneCoordinator(t *testing.T) {
|
|
// /*
|
|
// Case scenario:
|
|
// - Two server X, Y are initilized
|
|
// - Client A creates a room on server X
|
|
// - Client B creates a room on server Y
|
|
// - Client B join a room created by A
|
|
// Expected behavior:
|
|
// - Bridge connection will be conducted between server Y and X
|
|
// - Client B can join a room hosted on A
|
|
// */
|
|
//
|
|
// oworker, obrowser := initCoordinator()
|
|
// defer obrowser.Close()
|
|
// defer oworker.Close()
|
|
//
|
|
// worker1 := initWorker(t, oworker.URL)
|
|
// defer worker1.Close()
|
|
//
|
|
// worker2 := initWorker(t, oworker.URL)
|
|
// defer worker2.Close()
|
|
//
|
|
// client1 := initClient(t, obrowser.URL)
|
|
// defer client1.Close()
|
|
//
|
|
// roomID := make(chan string)
|
|
// client1.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: "",
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// fmt.Println("RoomID:", resp.RoomID)
|
|
// roomID <- resp.RoomID
|
|
// })
|
|
//
|
|
// remoteRoomID := <-roomID
|
|
// if remoteRoomID == "" {
|
|
// fmt.Println("RoomID should not be empty")
|
|
// t.Fail()
|
|
// }
|
|
// fmt.Println("Done create a room in server 1")
|
|
//
|
|
// // ------------------------------------
|
|
// // Client2 trying to create a random room and later join the the room on server1
|
|
// client2 := initClient(t, obrowser.URL)
|
|
// defer client2.Close()
|
|
// // Wait
|
|
// // Doing the same create local room.
|
|
// localRoomID := make(chan string)
|
|
// client2.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: "",
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// fmt.Println("RoomID:", resp.RoomID)
|
|
// localRoomID <- resp.RoomID
|
|
// })
|
|
//
|
|
// <-localRoomID
|
|
//
|
|
// fmt.Println("Request the room from server 1", remoteRoomID)
|
|
// log.Println("Server2 trying to join server1 room")
|
|
// // After trying loging in to one session, login to other with the roomID
|
|
// bridgeRoom := make(chan string)
|
|
// client2.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: remoteRoomID,
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// fmt.Println("RoomID:", resp.RoomID)
|
|
// bridgeRoom <- resp.RoomID
|
|
// })
|
|
//
|
|
// <-bridgeRoom
|
|
// //respRoomID := <-bridgeRoom
|
|
// //if respRoomID == "" {
|
|
// //fmt.Println("The room ID should be equal to the saved room")
|
|
// //t.Fail()
|
|
// //}
|
|
// // If receive roomID, the server is running correctly
|
|
// time.Sleep(time.Second)
|
|
// fmt.Println("Done")
|
|
// }
|
|
//
|
|
// func TestReconnectRoom(t *testing.T) {
|
|
// /*
|
|
// Case scenario:
|
|
// - A server X is initialized connecting to overlord
|
|
// - Client A creates a room K on server X
|
|
// - Server X is turned down, Client is closed
|
|
// - Spawn a new server and a new client connecting to the same room K
|
|
// Expected behavior:
|
|
// - The game should be continue
|
|
// TODO: Current test just make sure the game is running, not check if the game is the same
|
|
// */
|
|
//
|
|
// oworker, obrowser := initCoordinator()
|
|
// defer obrowser.Close()
|
|
// defer oworker.Close()
|
|
//
|
|
// // Init worker
|
|
// worker := initWorker(t, oworker.URL)
|
|
//
|
|
// client := initClient(t, obrowser.URL)
|
|
//
|
|
// fmt.Println("Sending start...")
|
|
// roomID := make(chan string)
|
|
// client.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: "",
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// fmt.Println("RoomID:", resp.RoomID)
|
|
// roomID <- resp.RoomID
|
|
// })
|
|
//
|
|
// saveRoomID := <-roomID
|
|
// if saveRoomID == "" {
|
|
// fmt.Println("RoomID should not be empty")
|
|
// t.Fail()
|
|
// }
|
|
//
|
|
// log.Println("Closing room and server")
|
|
// client.Close()
|
|
// worker.GetCoordinatorClient().Close()
|
|
// worker.Close()
|
|
//
|
|
// // Close server and reconnect
|
|
//
|
|
// log.Println("Server respawn")
|
|
// // Init slave server again
|
|
// worker = initWorker(t, oworker.URL)
|
|
// defer worker.Close()
|
|
//
|
|
// client = initClient(t, obrowser.URL)
|
|
// defer client.Close()
|
|
//
|
|
// fmt.Println("Re-access room ", saveRoomID)
|
|
// roomID = make(chan string)
|
|
// client.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: saveRoomID,
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// fmt.Println("RoomID:", resp.RoomID)
|
|
// roomID <- resp.RoomID
|
|
// })
|
|
//
|
|
// respRoomID := <-roomID
|
|
// if respRoomID == "" || respRoomID != saveRoomID {
|
|
// fmt.Println("The room ID should be equal to the saved room")
|
|
// t.Fail()
|
|
// }
|
|
//
|
|
// time.Sleep(time.Second)
|
|
// fmt.Println("Done")
|
|
// }
|
|
//
|
|
// func TestReconnectRoomNoLocal(t *testing.T) {
|
|
// /*
|
|
// Case scenario:
|
|
// - A server X is initialized connecting to overlord
|
|
// - Client A creates a room K on server X
|
|
// - Server X is turned down, Client is closed
|
|
// - room on local is deleted
|
|
// - Spawn a new server and a new client connecting to the same room K
|
|
// Expected behavior:
|
|
// - room on local storage is refetched from cloud storage
|
|
// - The game should be continue where it is closed
|
|
// TODO: Current test just make sure the game is running, not check if the game is the same
|
|
// */
|
|
// // This test only run if GCP storage is set
|
|
//
|
|
// oworker, obrowser := initCoordinator()
|
|
// defer obrowser.Close()
|
|
// defer oworker.Close()
|
|
//
|
|
// // Init worker
|
|
// ggCredential := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
|
// if ggCredential == "" {
|
|
// return
|
|
// }
|
|
//
|
|
// worker := initWorker(t, oworker.URL)
|
|
//
|
|
// client := initClient(t, obrowser.URL)
|
|
//
|
|
// fmt.Println("Sending start...")
|
|
// roomID := make(chan string)
|
|
// client.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: "",
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// fmt.Println("RoomID:", resp.RoomID)
|
|
// roomID <- resp.RoomID
|
|
// })
|
|
//
|
|
// saveRoomID := <-roomID
|
|
// if saveRoomID == "" {
|
|
// fmt.Println("RoomID should not be empty")
|
|
// t.Fail()
|
|
// }
|
|
//
|
|
// log.Println("Closing room and server")
|
|
// client.Close()
|
|
// worker.GetCoordinatorClient().Close()
|
|
// worker.Close()
|
|
// // Remove room on local
|
|
// path := util.GetSavePath(saveRoomID)
|
|
// os.Remove(path)
|
|
// // Expect Google cloud call
|
|
//
|
|
// // Close server and reconnect
|
|
//
|
|
// log.Println("Server respawn")
|
|
// // Init slave server again
|
|
// worker = initWorker(t, oworker.URL)
|
|
// defer worker.Close()
|
|
//
|
|
// client = initClient(t, obrowser.URL)
|
|
// defer client.Close()
|
|
//
|
|
// fmt.Println("Re-access room ", saveRoomID)
|
|
// roomID = make(chan string)
|
|
// client.Send(cws.WSPacket{
|
|
// ID: "start",
|
|
// Data: "Contra.nes",
|
|
// RoomID: saveRoomID,
|
|
// PlayerIndex: 1,
|
|
// }, func(resp cws.WSPacket) {
|
|
// fmt.Println("RoomID:", resp.RoomID)
|
|
// roomID <- resp.RoomID
|
|
// })
|
|
//
|
|
// respRoomID := <-roomID
|
|
// if respRoomID == "" || respRoomID != saveRoomID {
|
|
// fmt.Println("The room ID should be equal to the saved room")
|
|
// t.Fail()
|
|
// }
|
|
//
|
|
// time.Sleep(time.Second)
|
|
// fmt.Println("Done")
|
|
// }
|
|
|
|
//func TestRejoinNoCoordinatorMultiple(t *testing.T) {
|
|
//[>
|
|
//Case scenario:
|
|
//- A server X without connecting to overlord
|
|
//- Client A keeps creating a new room
|
|
//Expected behavior:
|
|
//- The game should running normally
|
|
//*/
|
|
|
|
//// Init slave server
|
|
//s := initServer(t, nil)
|
|
//defer s.Close()
|
|
|
|
//fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine())
|
|
//client := initClient(t, s.URL)
|
|
//for i := 0; i < 100; i++ {
|
|
//fmt.Println("Sending start...")
|
|
//// Keep starting game
|
|
//roomID := make(chan string)
|
|
//client.Send(cws.WSPacket{
|
|
//ID: "start",
|
|
//Data: "Contra.nes",
|
|
//RoomID: "",
|
|
//PlayerIndex: 1,
|
|
//}, func(resp cws.WSPacket) {
|
|
//fmt.Println("RoomID:", resp.RoomID)
|
|
//roomID <- resp.RoomID
|
|
//})
|
|
|
|
//respRoomID := <-roomID
|
|
//if respRoomID == "" {
|
|
//fmt.Println("The room ID should be equal to the saved room")
|
|
//t.Fail()
|
|
//}
|
|
//}
|
|
//time.Sleep(time.Second)
|
|
//fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine())
|
|
//fmt.Println("Done")
|
|
|
|
//}
|
|
|
|
//func TestRejoinWithCoordinatorMultiple(t *testing.T) {
|
|
//[>
|
|
//Case scenario:
|
|
//- A server X is initialized connecting to overlord
|
|
//- Client A keeps creating a new room
|
|
//Expected behavior:
|
|
//- The game should running normally
|
|
//*/
|
|
|
|
//// Init slave server
|
|
//o := initCoordinator()
|
|
//defer o.Close()
|
|
|
|
//oconn := connectTestCoordinatorServer(t, o.URL)
|
|
//// Init slave server
|
|
//s := initServer(t, oconn)
|
|
//defer s.Close()
|
|
|
|
//fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine())
|
|
//client := initClient(t, s.URL)
|
|
//for i := 0; i < 100; i++ {
|
|
//fmt.Println("Sending start...")
|
|
//// Keep starting game
|
|
//roomID := make(chan string)
|
|
//client.Send(cws.WSPacket{
|
|
//ID: "start",
|
|
//Data: "Contra.nes",
|
|
//RoomID: "",
|
|
//PlayerIndex: 1,
|
|
//}, func(resp cws.WSPacket) {
|
|
//fmt.Println("RoomID:", resp.RoomID)
|
|
//roomID <- resp.RoomID
|
|
//})
|
|
|
|
//respRoomID := <-roomID
|
|
//if respRoomID == "" {
|
|
//fmt.Println("The room ID should be equal to the saved room")
|
|
//t.Fail()
|
|
//}
|
|
//}
|
|
//fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine())
|
|
//fmt.Println("Done")
|
|
|
|
//}
|