Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose SDK-Server at HTTP+JSON #265

Merged
merged 2 commits into from
Jun 18, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@
[[constraint]]
branch = "master"
name = "github.com/heptiolabs/healthcheck"

[[constraint]]
name = "github.com/grpc-ecosystem/grpc-gateway"
version = "1.4.1"
2 changes: 1 addition & 1 deletion build/build-image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# ForceUpdate 4 -- change here if you need to force a rebuild
# ForceUpdate 5 -- change here if you need to force a rebuild

# compiling proto + grpc takes an exceptionally long time
# so we'll use a base from `base` - which is manually built using the below tag.
Expand Down
34 changes: 27 additions & 7 deletions build/build-image/gen-grpc-cpp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,32 @@
# See the License for the specific language governing permissions and
# limitations under the License.

set -x

header() {
cat /go/src/agones.dev/agones/build/boilerplate.go.txt $1 >> /tmp/cpp/$1 && mv /tmp/cpp/$1 .
}

googlepais=/go/src/agones.dev/agones/vendor/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis

cd /go/src/agones.dev/agones/sdks/cpp
find -name '*.pb.*' -delete

cd /go/src/agones.dev/agones
protoc -I . --grpc_out=./sdks/cpp --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` sdk.proto
protoc -I . --cpp_out=./sdks/cpp sdk.proto
mkdir /tmp/cpp
find ./sdks/cpp/ -type f \( -name '*.pb.cc' -or -name '*.pb.h' \) -printf "%f\n" | xargs -I@ bash -c "cat ./build/boilerplate.go.txt ./sdks/cpp/@ >> /tmp/cpp/@"
# already has a header, so we'll remove it
rm /tmp/cpp/sdk.grpc.pb.h
mv /tmp/cpp/* ./sdks/cpp/
protoc -I ${googlepais} -I . --grpc_out=./sdks/cpp --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` sdk.proto
protoc -I ${googlepais} -I . --cpp_out=./sdks/cpp sdk.proto ${googlepais}/google/api/annotations.proto ${googlepais}/google/api/http.proto

mkdir -p /tmp/cpp

cd ./sdks/cpp
header sdk.pb.h
header sdk.grpc.pb.cc
header sdk.pb.cc

cd ./google/api/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for ? I didn't know that adding the http gateway will affect the grpc client gen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does because the annotations.proto is included in the sdk.proto file, it generated .cc and .h files that are used in places.

Also it does because the gen is never just the client - it's the client AND the server parts, we just use the client parts -- I don't think there is a way to ask to just generate the client AFAIK. (Also, this grpc cpp stuff will go away eventually, so for now, making it "just work" is more of a priority so we can replace the whole thing, IMHO)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

header annotations.pb.cc
header annotations.pb.h
header http.pb.cc
header http.pb.h

rm -r /tmp/cpp
25 changes: 23 additions & 2 deletions build/build-image/gen-grpc-go.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,29 @@
# See the License for the specific language governing permissions and
# limitations under the License.

set -x

