K8PetStore: A scalable kubernetes application with automated load generation (#3137).

This commit is contained in:
jayunit100
2015-03-13 13:44:08 -04:00
parent 13253d09e1
commit c552e916da
30 changed files with 3506 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
FROM google/golang:latest
# Add source to gopath. This is defacto required for go apps.
ADD ./src /gopath/src/
ADD ./static /tmp/static
ADD ./test.sh /opt/test.sh
RUN chmod 777 /opt/test.sh
# $GOPATH/[src/a/b/c]
# go build a/b/c
# go run main
# So that we can easily run and install
WORKDIR /gopath/src/
# Install the code (the executables are in the main dir) This will get the deps also.
RUN go get main
#RUN go build main
# Expected that you will override this in production kubernetes.
ENV STATIC_FILES /tmp/static
CMD /gopath/bin/main

Submodule examples/k8petstore/web-server/src/github.com/codegangsta/negroni added at 1dd3ab0ff5

Submodule examples/k8petstore/web-server/src/github.com/garyburd/redigo added at 535138d7bc

Submodule examples/k8petstore/web-server/src/github.com/gorilla/context added at 215affda49

Submodule examples/k8petstore/web-server/src/github.com/gorilla/mux added at 8096f47503

Submodule examples/k8petstore/web-server/src/github.com/xyproto/simpleredis added at 5292687f53

View File

@@ -0,0 +1,207 @@
package main
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//package main
import (
"encoding/json"
"net/http"
"os"
"strings"
"fmt"
"github.com/codegangsta/negroni"
"github.com/gorilla/mux"
"github.com/xyproto/simpleredis"
)
//return the path to static assets (i.e. index.html)
func pathToStaticContents() (string) {
var static_content = os.Getenv("STATIC_FILES");
// Take a wild guess. This will work in dev environment.
if static_content == "" {
println("*********** WARNING: DIDNT FIND ENV VAR 'STATIC_FILES', guessing your running in dev.")
static_content = "../../static/"
} else {
println("=========== Read ENV 'STATIC_FILES', path to assets : " + static_content);
}
//Die if no the static files are missing.
_, err := os.Stat(static_content)
if err != nil {
println("*********** os.Stat failed on " + static_content + " This means no static files are available. Dying...");
os.Exit(2);
}
return static_content;
}
func main() {
var connection = os.Getenv("REDISMASTER_SERVICE_HOST")+":"+os.Getenv("REDISMASTER_SERVICE_PORT");
if connection == ":" {
print("WARNING ::: If in kube, this is a failure: Missing env variable REDISMASTER_SERVICE_HOST");
print("WARNING ::: Attempting to connect redis localhost.")
connection="127.0.0.1:6379";
} else {
print("Found redis master host "+ os.Getenv("REDISMASTER_SERVICE_PORT"));
connection = os.Getenv("REDISMASTER_SERVICE_HOST") + ":" + os.Getenv("REDISMASTER_SERVICE_PORT");
}
println("Now connecting to : " + connection)
/**
* Create a connection pool. ?The pool pointer will otherwise
* not be of any use.?https://gist.github.com/jayunit100/1d00e6d343056401ef00
*/
pool = simpleredis.NewConnectionPoolHost(connection)
println("Connection pool established : " + connection)
defer pool.Close()
r := mux.NewRouter()
println("Router created ")
/**
* Define a REST path.
* - The parameters (key) can be accessed via mux.Vars.
* - The Methods (GET) will be bound to a handler function.
*/
r.Path("/info").Methods("GET").HandlerFunc(InfoHandler)
r.Path("/lrange/{key}").Methods("GET").HandlerFunc(ListRangeHandler)
r.Path("/rpush/{key}/{value}").Methods("GET").HandlerFunc(ListPushHandler)
r.Path("/llen").Methods("GET").HandlerFunc(LLENHandler)
//for dev environment, the site is one level up...
r.PathPrefix("/").Handler(http.FileServer(http.Dir( pathToStaticContents() )))
r.Path("/env").Methods("GET").HandlerFunc(EnvHandler)
list := simpleredis.NewList(pool, "k8petstore")
HandleError(nil, list.Add("jayunit100"))
HandleError(nil, list.Add("tstclaire"))
HandleError(nil, list.Add("rsquared"))
// Verify that this is 3 on startup.
infoL := HandleError(pool.Get(0).Do("LLEN","k8petstore")).(int64)
fmt.Printf("\n=========== Starting DB has %d elements \n", infoL)
if infoL < 3 {
print("Not enough entries in DB. something is wrong w/ redis querying")
print(infoL)
panic("Failed ... ")
}
println("=========== Now launching negroni...this might take a second...")
n := negroni.Classic()
n.UseHandler(r)
n.Run(":3000")
println("Done ! Web app is now running.")
}
/**
* the Pool will be populated on startup,
* it will be an instance of a connection pool.
* Hence, we reference its address rather than copying.
*/
var pool *simpleredis.ConnectionPool
/**
* REST
* input: key
*
* Writes all members to JSON.
*/
func ListRangeHandler(rw http.ResponseWriter, req *http.Request) {
println("ListRangeHandler")
key := mux.Vars(req)["key"]
list := simpleredis.NewList(pool, key)
//members := HandleError(list.GetAll()).([]string)
members := HandleError(list.GetLastN(4)).([]string)
print(members)
membersJSON := HandleError(json.MarshalIndent(members, "", " ")).([]byte)
print("RETURN MEMBERS = "+string(membersJSON))
rw.Write(membersJSON)
}
func LLENHandler(rw http.ResponseWriter, req *http.Request) {
println("=========== LLEN HANDLER")
infoL := HandleError(pool.Get(0).Do("LLEN","k8petstore")).(int64)
fmt.Printf("=========== LLEN is %d ", infoL)
lengthJSON := HandleError(json.MarshalIndent(infoL, "", " ")).([]byte)
fmt.Printf("================ LLEN json is %s", infoL)
print("RETURN LEN = "+string(lengthJSON))
rw.Write(lengthJSON)
}
func ListPushHandler(rw http.ResponseWriter, req *http.Request) {
println("ListPushHandler")
/**
* Expect a key and value as input.
*
*/
key := mux.Vars(req)["key"]
value := mux.Vars(req)["value"]
println("New list " + key + " " + value)
list := simpleredis.NewList(pool, key)
HandleError(nil, list.Add(value))
ListRangeHandler(rw, req)
}
func InfoHandler(rw http.ResponseWriter, req *http.Request) {
println("InfoHandler")
info := HandleError(pool.Get(0).Do("INFO")).([]byte)
rw.Write(info)
}
func EnvHandler(rw http.ResponseWriter, req *http.Request) {
println("EnvHandler")
environment := make(map[string]string)
for _, item := range os.Environ() {
splits := strings.Split(item, "=")
key := splits[0]
val := strings.Join(splits[1:], "=")
environment[key] = val
}
envJSON := HandleError(json.MarshalIndent(environment, "", " ")).([]byte)
rw.Write(envJSON)
}
func HandleError(result interface{}, err error) (r interface{}) {
if err != nil {
print("ERROR : " + err.Error())
//panic(err)
}
return result
}

Binary file not shown.

View File

@@ -0,0 +1,39 @@
//var data = [4, 8, 15, 16, 23, 42];
function defaults(){
Chart.defaults.global.animation = false;
}
function f(data2) {
defaults();
// Get context with jQuery - using jQuery's .get() method.
var ctx = $("#myChart").get(0).getContext("2d");
ctx.width = $(window).width()*1.5;
ctx.width = $(window).height *.5;
// This will get the first returned node in the jQuery collection.
var myNewChart = new Chart(ctx);
var data = {
labels: Array.apply(null, Array(data2.length)).map(function (_, i) {return i;}),
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.2)",
strokeColor: "rgba(220,220,220,1)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: data2
}
]
};
var myLineChart = new Chart(ctx).Line(data);
}

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- d3 is used by histogram.js
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
-->
<script src="https://raw.githubusercontent.com/nnnick/Chart.js/master/Chart.js"></script>
<script src="script.js"></script>
<script src="histogram.js"></script>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta charset="utf-8">
<meta content="width=device-width" name="viewport">
<link href="/style.css" rel="stylesheet">
<title>((( - PRODUCTION -))) Guestbook</title>
</head>
<body>
<TABLE>
<TR>
<TD>
<div id="k8petstore-entries">
<p>Waiting for database connection...This will get overwritten...</p>
</div>
</TD>
<TD>
<div id="header">
<h1>k8-bps.</h1>
</div><br>
<div>
<p><h2 id="k8petstore-host-address"></h2></p>
<p><a href="/env">/env</a>
<a href="/info">/info</a></p>
</div>
</TD>
</TR>
<TR >
<TD colspan="2">
<canvas id="myChart" width="2000" height="600"></canvas>
</TD>
</TR>
</TABLE>
</body>
</html>

