agones.dev/agones@v1.54.0/sdks/go/sdk.go (about)

     1  // Copyright 2017 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package sdk is the Go game server sdk.
    16  package sdk
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"time"
    24  
    25  	"github.com/pkg/errors"
    26  	"google.golang.org/grpc/credentials/insecure"
    27  
    28  	"google.golang.org/grpc"
    29  
    30  	"agones.dev/agones/pkg/sdk"
    31  )
    32  
    33  // GameServerCallback is a function definition to be called
    34  // when a GameServer CRD has been changed.
    35  type GameServerCallback func(gs *sdk.GameServer)
    36  
    37  // SDK is an instance of the Agones SDK.
    38  type SDK struct {
    39  	client sdk.SDKClient
    40  	ctx    context.Context
    41  	health sdk.SDK_HealthClient
    42  	alpha  *Alpha
    43  	beta   *Beta
    44  }
    45  
    46  // ErrorLog is a function to log the error.
    47  type ErrorLog func(string, error)
    48  
    49  // Logger is a pluggable function that outputs the error message to standard error.
    50  var Logger ErrorLog = func(msg string, err error) {
    51  	fmt.Fprintf(os.Stderr, "%s: %s\n", msg, err)
    52  }
    53  
    54  // NewSDK starts a new SDK instance, defaulting to a connection at "localhost:9357"
    55  // unless overridden by "AGONES_SDK_GRPC_HOST" and "AGONES_SDK_GRPC_PORT" environment variables.
    56  // Blocks until connection and handshake are made.
    57  // Times out after 30 seconds.
    58  func NewSDK() (*SDK, error) {
    59  	host := os.Getenv("AGONES_SDK_GRPC_HOST")
    60  	if host == "" {
    61  		host = "localhost"
    62  	}
    63  
    64  	port := os.Getenv("AGONES_SDK_GRPC_PORT")
    65  	if port == "" {
    66  		port = "9357"
    67  	}
    68  	addr := fmt.Sprintf("%s:%s", host, port)
    69  	s := &SDK{
    70  		ctx: context.Background(),
    71  	}
    72  	// Block for at least 30 seconds.
    73  	ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second)
    74  	defer cancel()
    75  	// nolint: staticcheck
    76  	conn, err := grpc.DialContext(ctx, addr, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
    77  	if err != nil {
    78  		return s, errors.Wrapf(err, "could not connect to %s", addr)
    79  	}
    80  	s.client = sdk.NewSDKClient(conn)
    81  	s.health, err = s.client.Health(s.ctx)
    82  	s.alpha = newAlpha(conn)
    83  	s.beta = newBeta(conn)
    84  	return s, errors.Wrap(err, "could not set up health check")
    85  }
    86  
    87  // Alpha returns the Alpha SDK.
    88  func (s *SDK) Alpha() *Alpha {
    89  	return s.alpha
    90  }
    91  
    92  // Beta returns the Beta SDK.
    93  func (s *SDK) Beta() *Beta {
    94  	return s.beta
    95  }
    96  
    97  // Ready marks the Game Server as ready to receive connections.
    98  func (s *SDK) Ready() error {
    99  	_, err := s.client.Ready(s.ctx, &sdk.Empty{})
   100  	return errors.Wrap(err, "could not send Ready message")
   101  }
   102  
   103  // Allocate self marks this gameserver as Allocated.
   104  func (s *SDK) Allocate() error {
   105  	_, err := s.client.Allocate(s.ctx, &sdk.Empty{})
   106  	return errors.Wrap(err, "could not mark self as Allocated")
   107  }
   108  
   109  // Shutdown marks the Game Server as ready to shutdown.
   110  func (s *SDK) Shutdown() error {
   111  	_, err := s.client.Shutdown(s.ctx, &sdk.Empty{})
   112  	return errors.Wrapf(err, "could not send Shutdown message")
   113  }
   114  
   115  // Reserve marks the Game Server as Reserved for a given duration, at which point
   116  // it will return the GameServer to a Ready state.
   117  // Do note, the smallest unit available in the time.Duration argument is a second.
   118  func (s *SDK) Reserve(d time.Duration) error {
   119  	_, err := s.client.Reserve(s.ctx, &sdk.Duration{Seconds: int64(d.Seconds())})
   120  	return errors.Wrap(err, "could not send Reserve message")
   121  }
   122  
   123  // Health sends a ping to the sidecar health check to indicate that this Game Server is healthy.
   124  func (s *SDK) Health() error {
   125  	return errors.Wrap(s.health.Send(&sdk.Empty{}), "could not send Health ping")
   126  }
   127  
   128  // SetLabel sets a metadata label on the `GameServer` with the prefix "agones.dev/sdk-".
   129  func (s *SDK) SetLabel(key, value string) error {
   130  	kv := &sdk.KeyValue{Key: key, Value: value}
   131  	_, err := s.client.SetLabel(s.ctx, kv)
   132  	return errors.Wrap(err, "could not set label")
   133  }
   134  
   135  // SetAnnotation sets a metadata annotation on the `GameServer` with the prefix "agones.dev/sdk-".
   136  func (s *SDK) SetAnnotation(key, value string) error {
   137  	kv := &sdk.KeyValue{Key: key, Value: value}
   138  	_, err := s.client.SetAnnotation(s.ctx, kv)
   139  	return errors.Wrap(err, "could not set annotation")
   140  }
   141  
   142  // GameServer retrieve the GameServer details.
   143  func (s *SDK) GameServer() (*sdk.GameServer, error) {
   144  	gs, err := s.client.GetGameServer(s.ctx, &sdk.Empty{})
   145  	return gs, errors.Wrap(err, "could not retrieve gameserver")
   146  }
   147  
   148  // WatchGameServer asynchronously calls the given GameServerCallback with the current GameServer
   149  // configuration when the backing GameServer configuration is updated.
   150  // This function can be called multiple times to add more than one GameServerCallback.
   151  func (s *SDK) WatchGameServer(f GameServerCallback) error {
   152  	stream, err := s.client.WatchGameServer(s.ctx, &sdk.Empty{})
   153  	if err != nil {
   154  		return errors.Wrap(err, "could not watch gameserver")
   155  	}
   156  	log := func(gs *sdk.GameServer, msg string, err error) {
   157  		if gs == nil || gs.ObjectMeta.DeletionTimestamp == 0 {
   158  			return
   159  		}
   160  		Logger(msg, err)
   161  	}
   162  	go func() {
   163  		for {
   164  			var gs *sdk.GameServer
   165  			gs, err = stream.Recv()
   166  			if err != nil {
   167  				if err == io.EOF {
   168  					log(gs, "gameserver event stream EOF received", nil)
   169  					return
   170  				}
   171  				log(gs, "error watching GameServer", err)
   172  				// This is to wait for the reconnection, and not peg the CPU at 100%.
   173  				time.Sleep(time.Second)
   174  				continue
   175  			}
   176  			f(gs)
   177  		}
   178  	}()
   179  	return nil
   180  }