agones.dev/agones@v1.54.0/sdks/rust/src/sdk.rs (about)

     1  // Copyright 2018 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 std::{env, time::Duration};
    16  use tonic::transport::Channel;
    17  
    18  mod api {
    19      tonic::include_proto!("agones.dev.sdk");
    20  }
    21  
    22  use api::sdk_client::SdkClient;
    23  pub use api::{
    24      game_server::{
    25          status::{PlayerStatus, Port},
    26          ObjectMeta, Spec, Status,
    27      },
    28      GameServer,
    29  };
    30  
    31  pub type WatchStream = tonic::Streaming<GameServer>;
    32  
    33  use crate::{alpha::Alpha, beta::Beta, errors::Result};
    34  
    35  #[inline]
    36  fn empty() -> api::Empty {
    37      api::Empty {}
    38  }
    39  
    40  /// SDK is an instance of the Agones SDK
    41  #[derive(Clone)]
    42  pub struct Sdk {
    43      client: SdkClient<Channel>,
    44      alpha: Alpha,
    45      beta: Beta,
    46  }
    47  
    48  impl Sdk {
    49      /// Starts a new SDK instance, and connects to localhost on the port specified
    50      /// or else falls back to the `AGONES_SDK_GRPC_PORT` environment variable,
    51      /// or defaults to 9357.
    52      ///
    53      /// # Errors
    54      ///
    55      /// - The port specified in `AGONES_SDK_GRPC_PORT` can't be parsed as a `u16`.
    56      /// - A connection cannot be established with an Agones SDK server
    57      /// - The handshake takes longer than 30 seconds
    58      pub async fn new(port: Option<u16>, keep_alive: Option<Duration>) -> Result<Self> {
    59          let addr: http::Uri = format!(
    60              "http://localhost:{}",
    61              port.unwrap_or_else(|| {
    62                  env::var("AGONES_SDK_GRPC_PORT")
    63                      .ok()
    64                      .and_then(|s| s.parse().ok())
    65                      .unwrap_or(9357)
    66              })
    67          )
    68          .parse()?;
    69  
    70          Self::new_internal(addr, keep_alive).await
    71      }
    72  
    73      pub async fn new_with_host(
    74          host: Option<String>,
    75          port: Option<u16>,
    76          keep_alive: Option<Duration>,
    77      ) -> Result<Self> {
    78          let addr: http::Uri = format!(
    79              "{}:{}",
    80              host.unwrap_or_else(|| {
    81                  env::var("AGONES_SDK_GRPC_HOST")
    82                      .ok()
    83                      .and_then(|s| s.parse().ok())
    84                      .unwrap_or("http://localhost".to_owned())
    85              }),
    86              port.unwrap_or_else(|| {
    87                  env::var("AGONES_SDK_GRPC_PORT")
    88                      .ok()
    89                      .and_then(|s| s.parse().ok())
    90                      .unwrap_or(9357)
    91              })
    92          )
    93          .parse()?;
    94  
    95          Self::new_internal(addr, keep_alive).await
    96      }
    97  
    98      async fn new_internal(addr: http::Uri, keep_alive: Option<Duration>) -> Result<Self> {
    99          let builder = tonic::transport::channel::Channel::builder(addr)
   100              .connect_timeout(Duration::from_secs(30))
   101              .keep_alive_timeout(keep_alive.unwrap_or_else(|| Duration::from_secs(30)));
   102  
   103          // will only attempt to connect on first invocation, so won't exit straight away.
   104          let channel = builder.connect_lazy();
   105          let mut client = SdkClient::new(channel.clone());
   106          let alpha = Alpha::new(channel.clone());
   107          let beta = Beta::new(channel);
   108  
   109          tokio::time::timeout(Duration::from_secs(30), async {
   110              let mut connect_interval = tokio::time::interval(Duration::from_millis(100));
   111  
   112              loop {
   113                  connect_interval.tick().await;
   114                  if client.get_game_server(empty()).await.is_ok() {
   115                      break;
   116                  }
   117              }
   118          })
   119          .await?;
   120  
   121          Ok(Self { client, alpha, beta })
   122      }
   123  
   124      /// Alpha returns the Alpha SDK
   125      #[inline]
   126      pub fn alpha(&self) -> &Alpha {
   127          &self.alpha
   128      }
   129  
   130      /// Beta returns the Beta SDK
   131      #[inline]
   132      pub fn beta(&self) -> &Beta {
   133          &self.beta
   134      }
   135  
   136      /// Marks the Game Server as ready to receive connections
   137      pub async fn ready(&mut self) -> Result<()> {
   138          Ok(self.client.ready(empty()).await.map(|_| ())?)
   139      }
   140  
   141      /// Allocate the Game Server
   142      pub async fn allocate(&mut self) -> Result<()> {
   143          Ok(self.client.allocate(empty()).await.map(|_| ())?)
   144      }
   145  
   146      /// Marks the Game Server as ready to shutdown
   147      pub async fn shutdown(&mut self) -> Result<()> {
   148          Ok(self.client.shutdown(empty()).await.map(|_| ())?)
   149      }
   150  
   151      /// Returns a [`tokio::sync::mpsc::Sender`] that will emit a health check
   152      /// every time a message is sent on the channel.
   153      pub fn health_check(&self) -> tokio::sync::mpsc::Sender<()> {
   154          let mut health_client = self.clone();
   155          let (tx, mut rx) = tokio::sync::mpsc::channel(10);
   156  
   157          tokio::task::spawn(async move {
   158              let health_stream = async_stream::stream! {
   159                  while rx.recv().await.is_some() {
   160                      yield empty();
   161                  }
   162              };
   163  
   164              let _ = health_client.client.health(health_stream).await;
   165          });
   166  
   167          tx
   168      }
   169  
   170      /// Set a Label value on the backing GameServer record that is stored in Kubernetes
   171      pub async fn set_label(
   172          &mut self,
   173          key: impl Into<String>,
   174          value: impl Into<String>,
   175      ) -> Result<()> {
   176          Ok(self
   177              .client
   178              .set_label(api::KeyValue {
   179                  key: key.into(),
   180                  value: value.into(),
   181              })
   182              .await
   183              .map(|_| ())?)
   184      }
   185  
   186      /// Set a Annotation value on the backing Gameserver record that is stored in Kubernetes
   187      pub async fn set_annotation(
   188          &mut self,
   189          key: impl Into<String>,
   190          value: impl Into<String>,
   191      ) -> Result<()> {
   192          Ok(self
   193              .client
   194              .set_annotation(api::KeyValue {
   195                  key: key.into(),
   196                  value: value.into(),
   197              })
   198              .await
   199              .map(|_| ())?)
   200      }
   201  
   202      /// Returns most of the backing GameServer configuration and Status
   203      pub async fn get_gameserver(&mut self) -> Result<GameServer> {
   204          Ok(self
   205              .client
   206              .get_game_server(empty())
   207              .await
   208              .map(|res| res.into_inner())?)
   209      }
   210  
   211      /// Reserve marks the Game Server as Reserved for a given duration, at which point
   212      /// it will return the GameServer to a Ready state.
   213      /// Do note, the smallest unit available in the time.Duration argument is one second.
   214      pub async fn reserve(&mut self, duration: Duration) -> Result<()> {
   215          Ok(self
   216              .client
   217              .reserve(api::Duration {
   218                  seconds: std::cmp::max(duration.as_secs() as i64, 1),
   219              })
   220              .await
   221              .map(|_| ())?)
   222      }
   223  
   224      /// Watch the backing GameServer configuration on updated
   225      pub async fn watch_gameserver(&mut self) -> Result<WatchStream> {
   226          Ok(self
   227              .client
   228              .watch_game_server(empty())
   229              .await
   230              .map(|stream| stream.into_inner())?)
   231      }
   232  }