blog single gear
Tutorials

100-day Challenge #091: Running shooter.io on Cloud Foundry

Translator’s note: This is the 20th article of the series “Cloud Foundry 100-day Challenge selection”. “#091” in the title means that it is 91st (published on November 2, 2015) in the original Japanese series.

Original Author: Hiroaki UKAJI (GitHub)


The 91st topic of “Cloud Foundry 100-Day Challenge” is shooter.io, a multi-player shooter game.
This is an application that runs with coordinated servers, between the shooter-server, written in Go, and an HTTP server shooter-html5.
Because this application uses WebSocket, please be noted that the application may not function properly if your environment is behind a proxy not configured to work with WebSocket.

Basic Information

The summary of procedures is as follows:

  • 1) Deploying shooter-server
    • 1.1) Retrieving Source Code
    • 1.2) Creating Service
    • 1.3) Preparation
    • 1.4) Deploying onto Cloud Foundry
  • 2) Deploying shooter-html5
    • 2.1) Retrieving Source Code
    • 2.2) Preparation
    • 2.3) Deploying onto Cloud Foundry
  • 3) Checking Application Behavior

1. Deploying shooter-server

1.1. Retrieving Source Code

$ git clone https://github.com/xiam/shooter-server
$ cd shooter-server/
shooter-server$ ls
LICENSE  Makefile  README.md  shooter.png  src
shooter-server$ ls src/
agent.go  control.go  entity   fn.go  main.go   player.go   scores.go  ship
bullet    diff        fire.go  item   Makefile  powerup.go  sector.go

This is an application written in Go.
According to the README, the subsequent procedures to start the server up are:

make
cd src
go get -d
make
MONGO_HOST="[MONGODB_HOST]" ./shooter-server -listen [LISTEN_IP]:[LISTEN_PORT]
  • MONGODB_HOST is the IP address where MongoDB is located; the default value is “127.0.0.1”. (reference)
  • LISTEN_IP : LISTEN_PORT are the IP address and port that listen to the HTTP server. The default value is “127.0.0.1:3223”. (reference)

We assume that it is not possible to run make over two directories and simultaneously run go get at the appropriate timing, etc., when using go-buildpack.
Of course this may be achieved if you have sound understandings of the Go language and the specifications of go-buildpack, but we unfortunately could not come up with such a procedure in the time available to complete this article.
However, fortunately, there is an advantage in Go that allows us to run, independent of outside libraries etc., as long as we build one executable binary.
As such, we will leverage this characteristic, and first build Go binary locally, and then run it on Cloud Foundry later.
We will use the binary-buildpack as our buildpack.

1.2. Creating Service

The shooter-server apparently uses MongoDB to store game scores etc., so we will use the MongoDB service that we created in this article.

shooter-server$ cf marketplace
Getting services from marketplace in org ukaji / space default as ukaji...
OK

service      plans                     description   
Mongo DB     Default Mongo Plan*       A simple mongo implementation   
PostgreSQL   Basic PostgreSQL Plan*    PostgreSQL on shared instance.   
p-mysql      100mb, 1gb                MySQL databases on demand   
p-redis      shared-vm, dedicated-vm   Redis service to provide a key-value store

* These service plans have an associated cost. Creating a service instance will incur this cost.

TIP:  Use 'cf marketplace -s SERVICE' to view descriptions of individual plans of a given service.
shooter-server$ cf create-service "Mongo DB" "Default Mongo Plan" shooter-mongo
Creating service instance shooter-mongo in org ukaji / space default as ukaji...
OK

Let’s go ahead to complete the binding procedure.

shooter-server$ cf push shooter-server --no-start
..
shooter-server$ cf bind-service shooter-server shooter-mongo
Binding service shooter-mongo to app shooter-server in org ukaji / space default as ukaji...
OK

1.3. Preparation

We will make modifications to the part that relates to the connection to MongoDB created in section 1.2.
When we take a look at scores.go, we find that this application runs the connection solely based on the database’s host name and database name.

shooter-server$ cat src/scores.go
..
        settings = db.Settings{
                Host:     host,
                Database: defaultDatabase,
        }

        if sess, err = db.Open("mongo", settings); err != nil {
                log.Fatal("db.Open: ", err)
        }
..