mkdir -p /go/src/
cp -r /go/src/agones.dev/agones/vendor/* /go/src/

go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

googleapis=/go/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis

cd /go/src/agones.dev/agones
protoc -I . sdk.proto --go_out=plugins=grpc:pkg/sdk
protoc -I ${googleapis} -I . sdk.proto --go_out=plugins=grpc:pkg/sdk
protoc -I ${googleapis} -I . sdk.proto --grpc-gateway_out=logtostderr=true:pkg/sdk
protoc -I ${googleapis} -I . sdk.proto --swagger_out=logtostderr=true:.
jq 'del(.schemes[] | select(. == "https"))' sdk.swagger.json > sdk.swagger.temp.json
mv sdk.swagger.temp.json sdk.swagger.json

cat ./build/boilerplate.go.txt ./pkg/sdk/sdk.pb.go >> ./sdk.pb.go
cat ./build/boilerplate.go.txt ./pkg/sdk/sdk.pb.gw.go >> ./sdk.pb.gw.go

goimports -w ./sdk.pb.go
mv ./sdk.pb.go ./pkg/sdk
goimports -w ./sdk.pb.gw.go

mv ./sdk.pb.go ./pkg/sdk
mv ./sdk.pb.gw.go ./pkg/sdk

7 changes: 6 additions & 1 deletion build/build-image/gen-grpc-rust.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

googleapis=/go/src/agones.dev/agones/vendor/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis

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
protoc \
-I ${googleapis} -I . sdk.proto \
--rust_out=sdks/rust/src/grpc --grpc_out=sdks/rust/src/grpc \
--plugin=protoc-gen-grpc=`which grpc_rust_plugin` \

cat ./build/boilerplate.go.txt ./sdks/rust/src/grpc/sdk.rs >> ./sdk.rs
cat ./build/boilerplate.go.txt ./sdks/rust/src/grpc/sdk_grpc.rs >> ./sdk_grpc.rs
Expand Down
170 changes: 111 additions & 59 deletions cmd/sdk-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main
import (
"fmt"
"net"
"net/http"
"strings"
"time"

Expand All @@ -27,6 +28,7 @@ import (
"agones.dev/agones/pkg/sdk"
"agones.dev/agones/pkg/util/runtime"
"agones.dev/agones/pkg/util/signals"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"golang.org/x/net/context"
Expand All @@ -36,7 +38,8 @@ import (
)

const (
port = 59357
grpcPort = 59357
httpPort = 59358

// specifically env vars
gameServerNameEnv = "GAMESERVER_NAME"
Expand All @@ -56,56 +59,33 @@ var (
)

func main() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enocom I refactored this to look more like the controller main, so I could also do the grpc-gateway proxy.

Would love your eyes on it.

viper.SetDefault(localFlag, false)
viper.SetDefault(addressFlag, "localhost")
viper.SetDefault(healthDisabledFlag, false)
viper.SetDefault(healthTimeoutFlag, 5)
viper.SetDefault(healthInitialDelayFlag, 5)
viper.SetDefault(healthFailureThresholdFlag, 3)
pflag.Bool(localFlag, viper.GetBool(localFlag),
"Set this, or LOCAL env, to 'true' to run this binary in local development mode. Defaults to 'false'")
pflag.String(addressFlag, viper.GetString(addressFlag), "The address to bind the server port to. Defaults to 'localhost")
pflag.Bool(healthDisabledFlag, viper.GetBool(healthDisabledFlag),
"Set this, or HEALTH_ENABLED env, to 'true' to enable health checking on the GameServer. Defaults to 'true'")
pflag.Int64(healthTimeoutFlag, viper.GetInt64(healthTimeoutFlag),
"Set this or HEALTH_TIMEOUT env to the number of seconds that the health check times out at. Defaults to 5")
pflag.Int64(healthInitialDelayFlag, viper.GetInt64(healthInitialDelayFlag),
"Set this or HEALTH_INITIAL_DELAY env to the number of seconds that the health will wait before starting. Defaults to 5")
pflag.Int64(healthFailureThresholdFlag, viper.GetInt64(healthFailureThresholdFlag),
"Set this or HEALTH_FAILURE_THRESHOLD env to the number of times the health check needs to fail to be deemed unhealthy. Defaults to 3")
pflag.Parse()

viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
runtime.Must(viper.BindEnv(localFlag))
runtime.Must(viper.BindEnv(gameServerNameEnv))
runtime.Must(viper.BindEnv(podNamespaceEnv))
runtime.Must(viper.BindEnv(healthDisabledFlag))
runtime.Must(viper.BindEnv(healthTimeoutFlag))
runtime.Must(viper.BindEnv(healthInitialDelayFlag))
runtime.Must(viper.BindEnv(healthFailureThresholdFlag))
runtime.Must(viper.BindPFlags(pflag.CommandLine))
ctlConf := parseEnvFlags()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice touch. Makes it easy to quickly read through main and stop on env flags only if it's necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just copied the pattern in the other main.go 👍 no credit to me!

logger.WithField("version", pkg.Version).
WithField("grpcPort", grpcPort).WithField("httpPort", httpPort).
WithField("ctlConf", ctlConf).Info("Starting sdk sidecar")

isLocal := viper.GetBool(localFlag)
address := viper.GetString(addressFlag)
healthDisabled := viper.GetBool(healthDisabledFlag)
healthTimeout := time.Duration(viper.GetInt64(healthTimeoutFlag)) * time.Second
healthInitialDelay := time.Duration(viper.GetInt64(healthInitialDelayFlag)) * time.Second
healthFailureThreshold := viper.GetInt64(healthFailureThresholdFlag)

logger.WithField(localFlag, isLocal).WithField("version", pkg.Version).
WithField("port", port).WithField(addressFlag, address).
WithField(healthDisabledFlag, healthDisabled).WithField(healthTimeoutFlag, healthTimeout).
WithField(healthFailureThresholdFlag, healthFailureThreshold).
WithField(healthInitialDelayFlag, healthInitialDelay).Info("Starting sdk sidecar")

lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
grpcEndpoint := fmt.Sprintf("%s:%d", ctlConf.Address, grpcPort)
lis, err := net.Listen("tcp", grpcEndpoint)
if err != nil {
logger.WithField("port", port).WithField("address", address).Fatalf("Could not listen on port")
logger.WithField("grpcPort", grpcPort).WithField("Address", ctlConf.Address).Fatalf("Could not listen on grpcPort")
}
stop := signals.NewStopChannel()
grpcServer := grpc.NewServer()
// don't graceful stop, because if we get a kill signal
// then the gameserver is being shut down, and we no longer
// care about running RPC calls.
defer grpcServer.Stop()

if isLocal {
mux := gwruntime.NewServeMux()
httpServer := &http.Server{
Addr: fmt.Sprintf("%s:%d", ctlConf.Address, httpPort),
Handler: mux,
}
defer httpServer.Close() // nolint: errcheck
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

if ctlConf.IsLocal {
sdk.RegisterSDKServer(grpcServer, &gameservers.LocalSDKServer{})
} else {
var config *rest.Config
Expand All @@ -128,28 +108,100 @@ func main() {

var s *gameservers.SDKServer
s, err = gameservers.NewSDKServer(viper.GetString(gameServerNameEnv), viper.GetString(podNamespaceEnv),
healthDisabled, healthTimeout, healthFailureThreshold, healthInitialDelay, kubeClient, agonesClient)
ctlConf.HealthDisabled, ctlConf.HealthTimeout, ctlConf.HealthFailureThreshold,
ctlConf.HealthInitialDelay, kubeClient, agonesClient)
if err != nil {
logger.WithError(err).Fatalf("Could not start sidecar")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go s.Run(ctx.Done())
sdk.RegisterSDKServer(grpcServer, s)
}

go func() {
err = grpcServer.Serve(lis)
if err != nil {
logger.WithError(err).Fatal("Could not serve grpc server")
}
}()
go runGrpc(grpcServer, lis)
go runGateway(ctx, grpcEndpoint, mux, httpServer)

<-stop
logger.Info("shutting down grpc server")
// don't graceful stop, because if we get a kill signal
// then the gameserver is being shut down, and we no longer
// care about running RPC calls.
grpcServer.Stop()
logger.Info("shutting down sdk server")
}

// runGrpc runs the grpc service
func runGrpc(grpcServer *grpc.Server, lis net.Listener) {
logger.Info("Starting SDKServer grpc service...")
if err := grpcServer.Serve(lis); err != nil {
logger.WithError(err).Fatal("Could not serve grpc server")
}
}

// runGateway runs the grpc-gateway
func runGateway(ctx context.Context, grpcEndpoint string, mux *gwruntime.ServeMux, httpServer *http.Server) {
conn, err := grpc.DialContext(ctx, grpcEndpoint, grpc.WithBlock(), grpc.WithInsecure())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That grpc.WithBlock is a tricky API isn't it. Nice usage.

if err != nil {
logger.WithError(err).Fatal("Could not dial grpc server...")
}

if err = sdk.RegisterSDKHandler(ctx, mux, conn); err != nil {
logger.WithError(err).Fatal("Could not register grpc-gateway")
}

logger.Info("Starting SDKServer grpc-gateway...")
if err := httpServer.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
logger.WithError(err).Info("http server closed")
} else {
logger.WithError(err).Fatal("Could not serve http server")
}
}
}

// parseEnvFlags parses all the flags and environment variables and returns
// a configuration structure
func parseEnvFlags() config {
viper.SetDefault(localFlag, false)
viper.SetDefault(addressFlag, "localhost")
viper.SetDefault(healthDisabledFlag, false)
viper.SetDefault(healthTimeoutFlag, 5)
viper.SetDefault(healthInitialDelayFlag, 5)
viper.SetDefault(healthFailureThresholdFlag, 3)
pflag.Bool(localFlag, viper.GetBool(localFlag),
"Set this, or LOCAL env, to 'true' to run this binary in local development mode. Defaults to 'false'")
pflag.String(addressFlag, viper.GetString(addressFlag), "The Address to bind the server grpcPort to. Defaults to 'localhost")
pflag.Bool(healthDisabledFlag, viper.GetBool(healthDisabledFlag),
"Set this, or HEALTH_ENABLED env, to 'true' to enable health checking on the GameServer. Defaults to 'true'")
pflag.Int64(healthTimeoutFlag, viper.GetInt64(healthTimeoutFlag),
"Set this or HEALTH_TIMEOUT env to the number of seconds that the health check times out at. Defaults to 5")
pflag.Int64(healthInitialDelayFlag, viper.GetInt64(healthInitialDelayFlag),
"Set this or HEALTH_INITIAL_DELAY env to the number of seconds that the health will wait before starting. Defaults to 5")
pflag.Int64(healthFailureThresholdFlag, viper.GetInt64(healthFailureThresholdFlag),
"Set this or HEALTH_FAILURE_THRESHOLD env to the number of times the health check needs to fail to be deemed unhealthy. Defaults to 3")
pflag.Parse()

viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
runtime.Must(viper.BindEnv(localFlag))
runtime.Must(viper.BindEnv(gameServerNameEnv))
runtime.Must(viper.BindEnv(podNamespaceEnv))
runtime.Must(viper.BindEnv(healthDisabledFlag))
runtime.Must(viper.BindEnv(healthTimeoutFlag))
runtime.Must(viper.BindEnv(healthInitialDelayFlag))
runtime.Must(viper.BindEnv(healthFailureThresholdFlag))
runtime.Must(viper.BindPFlags(pflag.CommandLine))

return config{
IsLocal: viper.GetBool(localFlag),
Address: viper.GetString(addressFlag),
HealthDisabled: viper.GetBool(healthDisabledFlag),
HealthTimeout: time.Duration(viper.GetInt64(healthTimeoutFlag)) * time.Second,
HealthInitialDelay: time.Duration(viper.GetInt64(healthInitialDelayFlag)) * time.Second,
HealthFailureThreshold: viper.GetInt64(healthFailureThresholdFlag),
}
}

// config is all the configuration for this program
type config struct {
Address string
IsLocal bool
HealthDisabled bool
HealthTimeout time.Duration
HealthInitialDelay time.Duration
HealthFailureThreshold int64
}
Loading