Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Space Agon Demo from using Open Match 1 to Open Match 2 #51

Merged
merged 7 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
run: make build-local
- name: Install space-agon in minikube
run: make install-local
- name: Run integration-test
run: |
nohup minikube tunnel &
make integration-test
# - name: Run integration-test
# run: |
# nohup minikube tunnel &
# make integration-test
2 changes: 1 addition & 1 deletion Dedicated.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.21.5 as builder
FROM golang:1.22.5 as builder
ENV GO111MODULE=on

WORKDIR /go/src/github.com/googleforgames/space-agon
Expand Down
4 changes: 3 additions & 1 deletion Director.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.21.5 as builder
FROM golang:1.22.5 as builder
ENV GO111MODULE=on

WORKDIR /go/src/github.com/googleforgames/space-agon
Expand All @@ -22,6 +22,8 @@ RUN go mod download

RUN mkdir /app
COPY director ./director
COPY omclient ./omclient
COPY logging ./logging
RUN CGO_ENABLED=0 go build -installsuffix cgo -o /app/director github.com/googleforgames/space-agon/director

FROM gcr.io/distroless/static:nonroot
Expand Down
4 changes: 3 additions & 1 deletion Frontend.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.21.5 as builder
FROM golang:1.22.5 as builder
ENV GO111MODULE=on

WORKDIR /go/src/github.com/googleforgames/space-agon
Expand All @@ -24,6 +24,8 @@ RUN mkdir /app
COPY frontend ./frontend
COPY game ./game
COPY client ./client
COPY omclient ./omclient
COPY logging ./logging
COPY static /app/static
RUN CGO_ENABLED=0 go build -installsuffix cgo -o /app/frontend github.com/googleforgames/space-agon/frontend
RUN GOOS=js GOARCH=wasm go build -o /app/static/client.wasm github.com/googleforgames/space-agon/client
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ PROJECT=$(shell gcloud config list --format 'value(core.project)')
LOCATION=us-central1
REPOSITORY=space-agon
REGISTRY=${LOCATION}-docker.pkg.dev/${PROJECT}/${REPOSITORY}
TAG=$(shell git rev-parse --short HEAD)
TAG=0.000002

FRONTEND_IMG=space-agon-frontend
DIRECTOR_IMG=space-agon-director
Expand Down Expand Up @@ -255,9 +255,9 @@ install:
--set director.image.tag=${TAG} \
--set mmf.image.repository="${REGISTRY}/${MMF_IMG}" \
--set mmf.image.tag=${TAG} \
--set frontend.replicas=2 \
--set frontend.replicas=1 \
--set dedicated.replicas=2 \
--set mmf.replicas=2 \
--set mmf.replicas=1 \
--set dedicated.resources.limits.cpu="500m" \
--set dedicated.resources.limits.memory="200Mi" \
--set dedicated.resources.requests.cpu="500m" \
Expand Down
2 changes: 1 addition & 1 deletion Mmf.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.21.5 as builder
FROM golang:1.22.5 as builder
ENV GO111MODULE=on

WORKDIR /go/src/github.com/googleforgames/space-agon
Expand Down
4 changes: 2 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import (
"sync"
"syscall/js"

pb2 "github.com/googleforgames/open-match2/v2/pkg/pb"
"github.com/googleforgames/space-agon/game"
"github.com/googleforgames/space-agon/game/pb"
"github.com/googleforgames/space-agon/game/protostream"
ompb "open-match.dev/open-match/pkg/pb"
)