However, as is obvious when seeing the uri value within the environment variable VCAP_SERVICES that can be confirmed in cf env, we will not be able to connect under the current configuration, as the MongoDB service created on Cloud Foundry requires username and password.

shooter-server$ cf env shooter-server
{
 "VCAP_SERVICES": {
  "Mongo DB": [
   {
    "credentials": {
     "uri": "mongodb://ad55761d-bd50-40f1-ae0f-875cc1c92e96:[email protected]:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
    },
    "label": "Mongo DB",
    "name": "shooter-mongo",
    "plan": "Default Mongo Plan",
    "tags": [
     "mongodb",
     "document"
    ]
   }
  ]
 }
}

Therefore, this time, we will make modifications based on the design of “Connect to the database if the URI (mongodb://<username>:<password>@<host>:<port>/<database>) is specified in the environment variable MONGO_URI at launch”. Of course, we will make sure not to influence any functions in situations where the standard MONGO_HOST is specified.

shooter-server$ vi src/scores.go
..
import (
        "log"
        "os"
        "strings"
        "time"
        "upper.io/db"
        "upper.io/db/mongo"
)
..
var settings mongo.ConnectionURL
var sess db.Database
var scores db.Collection
..
func init() {
        var err error

        var mongoURI = ""
        host := os.Getenv("MONGO_HOST")
        uri := os.Getenv("MONGO_URI")

        if uri != "" {
                mongoURI = uri
        } else if host != "" {
                mongoURI = host
        } else {
                mongoURI = defaultHost
        }

        parsedURI, _ := mongo.ParseURL(mongoURI)

        settings = mongo.ConnectionURL {
                Address:  parsedURI.Address,
                Database: parsedURI.Database,
                User:     parsedURI.User,
                Password: parsedURI.Password,
        }

        if sess, err = db.Open("mongo", settings); err != nil {
                log.Fatal("db.Open: ", err)
        }

        log.Printf("Connected to mongo://%s.\n", mongoURI)

..
shooter-server$ git diff --no-prefix src/scores.go
diff --git src/scores.go src/scores.go
index 1d5b173..bb6666f 100644
--- src/scores.go
+++ src/scores.go
@@ -20,7 +20,7 @@ import (
        "strings"
        "time"
        "upper.io/db"
-       _ "upper.io/db/mongo"
+       "upper.io/db/mongo"
 )
 
 type mark struct {
@@ -29,7 +29,7 @@ type mark struct {
        Created time.Time `json:"-" bson:"created"`
 }
 
-var settings db.Settings
+var settings mongo.ConnectionURL
 var sess db.Database
 var scores db.Collection
 
@@ -41,22 +41,32 @@ const (
 func init() {
        var err error
 
+       var mongoURI = ""
        host := os.Getenv("MONGO_HOST")
+       uri := os.Getenv("MONGO_URI")
 
-       if host == "" {
-               host = defaultHost
-       }
+        if uri != "" {
+                mongoURI = uri
+        } else if host != "" {
+                mongoURI = host
+        } else {
+                mongoURI = defaultHost
+        }
 
-       settings = db.Settings{
-               Host:     host,
-               Database: defaultDatabase,
-       }
+       parsedURI, _ := mongo.ParseURL(mongoURI)
+
+        settings = mongo.ConnectionURL {
+                Address:  parsedURI.Address,
+                Database: parsedURI.Database,
+                User:     parsedURI.User,
+                Password: parsedURI.Password,
+        }
 
        if sess, err = db.Open("mongo", settings); err != nil {
                log.Fatal("db.Open: ", err)
        }
 
-       log.Printf("Connected to mongo://%s/%s.\n", host, defaultDatabase)
+       log.Printf("Connected to mongo://%s.\n", mongoURI)
 
        scores, err = sess.Collection("scores")
        if err != nil {

We are all set.
Now, if the environment variable MONGO_URI contains a URI that refers to the database information of MongoDB, this information will be used.
Additionally, if only the host name is specified in MONGO_HOST as is standard, this location will be referenced, and will look for localhost if there is no environment variable related to MongoDB; this setting is also still valid.

Building Go Binary

We are using the binary-buildpack this time, so we will go ahead and build Go binary in the local environment.
The procedures can be found in the README.

shooter-server$ go version
go version go1.4 linux/amd64

We are using the version 1.4 of Go.

shooter-server$ make
mkdir -p $GOPATH/src/
ln -sf $PWD/src $GOPATH/src/shooter.io
shooter-server$ cd src/
shooter-server/src$ go get -d
shooter-server/src$ make
go build -o shooter-server

We are all set if a binary file “shooter-server” is generated.

shooter-server/src$ ls shooter-server
shooter-server

Creating Manifest File

This binary file is all we need to deploy this time, so we will create a separate directory containing only the binary and a manifest file, in order to avoid uploading unnecessary files.

go-server$ ls
manifest.yml  shooter-server

We separately prepared an arbitrary directory named “go-server”.
By the way, the content of the manifest file looks like this:

go-server$ vi manifest.yml
---
applications:
- name: shooter-server
  domain: 192.168.15.91.xip.io
  buildpack: binary_buildpack
  memory: 16M
  command: ./shooter-server -listen :$PORT
  env:
    MONGO_URI: "mongodb://ad55761d-bd50-40f1-ae0f-875cc1c92e96:[email protected]:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
  services:
  - shooter-mongo

In command, we are launching the executable binary, with the listening PORT number obtained from the environment variable.
For the environment variable MONGO_URI, we scripted the uri for MongoDB connection found in cf env, as is.
We also set the domain in manifest, because WebSocket connection is allowed for 192.168.15.91.xip.io in the environment used in this post.

1.4. Deploying onto Cloud Foundry

Then we go ahead to deploy it on Cloud Foundry.

go-server$ cf push
..
-----> Uploading droplet (2.2M)

1 of 1 instances running

App started


OK

App shooter-server was started using this command `./shooter-server -listen :$PORT`

Showing health and status for app shooter-server in org ukaji / space default as ukaji...
OK

requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter-server.192.168.15.91.xip.io
last uploaded: Thu Oct 29 09:45:48 UTC 2015
stack: cflinuxfs2
buildpack: binary_buildpack

     state     since                    cpu     memory      disk      details   
#0   running   2015-10-29 06:46:01 PM   28.3%   5M of 16M   0 of 1G 

It looks fine.
The URL produced will be used later.

2. Deploying shooter-html5

2.1. Retrieving Source Code

Next, let’s proceed to deploying shooter-html5, the HTTP server.
We begin by obtaining the source code.

$ git clone https://github.com/xiam/shooter-html5
$ cd shooter-html5/src/
shooter-html5/src$ ls
assets  css  index.html  js

This is an HTML+JavaScript application.

2.2. Preparation

Configuring Connection to shooter-server

We take the URL for shooter-server deployed earlier, and register it as the target for WebSocket connection.

shooter-html5/src$ vi js/main.js
// Websocker server address.
var WEBSOCKET_SERVICE = 'ws://shooter-server.192.168.15.91.xip.io/w/';
..
shooter-html5/src$ git diff --no-prefix js/main.js
diff --git src/js/main.js src/js/main.js
index b114c65..5e4cbbd 100644
--- src/js/main.js
+++ src/js/main.js
@@ -1,5 +1,5 @@
 // Websocker server address.
-var WEBSOCKET_SERVICE = 'ws://shooter.io/w/';
+var WEBSOCKET_SERVICE = 'ws://shooter-server.192.168.15.91.xip.io/w/';
 
 // Frames configuration.
 var FRAMES_PER_SECOND = 24;

When we look at how the files are organized in the source code for this application, we find two symbolic links.

shooter-html5/src$ tree js
js
├── controller.js
├── entity.js
├── fire.js
├── game.js
├── isMobile.js -> isMobile.min.js
├── isMobile.min.js
├── jquery.js -> jquery.min.js
├── jquery.min.js
├── json2.js
├── layer.js
├── license-sm2.txt
├── lifebar.js
├── main.js
├── powerup.js
├── radar.js
├── require.js
├── score.js
├── screen.js
├── ship.js
├── sm2.js
├── sound.js
├── swf
│   └── soundmanager2.swf
├── util.js
└── ws.js

This is fine when we are launching locally, but Cloud Foundry’s CLI is set to ignore symbolic links when pushing.

(Reference)

Although this is a temporary fix, we will remove the symbolic links and replace them with hard links.
It is also OK to copy those files.

shooter-html5/src$ unlink js/isMobile.js
shooter-html5/src$ ln js/isMobile.min.js js/isMobile.js
shooter-html5/src$ unlink js/jquery.js
shooter-html5/src$ ln js/jquery.min.js js/jquery.js

Creating Manifest File

We also create a manifest file here.

shooter-html5/src$ vi manifest.yml
---
applications:
- name: shooter
  domain: 192.168.15.91.xip.io
  buildpack: staticfile_buildpack
  memory: 16M

2.3. Deploying onto Cloud Foundry

Let’s deploy with cf push.

shooter-html5/src$ cf push
..
-----> Uploading droplet (4.3M)

1 of 1 instances running

App started


OK

App shooter was started using this command `sh boot.sh`

Showing health and status for app shooter in org ukaji / space default as ukaji...
OK

requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter.192.168.15.91.xip.io
last uploaded: Thu Oct 29 07:01:49 UTC 2015
stack: cflinuxfs2
buildpack: staticfile_buildpack

     state     since                    cpu    memory        disk      details   
#0   running   2015-10-29 04:01:59 PM   0.0%   5.2M of 16M   0 of 1G  

It looks fine.

3. Checking Application Behavior

Let’s access the produced URL.

We register a name, and the game begins when we push START >>.


The controls are simple:

  • Move with arrow keys
  • Shoot with the SPACE bar

That’s it.
The objective of the game is to shoot down enemies until you have no lives left, and compete for the highest final score.

Extras

This time, we introduced an example where two Cloud Foundry applications work in conjunction.
While we are at it, let us try to deploy both with one cf push.
The procedure is simple; we call two applications with one manifest file.

Let’s place both the shooter-html5 directory and the go-server directory which contains the executable binary of shooter-server under an arbitrary directory.

$ tree -L 3
.
├── go-server
│   ├── manifest.yml
│   └── shooter-server
└── shooter-html5
    ├── LICENSE
    ├── README.md
    └── src
        ├── assets
        ├── css
        ├── index.html
        ├── js
        └── manifest.yml

The necessary manifest file is the following:

$ vi manifest.yml
---
applications:
- name: shooter-server
  domain: 192.168.15.91.xip.io
  path: go-server/
  buildpack: binary_buildpack
  memory: 16M
  command: ./shooter-server -listen :$PORT
  env:
    MONGO_URI: "mongodb://ad55761d-bd50-40f1-ae0f-875cc1c92e96:[email protected]:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
  services:
  - shooter-mongo
- name: shooter
  domain: 192.168.15.91.xip.io
  path: shooter-html5/src/
  buildpack: staticfile_buildpack
  memory: 16M

We use the item path, and specify the directory containing the source code that runs the deployment with a relative path.
We no longer need individual manifest files, so we will delete them.

$ rm go-server/manifest.yml
$ rm shooter-html5/src/manifest.yml

The file structure should look like this:

$ tree -L 3
.
├── go-server
│   └── shooter-server
├── manifest.yml
└── shooter-html5
    ├── LICENSE
    ├── README.md
    └── src
        ├── assets
        ├── css
        ├── index.html
        └── js

We deploy with the above settings.

$ cf push
..
-----> Uploading droplet (2.2M)

1 of 1 instances running

App started


OK

App shooter-server was started using this command `./shooter-server -listen :$PORT`

Showing health and status for app shooter-server in org ukaji / space default as ukaji...
OK

requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter-server.192.168.15.91.xip.io
last uploaded: Fri Oct 30 02:28:48 UTC 2015
stack: cflinuxfs2
buildpack: binary_buildpack

     state     since                    cpu     memory        disk      details   
#0   running   2015-10-30 11:29:01 AM   19.3%   4.6M of 16M   0 of 1G      
..
-----> Uploading droplet (4.3M)

1 of 1 instances running

App started


OK

App shooter was started using this command `sh boot.sh`

Showing health and status for app shooter in org ukaji / space default as ukaji...
OK

requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter.192.168.15.91.xip.io
last uploaded: Fri Oct 30 02:29:23 UTC 2015
stack: cflinuxfs2
buildpack: staticfile_buildpack

     state     since                    cpu    memory        disk      details   
#0   running   2015-10-30 11:29:35 AM   0.0%   5.2M of 16M   0 of 1G

Now, two deployments are being run with one cf push command.

Software Used in This Post