View File

@@ -0,0 +1,72 @@
$(document).ready(function() {
var max_trials=1000
var headerTitleElement = $("#header h1");
var entriesElement = $("#k8petstore-entries");
var hostAddressElement = $("#k8petstore-host-address");
var currentEntries = []
var updateEntryCount = function(data, trial) {
if(currentEntries.length > 1000)
currentEntries.splice(0,100);
//console.info("entry count " + data) ;
currentEntries[trial]=data ;
}
var updateEntries = function(data) {
entriesElement.empty();
//console.info("data - > " + Math.random())
//uncommend for debugging...
//entriesElement.append("<br><br> CURRENT TIME : "+ $.now() +"<br><br>TOTAL entries : "+ JSON.stringify(currentEntries)+"<br><br>")
var c1 = currentEntries[currentEntries.length-1]
var c2 = currentEntries[currentEntries.length-2]
entriesElement.append("<br><br> CURRENT TIME : "+ $.now() +"<br><br>TOTAL entries : "+ c1 +"<BR>transaction delta " + (c1-c2) +"<br><br>")
f(currentEntries);
$.each(data, function(key, val) {
//console.info(key + " -> " +val);
entriesElement.append("<p>" + key + " " + val.substr(0,50) + val.substr(100,150) + "</p>");
});
}
// colors = purple, blue, red, green, yellow
var colors = ["#549", "#18d", "#d31", "#2a4", "#db1"];
var randomColor = colors[Math.floor(5 * Math.random())];
(
function setElementsColor(color) {
headerTitleElement.css("color", color);
})
(randomColor);
hostAddressElement.append(document.URL);
// Poll every second.
(function fetchGuestbook() {
// Get JSON by running the query, and append
$.getJSON("lrange/k8petstore").done(updateEntries).always(
function() {
setTimeout(fetchGuestbook, 2000);
});
})();
(function fetchLength(trial) {
$.getJSON("llen").done(
function a(llen1){
updateEntryCount(llen1, trial)
}).always(
function() {
// This function is run every 2 seconds.
setTimeout(
function(){
trial+=1 ;
fetchLength(trial);
f();
}, 5000);
}
)
})(0);
});

