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 }