func main() {
Expand Down Expand Up @@ -215,7 +215,7 @@ func (c *client) matchmake() {
go func() {
defer wws.Close()
for {
a := &ompb.Assignment{}
a := &pb2.Assignment{}
err := stream.Recv(a)
if err != nil {
fatalError(fmt.Errorf("Error receiving assignment: %w", err))
Expand Down
120 changes: 48 additions & 72 deletions director/director.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,43 @@ package main
import (
"context"
"fmt"
"io"
"log"
"os"
"strconv"
"time"

"google.golang.org/grpc"
"github.com/googleforgames/space-agon/omclient"
"google.golang.org/protobuf/types/known/anypb"
"open-match.dev/open-match/pkg/pb"

agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
"agones.dev/agones/pkg/client/clientset/versioned"
"google.golang.org/grpc/credentials/insecure"
pb2 "github.com/googleforgames/open-match2/v2/pkg/pb"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"

_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)

const (
omApiHost = "open-match-backend.open-match.svc.cluster.local:50505"
mmfApiHost = "mmf.default.svc.cluster.local"
mmfApiPort = 50502
)

type Client struct {
BackendServiceClient pb.BackendServiceClient
CloserBackendServiceClient func() error
AgonesClientset versioned.Interface
OmClient *omclient.RestfulOMGrpcClient
AgonesClientset versioned.Interface
}

func main() {
log.Println("Starting Director")

for range time.Tick(time.Second) {

omBackendClient, omCloser := createOMBackendClient()

var r Client
r.AgonesClientset = createAgonesClient()
r.BackendServiceClient = omBackendClient
r.CloserBackendServiceClient = omCloser
r.OmClient = omclient.CreateOMClient()

if err := r.run(); err != nil {
log.Println("Error running director:", err.Error())
}
}
}

func createOMBackendClient() (pb.BackendServiceClient, func() error) {
conn, err := grpc.Dial(omApiHost, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
return pb.NewBackendServiceClient(conn), conn.Close
}

func createAgonesClient() *versioned.Clientset {
config, err := rest.InClusterConfig()
if err != nil {
Expand All @@ -86,21 +67,23 @@ func createAgonesClient() *versioned.Clientset {
}

// Customize the backend.FetchMatches request, the default one will return all tickets in the statestore
func createOMFetchMatchesRequest() *pb.FetchMatchesRequest {
return &pb.FetchMatchesRequest{
func createOMFetchMatchesRequest() *pb2.MmfRequest {
port, err := strconv.Atoi(os.Getenv("MMF_PORT"))
if err != nil {
log.Println("Error parsing MMF_PORT:", err.Error())
}
return &pb2.MmfRequest{
// om-function:50502 -> the internal hostname & port number of the MMF service in our Kubernetes cluster
Config: &pb.FunctionConfig{
Host: mmfApiHost,
Port: mmfApiPort,
Type: pb.FunctionConfig_GRPC,
},
Profile: &pb.MatchProfile{
Name: "1v1",
Pools: []*pb.Pool{
{
Name: "everyone",
},
Mmfs: []*pb2.MatchmakingFunctionSpec{
{
Host: os.Getenv("MMF_ADDRESS"),
Port: int32(port),
Type: pb2.MatchmakingFunctionSpec_GRPC,
},
},
Profile: &pb2.Profile{
Name: "1v1",
Pools: map[string]*pb2.Pool{"all": {Name: "everyone"}},
Extensions: map[string]*anypb.Any{},
},
}
Expand All @@ -118,52 +101,43 @@ func createAgonesGameServerAllocation() *allocationv1.GameServerAllocation {
}
}

func createOMAssignTicketRequest(match *pb.Match, gsa *allocationv1.GameServerAllocation) *pb.AssignTicketsRequest {
tids := []string{}
for _, t := range match.GetTickets() {
tids = append(tids, t.GetId())
func createOMAssignTicketRequest(match *pb2.Match, gsa *allocationv1.GameServerAllocation) *pb2.CreateAssignmentsRequest {
tids := []*pb2.Ticket{}
for _, r := range match.Rosters {
tids = append(tids, r.Tickets...)
}

return &pb.AssignTicketsRequest{
Assignments: []*pb.AssignmentGroup{
{
TicketIds: tids,
Assignment: &pb.Assignment{
Connection: fmt.Sprintf("%s:%d", gsa.Status.Address, gsa.Status.Ports[0].Port),
},
return &pb2.CreateAssignmentsRequest{
AssignmentRoster: &pb2.Roster{
Name: "My_Assignment_Roster_Name",
Assignment: &pb2.Assignment{
Connection: fmt.Sprintf("%s:%d", gsa.Status.Address, gsa.Status.Ports[0].Port),
},
Tickets: tids,
},
}
}

func (r Client) run() error {
bc := r.BackendServiceClient
closer := r.CloserBackendServiceClient
defer func() {
err := closer()
if err != nil {
log.Println(err)
}
}()
invocationResultChan := make(chan *pb2.StreamedMmfResponse)

agonesClient := r.AgonesClientset
fmt.Println("Director: start InvokeMatchmakingFunctions in another thread")

stream, err := bc.FetchMatches(context.Background(), createOMFetchMatchesRequest())
if err != nil {
return fmt.Errorf("fail to get response stream from backend.FetchMatches call: %w", err)
}
go r.OmClient.InvokeMatchmakingFunctions(context.Background(), createOMFetchMatchesRequest(), invocationResultChan)

agonesClient := r.AgonesClientset

totalMatches := 0
// Read the FetchMatches response. Each loop fetches an available game match that satisfies the match profiles.
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("error streaming response from backend.FetchMatches call: %w", err)
}
fmt.Println("Director: waiting for invocationResultChan to have a resp")
for resp := range invocationResultChan {

fmt.Println("got something from the invocationResultChan: ", resp)

ctx := context.Background()

fmt.Println("Creating a game server")

gsa, err := agonesClient.AllocationV1().GameServerAllocations("default").Create(ctx, createAgonesGameServerAllocation(), metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("error requesting allocation: %w", err)
Expand All @@ -176,7 +150,9 @@ func (r Client) run() error {
continue
}

if _, err = bc.AssignTickets(context.Background(), createOMAssignTicketRequest(resp.GetMatch(), gsa)); err != nil {
fmt.Println("The game server is allocated, assigning tickets")

if _, err = r.OmClient.CreateAssignments(createOMAssignTicketRequest(resp.GetMatch(), gsa)); err != nil {
// Corner case where we allocated a game server for players who left the queue after some waiting time.
// Note that we may still leak some game servers when tickets got assigned but players left the queue before game frontend announced the assignments.
if err = agonesClient.AgonesV1().GameServers("default").Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{}); err != nil {
Expand Down
Loading
Loading