View File

@@ -0,0 +1,69 @@
body, input {
color: #123;
font-family: "Gill Sans", sans-serif;
}
div {
overflow: hidden;
padding: 1em 0;
position: relative;
text-align: center;
}
h1, h2, p, input, a {
font-weight: 300;
margin: 0;
}
h1 {
color: #BDB76B;
font-size: 3.5em;
}
h2 {
color: #999;
}
form {
margin: 0 auto;
max-width: 50em;
text-align: center;
}
input {
border: 0;
border-radius: 1000px;
box-shadow: inset 0 0 0 2px #BDB76B;
display: inline;
font-size: 1.5em;
margin-bottom: 1em;
outline: none;
padding: .5em 5%;
width: 55%;
}
form a {
background: #BDB76B;
border: 0;
border-radius: 1000px;
color: #FFF;
font-size: 1.25em;
font-weight: 400;
padding: .75em 2em;
text-decoration: none;
text-transform: uppercase;
white-space: normal;
}
p {
font-size: 1.5em;
line-height: 1.5;
}
.chart div {
font: 10px sans-serif;
background-color: steelblue;
text-align: right;
padding: 3px;
margin: 1px;
color: white;
}

View File

@@ -0,0 +1,9 @@
echo "start test of frontend"
curl localhost:3000/llen
curl localhost:3000/llen
curl localhost:3000/llen
curl localhost:3000/llen
curl localhost:3000/llen
curl localhost:3000/llen
x=`curl localhost:3000/llen`
echo "done testing frontend result = $x"