agones.dev/agones@v1.54.0/sdks/rust/src/beta.rs (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  
    15  use crate::errors::Result;
    16  use tonic::transport::Channel;
    17  
    18  mod api {
    19      tonic::include_proto!("agones.dev.sdk.beta");
    20  }
    21  
    22  use api::sdk_client::SdkClient;
    23  
    24  /// Beta is an instance of the Agones Beta SDK
    25  #[derive(Clone)]
    26  pub struct Beta {
    27      client: SdkClient<Channel>,
    28  }
    29  
    30  impl Beta {
    31  
    32      /// new creates a new instance of the Beta SDK
    33      pub(crate) fn new(ch: Channel) -> Self {
    34          Self {
    35              client: SdkClient::new(ch),
    36          }
    37      }
    38  
    39      /// get_counter_count returns the Count for a Counter, given the Counter's key (name).
    40      /// Will error if the key was not predefined in the GameServer resource on creation.
    41      #[inline]
    42      pub async fn get_counter_count(&mut self, key: &str) -> Result<i64> {
    43          Ok(self
    44              .client
    45              .get_counter(api::GetCounterRequest { name: key.to_string() })
    46              .await
    47              .map(|c| c.into_inner().count)?)
    48      }
    49  
    50      /// increment_counter increases a counter by the given nonnegative integer amount.
    51      /// Will execute the increment operation against the current CRD value. Will max at max(int64).
    52      /// Will error if the key was not predefined in the GameServer resource on creation.
    53      /// Returns error if the count is at the current capacity (to the latest knowledge of the SDK),
    54      /// and no increment will occur.
    55      ///
    56      /// Note: A potential race condition here is that if count values are set from both the SDK and
    57      /// through the K8s API (Allocation or otherwise), since the SDK append operation back to the CRD
    58      /// value is batched asynchronous any value incremented past the capacity will be silently truncated.
    59      #[inline]
    60      pub async fn increment_counter(&mut self, key: &str, amount: i64) -> Result<()> {
    61          Ok(self
    62              .client
    63              .update_counter(api::UpdateCounterRequest {
    64                  counter_update_request: Some(api::CounterUpdateRequest {
    65                      name: key.to_string(),
    66                      count: None,
    67                      capacity: None,
    68                      count_diff: amount,
    69                  }),
    70              })
    71              .await
    72              .map(|_| ())?)
    73      }
    74  
    75      /// decrement_counter decreases the current count by the given nonnegative integer amount.
    76      /// The Counter Will not go below 0. Will execute the decrement operation against the current CRD value.
    77      /// Will error if the count is at 0 (to the latest knowledge of the SDK), and no decrement will occur.
    78      #[inline]
    79      pub async fn decrement_counter(&mut self, key: &str, amount: i64) -> Result<()> {
    80          Ok(self
    81              .client
    82              .update_counter(api::UpdateCounterRequest {
    83                  counter_update_request: Some(api::CounterUpdateRequest {
    84                      name: key.to_string(),
    85                      count: None,
    86                      capacity: None,
    87                      count_diff: -amount,
    88                  }),
    89              })
    90              .await
    91              .map(|_| ())?)
    92      }
    93  
    94      /// set_counter_count sets a count to the given value. Use with care, as this will overwrite any previous
    95      /// invocations’ value. Cannot be greater than Capacity.
    96      #[inline]
    97      pub async fn set_counter_count(&mut self, key: &str, amount: i64) -> Result<()> {
    98          Ok(self
    99              .client
   100              .update_counter(api::UpdateCounterRequest {
   101                  counter_update_request: Some(api::CounterUpdateRequest {
   102                      name: key.to_string(),
   103                      count: Some(amount.into()),
   104                      capacity: None,
   105                      count_diff: 0,
   106                  }),
   107              })
   108              .await
   109              .map(|_| ())?)
   110      }
   111  
   112      /// get_counter_capacity returns the Capacity for a Counter, given the Counter's key (name).
   113      /// Will error if the key was not predefined in the GameServer resource on creation.
   114      #[inline]
   115      pub async fn get_counter_capacity(&mut self, key: &str) -> Result<i64> {
   116          Ok(self
   117              .client
   118              .get_counter(api::GetCounterRequest { name: key.to_string() })
   119              .await
   120              .map(|c| c.into_inner().capacity)?)
   121      }
   122  
   123      /// set_counter_capacity sets the capacity for the given Counter. A capacity of 0 is no capacity.
   124      #[inline]
   125      pub async fn set_counter_capacity(&mut self, key: &str, amount: i64) -> Result<()> {
   126          Ok(self
   127              .client
   128              .update_counter(api::UpdateCounterRequest {
   129                  counter_update_request: Some(api::CounterUpdateRequest {
   130                      name: key.to_string(),
   131                      count: None,
   132                      capacity: Some(amount.into()),
   133                      count_diff: 0,
   134                  }),
   135              })
   136              .await
   137              .map(|_| ())?)
   138      }
   139  
   140      /// get_list_capacity returns the Capacity for a List, given the List's key (name).
   141      /// Will error if the key was not predefined in the GameServer resource on creation.
   142      #[inline]
   143      pub async fn get_list_capacity(&mut self, key: &str) -> Result<i64> {
   144          Ok(self
   145              .client
   146              .get_list(api::GetListRequest { name: key.to_string() })
   147              .await
   148              .map(|l| l.into_inner().capacity)?)
   149      }
   150  
   151      /// set_list_capacity sets the capacity for a given list. Capacity must be between 0 and 1000.
   152      /// Will error if the key was not predefined in the GameServer resource on creation.
   153      #[inline]
   154      pub async fn set_list_capacity(&mut self, key: &str, amount: i64) -> Result<()> {
   155          Ok(self
   156              .client
   157              .update_list(api::UpdateListRequest {
   158                  list: Some(api::List {
   159                      name: key.to_string(),
   160                      capacity: amount,
   161                      values: vec![],
   162                  }),
   163                  update_mask: Some(prost_types::FieldMask { paths: vec!["capacity".to_string()] }),
   164              })
   165              .await
   166              .map(|_| ())?)
   167      }
   168  
   169      /// list_contains returns if a string exists in a List's values list, given the List's key (name)
   170      /// and the string value. Search is case-sensitive.
   171      /// Will error if the key was not predefined in the GameServer resource on creation.
   172      #[inline]
   173      pub async fn list_contains(&mut self, key: &str, value: &str) -> Result<bool> {
   174          Ok(self
   175              .client
   176              .get_list(api::GetListRequest { name: key.to_string() })
   177              .await
   178              .map(|l| l.into_inner().values.contains(&value.to_string()))?)
   179      }
   180  
   181      /// get_list_length returns the length of the Values list for a List, given the List's key (name).
   182      /// Will error if the key was not predefined in the GameServer resource on creation.
   183      #[inline]
   184      pub async fn get_list_length(&mut self, key: &str) -> Result<usize> {
   185          Ok(self
   186              .client
   187              .get_list(api::GetListRequest { name: key.to_string() })
   188              .await
   189              .map(|l| l.into_inner().values.len())?)
   190      }
   191  
   192      /// get_list_values returns the Values for a List, given the List's key (name).
   193      /// Will error if the key was not predefined in the GameServer resource on creation.
   194      #[inline]
   195      pub async fn get_list_values(&mut self, key: &str) -> Result<Vec<String>> {
   196          Ok(self
   197              .client
   198              .get_list(api::GetListRequest { name: key.to_string() })
   199              .await
   200              .map(|l| l.into_inner().values)?)
   201      }
   202  
   203      /// append_list_value appends a string to a List's values list, given the List's key (name)
   204      /// and the string value. Will error if the string already exists in the list.
   205      /// Will error if the key was not predefined in the GameServer resource on creation.
   206      #[inline]
   207      pub async fn append_list_value(&mut self, key: &str, value: &str) -> Result<()> {
   208          Ok(self
   209              .client
   210              .add_list_value(api::AddListValueRequest {
   211                  name: key.to_string(),
   212                  value: value.to_string(),
   213              })
   214              .await
   215              .map(|_| ())?)
   216      }
   217  
   218      /// delete_list_value removes a string from a List's values list, given the List's key (name)
   219      /// and the string value. Will error if the string does not exist in the list.
   220      /// Will error if the key was not predefined in the GameServer resource on creation.
   221      #[inline]
   222      pub async fn delete_list_value(&mut self, key: &str, value: &str) -> Result<()> {
   223          Ok(self
   224              .client
   225              .remove_list_value(api::RemoveListValueRequest {
   226                  name: key.to_string(),
   227                  value: value.to_string(),
   228              })
   229              .await
   230              .map(|_| ())?)
   231      }
   232  }
   233  
   234  
   235  #[cfg(test)]
   236  mod tests {
   237      type Result<T> = std::result::Result<T, String>;
   238      use std::collections::HashMap;
   239  
   240      #[derive(Debug, PartialEq)]
   241      struct Counter {
   242          name: String,
   243          count: i64,
   244          capacity: i64,
   245      }
   246  
   247      #[derive(Debug, PartialEq)]
   248      struct List {
   249          name: String,
   250          values: Vec<String>,
   251          capacity: i64,
   252      }
   253  
   254      // MockBeta simulates Beta's implementation
   255      struct MockBeta {
   256          counters: HashMap<String, Counter>,
   257          lists: HashMap<String, List>,
   258      }
   259  
   260      impl MockBeta {
   261          fn new() -> Self {
   262              Self {
   263                  counters: HashMap::new(),
   264                  lists: HashMap::new(),
   265              }
   266          }
   267  
   268      // Counter methods
   269      async fn get_counter_count(&mut self, key: &str) -> Result<i64> {
   270              self.counters.get(key)
   271                  .map(|c| c.count)
   272                  .ok_or_else::<String, _>(|| format!("counter not found: {}", key))
   273          }
   274  
   275      async fn get_counter_capacity(&mut self, key: &str) -> Result<i64> {
   276              self.counters.get(key)
   277                  .map(|c| c.capacity)
   278                  .ok_or_else::<String, _>(|| format!("counter not found: {}", key))
   279          }
   280  
   281      async fn set_counter_capacity(&mut self, key: &str, amount: i64) -> Result<()> {
   282              let counter = self.counters.get_mut(key)
   283                  .ok_or_else::<String, _>(|| format!("counter not found: {}", key))?;
   284              if amount < 0 {
   285                  return Err("capacity must be >= 0".to_string());
   286              }
   287              counter.capacity = amount;
   288              Ok(())
   289          }
   290  
   291      async fn set_counter_count(&mut self, key: &str, amount: i64) -> Result<()> {
   292              let counter = self.counters.get_mut(key)
   293                  .ok_or_else::<String, _>(|| format!("counter not found: {}", key))?;
   294              if amount < 0 || amount > counter.capacity {
   295                  return Err("count out of range".to_string());
   296              }
   297              counter.count = amount;
   298              Ok(())
   299          }
   300  
   301      async fn increment_counter(&mut self, key: &str, amount: i64) -> Result<()> {
   302              let counter = self.counters.get_mut(key)
   303                  .ok_or_else::<String, _>(|| format!("counter not found: {}", key))?;
   304              let new_count = counter.count + amount;
   305              if amount < 0 || new_count > counter.capacity {
   306                  return Err("increment out of range".to_string());
   307              }
   308              counter.count = new_count;
   309              Ok(())
   310          }
   311  
   312      async fn decrement_counter(&mut self, key: &str, amount: i64) -> Result<()> {
   313              let counter = self.counters.get_mut(key)
   314                  .ok_or_else::<String, _>(|| format!("counter not found: {}", key))?;
   315              let new_count = counter.count - amount;
   316              if amount < 0 || new_count < 0 {
   317                  return Err("decrement out of range".to_string());
   318              }
   319              counter.count = new_count;
   320              Ok(())
   321          }
   322  
   323      // List methods
   324      async fn get_list_capacity(&mut self, key: &str) -> Result<i64> {
   325              self.lists.get(key)
   326                  .map(|l| l.capacity)
   327                  .ok_or_else::<String, _>(|| format!("list not found: {}", key))
   328          }
   329  
   330      async fn set_list_capacity(&mut self, key: &str, amount: i64) -> Result<()> {
   331              let list = self.lists.get_mut(key)
   332                  .ok_or_else::<String, _>(|| format!("list not found: {}", key))?;
   333              if amount < 0 || amount > 1000 {
   334                  return Err("capacity out of range".to_string());
   335              }
   336              list.capacity = amount;
   337              if list.values.len() > amount as usize {
   338                  list.values.truncate(amount as usize);
   339              }
   340              Ok(())
   341          }
   342  
   343      async fn get_list_length(&mut self, key: &str) -> Result<usize> {
   344              self.lists.get(key)
   345                  .map(|l| l.values.len())
   346                  .ok_or_else::<String, _>(|| format!("list not found: {}", key))
   347          }
   348  
   349      async fn get_list_values(&mut self, key: &str) -> Result<Vec<String>> {
   350              self.lists.get(key)
   351                  .map(|l| l.values.clone())
   352                  .ok_or_else::<String, _>(|| format!("list not found: {}", key))
   353          }
   354  
   355      async fn list_contains(&mut self, key: &str, value: &str) -> Result<bool> {
   356              self.lists.get(key)
   357                  .map(|l| l.values.contains(&value.to_string()))
   358                  .ok_or_else::<String, _>(|| format!("list not found: {}", key))
   359          }
   360  
   361      async fn append_list_value(&mut self, key: &str, value: &str) -> Result<()> {
   362              let list = self.lists.get_mut(key)
   363                  .ok_or_else::<String, _>(|| format!("list not found: {}", key))?;
   364              if list.values.len() >= list.capacity as usize {
   365                  return Err("no available capacity".to_string());
   366              }
   367              if list.values.contains(&value.to_string()) {
   368                  return Err("already exists".to_string());
   369              }
   370              list.values.push(value.to_string());
   371              Ok(())
   372          }
   373  
   374      async fn delete_list_value(&mut self, key: &str, value: &str) -> Result<()> {
   375              let list = self.lists.get_mut(key)
   376                  .ok_or_else::<String, _>(|| format!("list not found: {}", key))?;
   377              if let Some(pos) = list.values.iter().position(|v| v == value) {
   378                  list.values.remove(pos);
   379                  Ok(())
   380              } else {
   381                  Err("not found".to_string())
   382              }
   383          }
   384      }
   385  
   386      #[tokio::test]
   387      async fn test_beta_get_and_update_counter() {
   388          let mut beta = MockBeta::new();
   389  
   390          beta.counters.insert("sessions".to_string(), Counter { name: "sessions".to_string(), count: 21, capacity: 42 });
   391          beta.counters.insert("games".to_string(), Counter { name: "games".to_string(), count: 12, capacity: 24 });
   392          beta.counters.insert("gamers".to_string(), Counter { name: "gamers".to_string(), count: 263, capacity: 500 });
   393  
   394          // Set Counter and Set Capacity
   395          {
   396              let count = beta.get_counter_count("sessions").await.unwrap();
   397              assert_eq!(count, 21);
   398  
   399              let capacity = beta.get_counter_capacity("sessions").await.unwrap();
   400              assert_eq!(capacity, 42);
   401  
   402              let want_capacity = 25;
   403              beta.set_counter_capacity("sessions", want_capacity).await.unwrap();
   404              let capacity = beta.get_counter_capacity("sessions").await.unwrap();
   405              assert_eq!(capacity, want_capacity);
   406  
   407              let want_count = 10;
   408              beta.set_counter_count("sessions", want_count).await.unwrap();
   409              let count = beta.get_counter_count("sessions").await.unwrap();
   410              assert_eq!(count, want_count);
   411          }
   412  
   413          // Get and Set Non-Defined Counter
   414          {
   415              assert!(beta.get_counter_count("secessions").await.is_err());
   416              assert!(beta.get_counter_capacity("secessions").await.is_err());
   417              assert!(beta.set_counter_capacity("secessions", 100).await.is_err());
   418              assert!(beta.set_counter_count("secessions", 0).await.is_err());
   419          }
   420  
   421          // Decrement Counter Fails then Success
   422          {
   423              let count = beta.get_counter_count("games").await.unwrap();
   424              assert_eq!(count, 12);
   425  
   426              assert!(beta.decrement_counter("games", 21).await.is_err());
   427              let count = beta.get_counter_count("games").await.unwrap();
   428              assert_eq!(count, 12);
   429  
   430              assert!(beta.decrement_counter("games", -12).await.is_err());
   431              let count = beta.get_counter_count("games").await.unwrap();
   432              assert_eq!(count, 12);
   433  
   434              beta.decrement_counter("games", 12).await.unwrap();
   435              let count = beta.get_counter_count("games").await.unwrap();
   436              assert_eq!(count, 0);
   437          }
   438      }
   439  
   440      #[tokio::test]
   441      async fn test_beta_increment_counter_fails_then_success() {
   442          let mut beta = MockBeta::new();
   443  
   444          beta.counters.insert("gamers".to_string(), Counter { name: "gamers".to_string(), count: 263, capacity: 500 });
   445  
   446          // Increment Counter Fails then Success
   447          {
   448              let count = beta.get_counter_count("gamers").await.unwrap();
   449              assert_eq!(count, 263);
   450  
   451              assert!(beta.increment_counter("gamers", 250).await.is_err());
   452              let count = beta.get_counter_count("gamers").await.unwrap();
   453              assert_eq!(count, 263);
   454  
   455              assert!(beta.increment_counter("gamers", -237).await.is_err());
   456              let count = beta.get_counter_count("gamers").await.unwrap();
   457              assert_eq!(count, 263);
   458  
   459              beta.increment_counter("gamers", 237).await.unwrap();
   460              let count = beta.get_counter_count("gamers").await.unwrap();
   461              assert_eq!(count, 500);
   462          }
   463      }
   464  
   465      #[tokio::test]
   466      async fn test_beta_get_and_update_list() {
   467          let mut beta = MockBeta::new();
   468  
   469          beta.lists.insert("foo".to_string(), List { name: "foo".to_string(), values: vec![], capacity: 2 });
   470          beta.lists.insert("bar".to_string(), List { name: "bar".to_string(), values: vec!["abc".to_string(), "def".to_string()], capacity: 5 });
   471          beta.lists.insert("baz".to_string(), List { name: "baz".to_string(), values: vec!["123".to_string(), "456".to_string(), "789".to_string()], capacity: 5 });
   472  
   473          // Get and Set List Capacity
   474          {
   475              let capacity = beta.get_list_capacity("foo").await.unwrap();
   476              assert_eq!(capacity, 2);
   477  
   478              let want_capacity = 5;
   479              beta.set_list_capacity("foo", want_capacity).await.unwrap();
   480              let capacity = beta.get_list_capacity("foo").await.unwrap();
   481              assert_eq!(capacity, want_capacity);
   482          }
   483  
   484          // Get List Length, Get List Values, ListContains, and Append List Value
   485          {
   486              let length = beta.get_list_length("bar").await.unwrap();
   487              assert_eq!(length, 2);
   488  
   489              let values = beta.get_list_values("bar").await.unwrap();
   490              assert_eq!(values, vec!["abc".to_string(), "def".to_string()]);
   491  
   492              beta.append_list_value("bar", "ghi").await.unwrap();
   493              let length = beta.get_list_length("bar").await.unwrap();
   494              assert_eq!(length, 3);
   495  
   496              let want_values = vec!["abc".to_string(), "def".to_string(), "ghi".to_string()];
   497              let values = beta.get_list_values("bar").await.unwrap();
   498              assert_eq!(values, want_values);
   499  
   500              let contains = beta.list_contains("bar", "ghi").await.unwrap();
   501              assert!(contains);
   502          }
   503  
   504          // Get List Length, Get List Values, ListContains, and Delete List Value
   505          {
   506              let length = beta.get_list_length("baz").await.unwrap();
   507              assert_eq!(length, 3);
   508  
   509              let values = beta.get_list_values("baz").await.unwrap();
   510              assert_eq!(values, vec!["123".to_string(), "456".to_string(), "789".to_string()]);
   511  
   512              beta.delete_list_value("baz", "456").await.unwrap();
   513              let length = beta.get_list_length("baz").await.unwrap();
   514              assert_eq!(length, 2);
   515  
   516              let want_values = vec!["123".to_string(), "789".to_string()];
   517              let values = beta.get_list_values("baz").await.unwrap();
   518              assert_eq!(values, want_values);
   519  
   520              let contains = beta.list_contains("baz", "456").await.unwrap();
   521              assert!(!contains);
   522          }
   523      }
   524  }