diff --git a/build/Makefile b/build/Makefile index 2738431ed1..224584b388 100644 --- a/build/Makefile +++ b/build/Makefile @@ -184,6 +184,7 @@ gen-install: ensure-build-image gen-gameservers-sdk-grpc: ensure-build-image docker run --rm $(common_mounts) -w $(mount_path) $(build_tag) /root/gen-grpc-go.sh docker run --rm $(common_mounts) -w $(mount_path) $(build_tag) /root/gen-grpc-cpp.sh + docker run --rm $(common_mounts) -w $(mount_path) $(build_tag) /root/gen-grpc-rust.sh # Generate the client for our CustomResourceDefinition gen-crd-client: ensure-build-image diff --git a/build/build-image/Dockerfile b/build/build-image/Dockerfile index 109b088979..a0a6fdcde5 100644 --- a/build/build-image/Dockerfile +++ b/build/build-image/Dockerfile @@ -67,6 +67,26 @@ RUN go get -u github.com/golang/dep/cmd/dep && \ RUN mkdir -p /go/src && cd /go/src && mkdir -p k8s.io && cd k8s.io && \ git clone -b kubernetes-1.9.2 --depth=3 https://github.com/kubernetes/code-generator.git +# install rust +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH \ + RUST_VERSION=1.26.1 +ENV RUST_ARCH=x86_64-unknown-linux-gnu \ + RUSTUP_SHA256=c9837990bce0faab4f6f52604311a19bb8d2cde989bea6a7b605c8e526db6f02 +RUN wget -q https://static.rust-lang.org/rustup/archive/1.11.0/${RUST_ARCH}/rustup-init && \ + echo "${RUSTUP_SHA256} *rustup-init" | sha256sum -c - && \ + chmod +x rustup-init && \ + ./rustup-init -y --no-modify-path --default-toolchain $RUST_VERSION && \ + rm rustup-init && \ + rustup --version; \ + cargo --version; \ + rustc --version; + +# install rust tooling for SDK generation +RUN cargo install protobuf-codegen --vers 2.0.2 +RUN cargo install grpcio-compiler --vers 0.3.0 + # make sure we keep the path to go RUN echo "export PATH=/usr/local/go/bin:/go/bin/:\$PATH" >> /root/.bashrc # make nano the editor diff --git a/build/build-image/gen-grpc-rust.sh b/build/build-image/gen-grpc-rust.sh new file mode 100644 index 0000000000..3663072b57 --- /dev/null +++ b/build/build-image/gen-grpc-rust.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd /go/src/agones.dev/agones +protoc --rust_out sdks/rust/src/grpc --grpc_out=sdks/rust/src/grpc --plugin=protoc-gen-grpc=`which grpc_rust_plugin` sdk.proto diff --git a/examples/rust-simple/.gitignore b/examples/rust-simple/.gitignore new file mode 100644 index 0000000000..2b811b4747 --- /dev/null +++ b/examples/rust-simple/.gitignore @@ -0,0 +1,19 @@ + +# Created by https://www.gitignore.io/api/rust + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + + +# End of https://www.gitignore.io/api/rust + +sdk diff --git a/examples/rust-simple/Cargo.toml b/examples/rust-simple/Cargo.toml new file mode 100644 index 0000000000..14393eeac9 --- /dev/null +++ b/examples/rust-simple/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rust-simple" +version = "0.1.0" + +[dependencies] +agones = { path = "../../sdks/rust" } diff --git a/examples/rust-simple/Dockerfile b/examples/rust-simple/Dockerfile new file mode 100644 index 0000000000..787907d465 --- /dev/null +++ b/examples/rust-simple/Dockerfile @@ -0,0 +1,50 @@ +FROM rust:1.25.0 as builder +RUN useradd -m build + +# Rust SDK depends on https://github.com/pingcap/grpc-rs and it requires CMake and Go + +## Cmake +ENV CMAKE_MINOR_VERSION=v3.10 \ + CMAKE_FULL_VERSION=3.10.3 +RUN mkdir -p /usr/src/cmake \ + && curl -fSLO https://cmake.org/files/${CMAKE_MINOR_VERSION}/cmake-${CMAKE_FULL_VERSION}.tar.gz \ + && curl -fSLO https://cmake.org/files/${CMAKE_MINOR_VERSION}/cmake-${CMAKE_FULL_VERSION}-SHA-256.txt \ + && sha256sum -c cmake-${CMAKE_FULL_VERSION}-SHA-256.txt 2>&1 | grep OK \ + && tar xf cmake-${CMAKE_FULL_VERSION}.tar.gz -C /usr/src/cmake --strip-components=1 \ + && rm -f cmake-${CMAKE_FULL_VERSION}.* \ + && cd /usr/src/cmake \ + && ./configure && make -j$(nproc) && make install + +## Go +ENV GO_VERSION=1.10.2 \ + GO_CHECKSUM=4b677d698c65370afa33757b6954ade60347aaca310ea92a63ed717d7cb0c2ff +RUN mkdir -p /usr/local/go \ + && curl -fSO https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz \ + && shasum -a 256 go${GO_VERSION}.linux-amd64.tar.gz | grep ${GO_CHECKSUM} \ + && tar xf go${GO_VERSION}.linux-amd64.tar.gz -C /usr/local/go --strip-components=1 \ + && rm -f go${GO_VERSION}.linux-amd64.tar.gz +ENV PATH $PATH:/usr/local/go/bin + +# SDK +COPY sdk/src /home/builder/agones/sdks/rust/src +COPY sdk/Cargo.toml /home/builder/agones/sdks/rust/ +COPY sdk/Cargo.lock /home/builder/agones/sdks/rust/ + +# Example +COPY src /home/builder/agones/examples/rust-simple/src +COPY Cargo.toml /home/builder/agones/examples/rust-simple/ +COPY Cargo.lock /home/builder/agones/examples/rust-simple/ +COPY Makefile /home/builder/agones/examples/rust-simple/ + +WORKDIR /home/builder/agones/examples/rust-simple +RUN make build + +FROM debian:stretch +RUN useradd -m server + +COPY --from=builder /home/builder/agones/examples/rust-simple/target/release/rust-simple /home/server/rust-simple +RUN chown -R server /home/server && \ + chmod o+x /home/server/rust-simple + +USER server +ENTRYPOINT /home/server/rust-simple diff --git a/examples/rust-simple/Makefile b/examples/rust-simple/Makefile new file mode 100644 index 0000000000..1e2ab23b4c --- /dev/null +++ b/examples/rust-simple/Makefile @@ -0,0 +1,40 @@ +# +# Makefile for building the world's simplest Rust game server +# + +# __ __ _ _ _ +# \ \ / /_ _ _ __(_) __ _| |__ | | ___ ___ +# \ \ / / _` | '__| |/ _` | '_ \| |/ _ \ __| +# \ V / (_| | | | | (_| | |_) | | __\__ \ +# \_/ \__,_|_| |_|\__,_|_.__/|_|\___|___/ +# + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +project_path := $(dir $(mkfile_path)) +server_tag = rust-simple-server:0.1 + +# _____ _ +# |_ _|_ _ _ __ __ _ ___| |_ ___ +# | |/ _` | '__/ _` |/ _ \ __/ __| +# | | (_| | | | (_| | __/ |_\__ \ +# |_|\__,_|_| \__, |\___|\__|___/ +# |___/ + +# build the library +build: build-server + +# Build the server +build-server: + cargo build --release + +# Build a docker image for the server, and tag it +build-image: + # Docker does not allow to copy outside files, + mkdir -p $(project_path)sdk + cp -rf $(project_path)../../sdks/rust/src $(project_path)sdk/src + cp -rf $(project_path)../../sdks/rust/Cargo.* $(project_path)sdk + docker build $(project_path) --tag=$(server_tag) + +clean: + cargo clean + rm -rf $(project_path)sdk diff --git a/examples/rust-simple/README.md b/examples/rust-simple/README.md new file mode 100644 index 0000000000..86e493088a --- /dev/null +++ b/examples/rust-simple/README.md @@ -0,0 +1,34 @@ +# Simple Rust Example + +This is a very simple "server" that doesn't do much other than show how the SDK works in Rust. + +It will +- Setup the Agones SDK +- Call `SDK::Ready()` to register that it is ready with Agones. +- Every 10 seconds, write a log saying "Hi! I'm a Game Server" +- After 60 seconds, call `SDK::Shutdown()` to shut the server down. + +## Running by minikube + +First of all, you have to configure Agones on minikude. Check out [these instructions](https://github.com/GoogleCloudPlatform/agones/blob/3b856a4b90862a3ea183643869f81801d4468220/install/README.md). + +``` +$ eval $(minikube docker-env) +$ make build-image +$ kubectl create -f gameserver.yaml +``` + +You can see output of the example by the following. + +``` +$ POD_NAME=`kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}'` +$ kubectl logs $POD_NAME -c rust-simple +Rust Game Server has started! +Creating SDK instance +Marking server as ready... +Running for 0 seconds +Health ping sent +Health ping sent +Health ping sent +Health ping sent +``` diff --git a/examples/rust-simple/gameserver.yaml b/examples/rust-simple/gameserver.yaml new file mode 100644 index 0000000000..abf2b6b147 --- /dev/null +++ b/examples/rust-simple/gameserver.yaml @@ -0,0 +1,14 @@ +apiVersion: "stable.agones.dev/v1alpha1" +kind: GameServer +metadata: + # generate a unique name + # will need to be created with `kubectl create` + generateName: rust-simple- +spec: + containerPort: 7654 + template: + spec: + containers: + - name: rust-simple + image: gcr.io/agones-images/rust-simple-server:0.1 + imagePullPolicy: IfNotPresent diff --git a/examples/rust-simple/src/main.rs b/examples/rust-simple/src/main.rs new file mode 100644 index 0000000000..d819606007 --- /dev/null +++ b/examples/rust-simple/src/main.rs @@ -0,0 +1,68 @@ +extern crate agones; + +use std::result::Result; +use std::thread; +use std::time::Duration; + +macro_rules! enclose { + ( ($( $x:ident ),*) $y:expr ) => { + { + $(let mut $x = $x.clone();)* + $y + } + }; +} + +fn main() { + println!("Rust Game Server has started!"); + + ::std::process::exit(match run() { + Ok(_) => { + println!("Rust Game Server finished."); + 0 + }, + Err(msg) => { + println!("{}", msg); + 1 + } + }); +} + +fn run() -> Result<(), String>{ + + println!("Creating SDK instance"); + let sdk = agones::Sdk::new().map_err(|_| "Could not connect to the sidecar. Exiting!")?; + + let _t = thread::spawn(enclose!{(sdk) move || { + loop { + match sdk.health() { + (s, Ok(_)) => { + println!("Health ping sent"); + sdk = s; + }, + (s, Err(e)) => { + println!("Health ping failed : {:?}", e); + sdk = s; + } + } + thread::sleep(Duration::from_secs(2)); + } + }}); + + println!("Marking server as ready..."); + + for i in 0..10 { + let time = i * 10; + println!("Running for {} seconds", time); + + thread::sleep(Duration::from_secs(10)); + + if i == 5 { + println!("Shutting down after 60 seconds..."); + sdk.shutdown().map_err(|e| format!("Could not run Shutdown: {}. Exiting!", e))?; + println!("...marked for Shutdown"); + } + } + + Ok(()) +} diff --git a/sdks/rust/.gitignore b/sdks/rust/.gitignore new file mode 100644 index 0000000000..2ace67fe0f --- /dev/null +++ b/sdks/rust/.gitignore @@ -0,0 +1,17 @@ + +# Created by https://www.gitignore.io/api/rust + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + + +# End of https://www.gitignore.io/api/rust diff --git a/sdks/rust/Cargo.toml b/sdks/rust/Cargo.toml new file mode 100644 index 0000000000..1562ee0ead --- /dev/null +++ b/sdks/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "agones" +version = "0.1.0" + +[dependencies] +grpcio = "0.3.0" +grpcio-proto = "0.3.0" +protobuf = "2.0.2" +futures = "^0.1.15" +error-chain = "0.11.0" diff --git a/sdks/rust/README.md b/sdks/rust/README.md new file mode 100644 index 0000000000..a060bf3d23 --- /dev/null +++ b/sdks/rust/README.md @@ -0,0 +1,46 @@ +# Rust Game Server Client SDK + +This is the Rust version of the Agones Game Server Client SDK. +Check the [Client SDK Documentation](../) for more details on each of the SDK functions and how to run the SDK locally. + +## Prerequisites + +- CMake >= 3.8.0 +- Rust >= 1.19.0 +- Go (>=1.7) + +The SDK needs the above for building to [gRPC-rs](https://github.com/pingcap/grpc-rs). + +## Usage + +Add this crate to `dependencies` section in your Cargo.toml. +Specify a directory where this README.md is located to the `path`. + +```toml +[dependencies] +agones = { path = "../agones/sdks/rust" } +``` + +Add `extern crate agones` to your crate root. + +To begin working with the SDK, create an instance of it. This function blocks until connection and handshake are made. + +```rust +let sdk = agones::Sdk::new()?; +``` + +To send a [health check](../README.md#health) ping call `sdk.health()`. + +```rust +if sdk.health().is_ok() { + println!("Health ping sent"); +} +``` + +To mark that the [game session is completed](../README.md#shutdown) and the game server should be shut down call `sdk.shutdown()`. + +```rust +if sdk.shutdown().is_err() { + println!("Could not run Shutdown"); +} +``` diff --git a/sdks/rust/src/errors.rs b/sdks/rust/src/errors.rs new file mode 100644 index 0000000000..7802c86565 --- /dev/null +++ b/sdks/rust/src/errors.rs @@ -0,0 +1,13 @@ +// Wrap in a new error +error_chain!{ + foreign_links { + Grpc(::grpcio::Error); + } + + errors { + HealthPingConnectionFailure(t: String) { + description("health ping connection failure"), + display("health ping connection failure: '{}'", t), + } + } +} diff --git a/sdks/rust/src/grpc/mod.rs b/sdks/rust/src/grpc/mod.rs new file mode 100644 index 0000000000..cd6e8899b4 --- /dev/null +++ b/sdks/rust/src/grpc/mod.rs @@ -0,0 +1,2 @@ +pub mod sdk; +pub mod sdk_grpc; diff --git a/sdks/rust/src/grpc/sdk.rs b/sdks/rust/src/grpc/sdk.rs new file mode 100644 index 0000000000..645d1148bd --- /dev/null +++ b/sdks/rust/src/grpc/sdk.rs @@ -0,0 +1,202 @@ +// This file is generated by rust-protobuf 2.0.2. Do not edit +// @generated + +// https://github.com/Manishearth/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy)] + +#![cfg_attr(rustfmt, rustfmt_skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unsafe_code)] +#![allow(unused_imports)] +#![allow(unused_results)] + +use protobuf::Message as Message_imported_for_functions; +use protobuf::ProtobufEnum as ProtobufEnum_imported_for_functions; + +#[derive(PartialEq,Clone,Default)] +pub struct Empty { + // special fields + unknown_fields: ::protobuf::UnknownFields, + cached_size: ::protobuf::CachedSize, +} + +impl Empty { + pub fn new() -> Empty { + ::std::default::Default::default() + } +} + +impl ::protobuf::Message for Empty { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream) -> ::protobuf::ProtobufResult<()> { + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &::std::any::Any { + self as &::std::any::Any + } + fn as_any_mut(&mut self) -> &mut ::std::any::Any { + self as &mut ::std::any::Any + } + fn into_any(self: Box) -> ::std::boxed::Box<::std::any::Any> { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> Empty { + Empty::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static mut descriptor: ::protobuf::lazy::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::lazy::Lazy { + lock: ::protobuf::lazy::ONCE_INIT, + ptr: 0 as *const ::protobuf::reflect::MessageDescriptor, + }; + unsafe { + descriptor.get(|| { + let fields = ::std::vec::Vec::new(); + ::protobuf::reflect::MessageDescriptor::new::( + "Empty", + fields, + file_descriptor_proto() + ) + }) + } + } + + fn default_instance() -> &'static Empty { + static mut instance: ::protobuf::lazy::Lazy = ::protobuf::lazy::Lazy { + lock: ::protobuf::lazy::ONCE_INIT, + ptr: 0 as *const Empty, + }; + unsafe { + instance.get(Empty::new) + } + } +} + +impl ::protobuf::Clear for Empty { + fn clear(&mut self) { + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for Empty { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for Empty { + fn as_ref(&self) -> ::protobuf::reflect::ProtobufValueRef { + ::protobuf::reflect::ProtobufValueRef::Message(self) + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\tsdk.proto\x12\x15stable.agones.dev.sdk\"\x07\n\x05Empty2\xe0\x01\n\ + \x03SDK\x12E\n\x05Ready\x12\x1c.stable.agones.dev.sdk.Empty\x1a\x1c.stab\ + le.agones.dev.sdk.Empty\"\0\x12H\n\x08Shutdown\x12\x1c.stable.agones.dev\ + .sdk.Empty\x1a\x1c.stable.agones.dev.sdk.Empty\"\0\x12H\n\x06Health\x12\ + \x1c.stable.agones.dev.sdk.Empty\x1a\x1c.stable.agones.dev.sdk.Empty\"\0\ + (\x01B\x05Z\x03sdkJ\xff\x08\n\x06\x12\x04\x0e\0!\x01\n\xd2\x04\n\x01\x0c\ + \x12\x03\x0e\0\x122\xc7\x04\x20Copyright\x202017\x20Google\x20Inc.\x20Al\ + l\x20Rights\x20Reserved.\n\n\x20Licensed\x20under\x20the\x20Apache\x20Li\ + cense,\x20Version\x202.0\x20(the\x20\"License\");\n\x20you\x20may\x20not\ + \x20use\x20this\x20file\x20except\x20in\x20compliance\x20with\x20the\x20\ + License.\n\x20You\x20may\x20obtain\x20a\x20copy\x20of\x20the\x20License\ + \x20at\n\n\x20\x20\x20\x20\x20http://www.apache.org/licenses/LICENSE-2.0\ + \n\n\x20Unless\x20required\x20by\x20applicable\x20law\x20or\x20agreed\ + \x20to\x20in\x20writing,\x20software\n\x20distributed\x20under\x20the\ + \x20License\x20is\x20distributed\x20on\x20an\x20\"AS\x20IS\"\x20BASIS,\n\ + \x20WITHOUT\x20WARRANTIES\x20OR\x20CONDITIONS\x20OF\x20ANY\x20KIND,\x20e\ + ither\x20express\x20or\x20implied.\n\x20See\x20the\x20License\x20for\x20\ + the\x20specific\x20language\x20governing\x20permissions\x20and\n\x20limi\ + tations\x20under\x20the\x20License.\n\n\x08\n\x01\x02\x12\x03\x10\x08\ + \x1d\n\x08\n\x01\x08\x12\x03\x11\0\x1a\n\x0b\n\x04\x08\xe7\x07\0\x12\x03\ + \x11\0\x1a\n\x0c\n\x05\x08\xe7\x07\0\x02\x12\x03\x11\x07\x11\n\r\n\x06\ + \x08\xe7\x07\0\x02\0\x12\x03\x11\x07\x11\n\x0e\n\x07\x08\xe7\x07\0\x02\0\ + \x01\x12\x03\x11\x07\x11\n\x0c\n\x05\x08\xe7\x07\0\x07\x12\x03\x11\x14\ + \x19\nM\n\x02\x06\0\x12\x04\x14\0\x1e\x01\x1aA\x20SDK\x20service\x20to\ + \x20be\x20used\x20in\x20the\x20GameServer\x20SDK\x20to\x20the\x20Pod\x20\ + Sidecar\n\n\n\n\x03\x06\0\x01\x12\x03\x14\x08\x0b\n1\n\x04\x06\0\x02\0\ + \x12\x04\x16\x04\x17\x05\x1a#\x20Call\x20when\x20the\x20GameServer\x20is\ + \x20ready\n\n\x0c\n\x05\x06\0\x02\0\x01\x12\x03\x16\x08\r\n\x0c\n\x05\ + \x06\0\x02\0\x02\x12\x03\x16\x0f\x14\n\x0c\n\x05\x06\0\x02\0\x03\x12\x03\ + \x16\x1f$\n9\n\x04\x06\0\x02\x01\x12\x04\x19\x04\x1a\x05\x1a+\x20Call\ + \x20when\x20the\x20GmaeServer\x20is\x20shutting\x20down\n\n\x0c\n\x05\ + \x06\0\x02\x01\x01\x12\x03\x19\x08\x10\n\x0c\n\x05\x06\0\x02\x01\x02\x12\ + \x03\x19\x12\x17\n\x0c\n\x05\x06\0\x02\x01\x03\x12\x03\x19\"'\nW\n\x04\ + \x06\0\x02\x02\x12\x04\x1c\x04\x1d\x05\x1aI\x20Send\x20a\x20Empty\x20eve\ + ry\x20d\x20Duration\x20to\x20declare\x20that\x20this\x20GameSever\x20is\ + \x20healthy\n\n\x0c\n\x05\x06\0\x02\x02\x01\x12\x03\x1c\x08\x0e\n\x0c\n\ + \x05\x06\0\x02\x02\x05\x12\x03\x1c\x10\x16\n\x0c\n\x05\x06\0\x02\x02\x02\ + \x12\x03\x1c\x17\x1c\n\x0c\n\x05\x06\0\x02\x02\x03\x12\x03\x1c',\n\n\n\ + \x02\x04\0\x12\x04\x20\0!\x01\n\n\n\x03\x04\0\x01\x12\x03\x20\x08\rb\x06\ + proto3\ +"; + +static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy { + lock: ::protobuf::lazy::ONCE_INIT, + ptr: 0 as *const ::protobuf::descriptor::FileDescriptorProto, +}; + +fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { + ::protobuf::parse_from_bytes(file_descriptor_proto_data).unwrap() +} + +pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + unsafe { + file_descriptor_proto_lazy.get(|| { + parse_descriptor_proto() + }) + } +} diff --git a/sdks/rust/src/grpc/sdk_grpc.rs b/sdks/rust/src/grpc/sdk_grpc.rs new file mode 100644 index 0000000000..c27b3f2dc1 --- /dev/null +++ b/sdks/rust/src/grpc/sdk_grpc.rs @@ -0,0 +1,118 @@ +// This file is generated. Do not edit +// @generated + +// https://github.com/Manishearth/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy)] + +#![cfg_attr(rustfmt, rustfmt_skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unsafe_code)] +#![allow(unused_imports)] +#![allow(unused_results)] + +const METHOD_SDK_READY: ::grpcio::Method = ::grpcio::Method { + ty: ::grpcio::MethodType::Unary, + name: "/stable.agones.dev.sdk.SDK/Ready", + req_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, + resp_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, +}; + +const METHOD_SDK_SHUTDOWN: ::grpcio::Method = ::grpcio::Method { + ty: ::grpcio::MethodType::Unary, + name: "/stable.agones.dev.sdk.SDK/Shutdown", + req_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, + resp_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, +}; + +const METHOD_SDK_HEALTH: ::grpcio::Method = ::grpcio::Method { + ty: ::grpcio::MethodType::ClientStreaming, + name: "/stable.agones.dev.sdk.SDK/Health", + req_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, + resp_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, +}; + +pub struct SdkClient { + client: ::grpcio::Client, +} + +impl SdkClient { + pub fn new(channel: ::grpcio::Channel) -> Self { + SdkClient { + client: ::grpcio::Client::new(channel), + } + } + + pub fn ready_opt(&self, req: &super::sdk::Empty, opt: ::grpcio::CallOption) -> ::grpcio::Result { + self.client.unary_call(&METHOD_SDK_READY, req, opt) + } + + pub fn ready(&self, req: &super::sdk::Empty) -> ::grpcio::Result { + self.ready_opt(req, ::grpcio::CallOption::default()) + } + + pub fn ready_async_opt(&self, req: &super::sdk::Empty, opt: ::grpcio::CallOption) -> ::grpcio::Result<::grpcio::ClientUnaryReceiver> { + self.client.unary_call_async(&METHOD_SDK_READY, req, opt) + } + + pub fn ready_async(&self, req: &super::sdk::Empty) -> ::grpcio::Result<::grpcio::ClientUnaryReceiver> { + self.ready_async_opt(req, ::grpcio::CallOption::default()) + } + + pub fn shutdown_opt(&self, req: &super::sdk::Empty, opt: ::grpcio::CallOption) -> ::grpcio::Result { + self.client.unary_call(&METHOD_SDK_SHUTDOWN, req, opt) + } + + pub fn shutdown(&self, req: &super::sdk::Empty) -> ::grpcio::Result { + self.shutdown_opt(req, ::grpcio::CallOption::default()) + } + + pub fn shutdown_async_opt(&self, req: &super::sdk::Empty, opt: ::grpcio::CallOption) -> ::grpcio::Result<::grpcio::ClientUnaryReceiver> { + self.client.unary_call_async(&METHOD_SDK_SHUTDOWN, req, opt) + } + + pub fn shutdown_async(&self, req: &super::sdk::Empty) -> ::grpcio::Result<::grpcio::ClientUnaryReceiver> { + self.shutdown_async_opt(req, ::grpcio::CallOption::default()) + } + + pub fn health_opt(&self, opt: ::grpcio::CallOption) -> ::grpcio::Result<(::grpcio::ClientCStreamSender, ::grpcio::ClientCStreamReceiver)> { + self.client.client_streaming(&METHOD_SDK_HEALTH, opt) + } + + pub fn health(&self) -> ::grpcio::Result<(::grpcio::ClientCStreamSender, ::grpcio::ClientCStreamReceiver)> { + self.health_opt(::grpcio::CallOption::default()) + } + pub fn spawn(&self, f: F) where F: ::futures::Future + Send + 'static { + self.client.spawn(f) + } +} + +pub trait Sdk { + fn ready(&self, ctx: ::grpcio::RpcContext, req: super::sdk::Empty, sink: ::grpcio::UnarySink); + fn shutdown(&self, ctx: ::grpcio::RpcContext, req: super::sdk::Empty, sink: ::grpcio::UnarySink); + fn health(&self, ctx: ::grpcio::RpcContext, stream: ::grpcio::RequestStream, sink: ::grpcio::ClientStreamingSink); +} + +pub fn create_sdk(s: S) -> ::grpcio::Service { + let mut builder = ::grpcio::ServiceBuilder::new(); + let instance = s.clone(); + builder = builder.add_unary_handler(&METHOD_SDK_READY, move |ctx, req, resp| { + instance.ready(ctx, req, resp) + }); + let instance = s.clone(); + builder = builder.add_unary_handler(&METHOD_SDK_SHUTDOWN, move |ctx, req, resp| { + instance.shutdown(ctx, req, resp) + }); + let instance = s.clone(); + builder = builder.add_client_streaming_handler(&METHOD_SDK_HEALTH, move |ctx, req, resp| { + instance.health(ctx, req, resp) + }); + builder.build() +} diff --git a/sdks/rust/src/lib.rs b/sdks/rust/src/lib.rs new file mode 100644 index 0000000000..add2b520ca --- /dev/null +++ b/sdks/rust/src/lib.rs @@ -0,0 +1,13 @@ +//! the Rust game server SDK +#[macro_use] +extern crate error_chain; +extern crate grpcio; +extern crate grpcio_proto; +extern crate protobuf; +extern crate futures; + +mod grpc; +mod sdk; +pub mod errors; + +pub use sdk::Sdk; diff --git a/sdks/rust/src/sdk.rs b/sdks/rust/src/sdk.rs new file mode 100644 index 0000000000..f4365b6ebc --- /dev/null +++ b/sdks/rust/src/sdk.rs @@ -0,0 +1,78 @@ +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use grpcio; +use futures::{Future, Sink}; + +use errors::*; +use grpc::sdk as sdk; +use grpc::sdk_grpc as sdk_grpc; +use protobuf::Message; + +const PORT: i32 = 59357; + +/// SDK is an instance of the Agones SDK +pub struct Sdk { + client : Arc, + health : Arc>>>, +} + +impl Sdk { + + /// Starts a new SDK instance, and connects to localhost on port 59357. + /// Blocks until connection and handshake are made. + /// Times out after 30 seconds. + pub fn new() -> Result { + let addr = format!("localhost:{}", PORT); + let env = Arc::new(grpcio::EnvBuilder::new().build()); + let ch = grpcio::ChannelBuilder::new(env).keepalive_timeout(Duration::new(30, 0)).connect(&addr); + let cli = sdk_grpc::SdkClient::new(ch); + let req = sdk::Empty::new(); + let _ = cli.ready(&req).map(Box::new)?; + let (sender, _) = cli.health()?; + Ok(Sdk{client: Arc::new(cli), health: Arc::new(Mutex::new(Some(sender)))}) + } + + /// Marks the Game Server as ready to receive connections + pub fn ready(&self) -> Result<()> { + let req = sdk::Empty::default_instance(); + let res = self.client.ready(req).map(|_| ())?; + Ok(res) + } + + /// Marks the Game Server as ready to shutdown + pub fn shutdown(&self) -> Result<()> { + let req = sdk::Empty::default_instance(); + let res = self.client.shutdown(req).map(|_| ())?; + Ok(res) + } + + /// Sends a ping to the health check to indicate that this server is healthy + pub fn health(mut self) -> (Self, Result<()>) { + // Avoid `cannot move out of borrowed content` compile error for self.health + let h = self.health.lock().unwrap().take(); + if h.is_none() { + return (self, Err(ErrorKind::HealthPingConnectionFailure("failed to hold client stream for health ping".to_string()).into())); + } + let h : grpcio::ClientCStreamSender = h.unwrap().into(); + + let req = sdk::Empty::new(); + match h.send((req, grpcio::WriteFlags::default())).wait() { + Ok(h) => { + self.health = Arc::new(Mutex::new(Some(h))); + (self, Ok(())) + }, + Err(e) => { + (self, Err(ErrorKind::Grpc(e).into())) + }, + } + } +} + +impl Clone for Sdk { + fn clone(&self) -> Self { + Self { + client: Arc::clone(&self.client), + health: self.health.clone(), + } + } +}