agones.dev/agones@v1.53.0/sdks/csharp/sdk/Beta.cs (about)

     1  // Copyright 2020 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  using Agones.Dev.Sdk.Beta;
    15  using Grpc.Core;
    16  using Microsoft.Extensions.Logging;
    17  using System;
    18  using System.Collections.Generic;
    19  using System.Linq;
    20  using System.Threading;
    21  using System.Threading.Tasks;
    22  using Grpc.Net.Client;
    23  using gProto = Google.Protobuf.WellKnownTypes;
    24  
    25  [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Agones.Test")]
    26  namespace Agones
    27  {
    28      public sealed class Beta : IAgonesBetaSDK
    29      {
    30  
    31          /// <summary>
    32          /// The timeout for gRPC calls.
    33          /// </summary>
    34          public double RequestTimeoutSec { get; set; }
    35  
    36          internal SDK.SDKClient client;
    37          internal readonly IClientStreamWriter<Empty> healthStream;
    38          internal readonly CancellationTokenSource cts;
    39          internal readonly bool ownsCts;
    40          internal CancellationToken ctoken;
    41  
    42          private readonly ILogger _logger;
    43          private bool _disposed;
    44  
    45          public Beta(
    46              GrpcChannel channel,
    47              double requestTimeoutSec = 15,
    48              CancellationTokenSource cancellationTokenSource = null,
    49              ILogger logger = null)
    50          {
    51              _logger = logger;
    52              RequestTimeoutSec = requestTimeoutSec;
    53  
    54              if (cancellationTokenSource == null)
    55              {
    56                  cts = new CancellationTokenSource();
    57                  ownsCts = true;
    58              }
    59              else
    60              {
    61                  cts = cancellationTokenSource;
    62                  ownsCts = false;
    63              }
    64  
    65              ctoken = cts.Token;
    66              client = new SDK.SDKClient(channel);
    67          }
    68  
    69          /// <summary>
    70          /// GetCounterCountAsync returns the Count for a Counter, given the Counter's key (name).
    71          /// Throws error if the key was not predefined in the GameServer resource on creation.
    72          /// </summary>
    73          /// <returns>The Counter's Count</returns>
    74          public async Task<long> GetCounterCountAsync(string key)
    75          {
    76            try
    77            {
    78                  var request = new GetCounterRequest()
    79                  {
    80                      Name = key,
    81                  };
    82                  var counter = await client.GetCounterAsync(request,
    83                deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
    84              return counter.Count;
    85            }
    86            catch (RpcException ex)
    87            {
    88                  LogError(ex, $"Unable to invoke GetCounterCount({key}).");
    89                  throw;
    90              }
    91          }
    92  
    93          /// <summary>
    94          /// IncrementCounterAsync increases a counter by the given nonnegative integer amount.
    95          /// Will execute the increment operation against the current CRD value. Will max at max(int64).
    96          /// Throws error if the key was not predefined in the GameServer resource on creation.
    97          /// Throws error if the count is at the current capacity (to the latest knowledge of the SDK),
    98          /// and no increment will occur.
    99          ///
   100          /// Note: A potential race condition here is that if count values are set from both the SDK and
   101          /// through the K8s API (Allocation or otherwise), since the SDK append operation back to the CRD
   102          /// value is batched asynchronous any value incremented past the capacity will be silently truncated.
   103          /// </summary>
   104          public async Task IncrementCounterAsync(string key, long amount)
   105          {
   106              if (amount < 0)
   107              {
   108                  throw new ArgumentOutOfRangeException($"CountIncrement amount must be a positive number, found {amount}");
   109              }
   110              try
   111              {
   112                  var request = new CounterUpdateRequest()
   113                  {
   114                      Name = key,
   115                      CountDiff = amount,
   116                  };
   117                  var updateRequest = new UpdateCounterRequest()
   118                  {
   119                      CounterUpdateRequest = request,
   120                  };
   121                  await client.UpdateCounterAsync(updateRequest,
   122                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   123                  // If there is no error, then the request was successful.
   124              }
   125              catch (RpcException ex)
   126              {
   127                  LogError(ex, $"Unable to invoke IncrementCounter({key}, {amount}).");
   128                  throw;
   129              }
   130          }
   131  
   132          /// <summary>
   133          /// DecrementCounterAsync decreases the current count by the given nonnegative integer amount.
   134          /// The Counter will not go below 0. Will execute the decrement operation against the current CRD value.
   135          /// Throws error if the count is at 0 (to the latest knowledge of the SDK), and no decrement will occur.
   136          /// </summary>
   137          public async Task DecrementCounterAsync(string key, long amount)
   138          {
   139              if (amount < 0)
   140              {
   141                  throw new ArgumentOutOfRangeException($"DecrementCounter amount must be a positive number, found {amount}");
   142              }
   143              try
   144              {
   145                  var request = new CounterUpdateRequest()
   146                  {
   147                      Name = key,
   148                      CountDiff = amount * -1,
   149                  };
   150                  var updateRequest = new UpdateCounterRequest()
   151                  {
   152                      CounterUpdateRequest = request,
   153                  };
   154                  await client.UpdateCounterAsync(updateRequest,
   155                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   156              }
   157              catch (RpcException ex)
   158              {
   159                  LogError(ex, $"Unable to invoke DecrementCounter({key}, {amount}).");
   160                  throw;
   161              }
   162          }
   163  
   164          /// <summary>
   165          /// SetCounterCountAsync sets a count to the given value. Use with care, as this will
   166          /// overwrite any previous invocations’ value. Cannot be greater than Capacity.
   167          /// </summary>
   168          public async Task SetCounterCountAsync(string key, long amount)
   169          {
   170              try
   171              {
   172                  var request = new CounterUpdateRequest()
   173                  {
   174                      Name = key,
   175                      Count = amount,
   176                  };
   177                  var updateRequest = new UpdateCounterRequest()
   178                  {
   179                      CounterUpdateRequest = request,
   180                  };
   181                  await client.UpdateCounterAsync(updateRequest,
   182                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   183              }
   184              catch (RpcException ex)
   185              {
   186                  LogError(ex, $"Unable to invoke SetCounterCount({key}, {amount}).");
   187                  throw;
   188              }
   189          }
   190  
   191          /// <summary>
   192          /// GetCounterCapacityAsync returns the Capacity for a Counter, given the Counter's key (name).
   193          /// Throws error if the key was not predefined in the GameServer resource on creation.
   194          /// </summary>
   195          /// <returns>The Counter's capacity</returns>
   196          public async Task<long> GetCounterCapacityAsync(string key)
   197          {
   198              try
   199              {
   200                  var request = new GetCounterRequest()
   201                  {
   202                      Name = key,
   203                  };
   204                  var counter = await client.GetCounterAsync(request,
   205                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   206                  return counter.Capacity;
   207              }
   208              catch (RpcException ex)
   209              {
   210                  LogError(ex, $"Unable to invoke GetCounterCapacity({key}).");
   211                  throw;
   212              }
   213          }
   214  
   215          /// <summary>
   216          /// SetCounterCapacityAsync sets the capacity for the given Counter.
   217          /// A capacity of 0 is no capacity.
   218          /// </summary>
   219          public async Task SetCounterCapacityAsync(string key, long amount)
   220          {
   221              try
   222              {
   223                  var request = new CounterUpdateRequest()
   224                  {
   225                      Name = key,
   226                      Capacity = amount,
   227                  };
   228                  var updateRequest = new UpdateCounterRequest()
   229                  {
   230                      CounterUpdateRequest = request,
   231                  };
   232                  await client.UpdateCounterAsync(updateRequest,
   233                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   234              }
   235              catch (RpcException ex)
   236              {
   237                  LogError(ex, $"Unable to invoke SetCounterCapacity({key}, {amount}).");
   238                  throw;
   239              }
   240          }
   241  
   242          /// <summary>
   243          /// GetListCapacityAsync returns the Capacity for a List, given the List's key (name).
   244          /// Throws error if the key was not predefined in the GameServer resource on creation.
   245          /// </summary>
   246          /// <returns>The List's capacity</returns>
   247          public async Task<long> GetListCapacityAsync(string key)
   248          {
   249              try
   250              {
   251                  var request = new GetListRequest()
   252                  {
   253                      Name = key,
   254                  };
   255                  var list = await client.GetListAsync(request,
   256                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   257                  return list.Capacity;
   258              }
   259              catch (RpcException ex)
   260              {
   261                  LogError(ex, $"Unable to invoke GetListCapacity({key}).");
   262                  throw;
   263              }
   264          }
   265  
   266          /// <summary>
   267          /// SetListCapacityAsync sets the capacity for a given list. Capacity must be between 0 and 1000.
   268          /// Throws error if the key was not predefined in the GameServer resource on creation.
   269          /// </summary>
   270          public async Task SetListCapacityAsync(string key, long amount)
   271          {
   272              try
   273              {
   274                  var list = new List()
   275                  {
   276                      Name = key,
   277                      Capacity = amount,
   278                  };
   279                  // FieldMask to update the capacity field only
   280                  var updateMask = new gProto.FieldMask()
   281                  {
   282                      Paths = { "capacity" },
   283                  };
   284                  var request = new UpdateListRequest()
   285                  {
   286                      List = list,
   287                      UpdateMask = updateMask,
   288                  };
   289                  await client.UpdateListAsync(request,
   290                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   291              }
   292              catch (RpcException ex)
   293              {
   294                  LogError(ex, $"Unable to invoke SetListCapacity({key}, {amount}).");
   295                  throw;
   296              }
   297          }
   298  
   299          /// <summary>
   300          /// ListContainsAsync returns if a string exists in a List's values list, given the List's key
   301          /// and the string value. Search is case-sensitive.
   302          /// Throws error if the key was not predefined in the GameServer resource on creation.
   303          /// </summary>
   304          /// <returns>True if the value is found in the List</returns>
   305          public async Task<bool> ListContainsAsync(string key, string value)
   306          {
   307              try
   308              {
   309                  var request = new GetListRequest()
   310                  {
   311                      Name = key,
   312                  };
   313                  var list = await client.GetListAsync(request,
   314                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   315                  if (list.Values.Contains(value))
   316                  {
   317                      return true;
   318                  };
   319                  return false;
   320              }
   321              catch (RpcException ex)
   322              {
   323                  LogError(ex, $"Unable to invoke ListContains({key}, {value}).");
   324                  throw;
   325              }
   326          }
   327  
   328          /// <summary>
   329          /// GetListLengthAsync returns the length of the Values list for a List, given the List's key.
   330          /// Throws error if the key was not predefined in the GameServer resource on creation.
   331          /// </summary>
   332          /// <returns>The length of List's values array</returns>
   333          public async Task<int> GetListLengthAsync(string key)
   334          {
   335              try
   336              {
   337                  var request = new GetListRequest()
   338                  {
   339                      Name = key,
   340                  };
   341                  var list = await client.GetListAsync(request,
   342                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   343                  return list.Values.Count;
   344              }
   345              catch (RpcException ex)
   346              {
   347                  LogError(ex, $"Unable to invoke GetListLength({key}).");
   348                  throw;
   349              }
   350          }
   351  
   352          /// <summary>
   353          /// GetListValuesAsync returns the Values for a List, given the List's key (name).
   354          /// Throws error if the key was not predefined in the GameServer resource on creation.
   355          /// </summary>
   356          /// <returns>The List's values array</returns>
   357          public async Task<IList<string>> GetListValuesAsync(string key)
   358          {
   359              try
   360              {
   361                  var request = new GetListRequest()
   362                  {
   363                      Name = key,
   364                  };
   365                  var list = await client.GetListAsync(request,
   366                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   367                  return list.Values;
   368              }
   369              catch (RpcException ex)
   370              {
   371                  LogError(ex, $"Unable to invoke GetListValues({key}).");
   372                  throw;
   373              }
   374          }
   375  
   376          /// <summary>
   377          /// AppendListValueAsync appends a string to a List's values list, given the List's key (name)
   378          /// and the string value. Throws error if the string already exists in the list.
   379          /// Throws error if the key was not predefined in the GameServer resource on creation.
   380          /// </summary>
   381          public async Task AppendListValueAsync(string key, string value)
   382          {
   383              try
   384              {
   385                  var request = new AddListValueRequest()
   386                  {
   387                      Name = key,
   388                      Value = value,
   389                  };
   390                  await client.AddListValueAsync(request,
   391                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   392              }
   393              catch (RpcException ex)
   394              {
   395                  LogError(ex, $"Unable to invoke AppendListValue({key}, {value}).");
   396                  throw;
   397              }
   398          }
   399  
   400          /// <summary>
   401          /// DeleteListValueAsync removes a string from a List's values list, given the List's key
   402          /// and the string value. Throws error if the string does not exist in the list.
   403          /// Throws error if the key was not predefined in the GameServer resource on creation.
   404          /// </summary>
   405          public async Task DeleteListValueAsync(string key, string value)
   406          {
   407              try
   408              {
   409                  var request = new RemoveListValueRequest()
   410                  {
   411                      Name = key,
   412                      Value = value,
   413                  };
   414                  await client.RemoveListValueAsync(request,
   415                    deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken);
   416              }
   417              catch (RpcException ex)
   418              {
   419                  LogError(ex, $"Unable to invoke DeleteListValue({key}, {value}).");
   420                  throw;
   421              }
   422          }
   423  
   424          public void Dispose()
   425          {
   426              if (_disposed)
   427              {
   428                  return;
   429              }
   430  
   431              cts.Cancel();
   432  
   433              if (ownsCts)
   434              {
   435                  cts.Dispose();
   436              }
   437  
   438              _disposed = true;
   439              GC.SuppressFinalize(this);
   440          }
   441  
   442          private void LogError(Exception ex, string message)
   443          {
   444              _logger?.LogError(ex, message);
   445          }
   446      }
   447  }