From 8d2e836d8287c7060cec4b689bbb643bc21cce04 Mon Sep 17 00:00:00 2001 From: chyeh Date: Fri, 2 Jun 2017 14:29:21 +0000 Subject: [PATCH 1/9] [#1] Add scripts for testing --- benchmark-agent/test/create-example.sh | 12 ++++++++++++ benchmark-agent/test/delete-example.sh | 3 +++ benchmark-agent/test/update-example.sh | 7 +++++++ 3 files changed, 22 insertions(+) create mode 100755 benchmark-agent/test/create-example.sh create mode 100755 benchmark-agent/test/delete-example.sh create mode 100755 benchmark-agent/test/update-example.sh diff --git a/benchmark-agent/test/create-example.sh b/benchmark-agent/test/create-example.sh new file mode 100755 index 0000000..a4cc296 --- /dev/null +++ b/benchmark-agent/test/create-example.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +curl -X POST -H "Content-Type: application/json" \ +-d "{ \ + \"name\": \"busycpu\", \ + \"count\": 4, \ + \"resources\": { \ + \"cpushares\": 512 + }, \ + \"image\": \"hyperpilot\/busycpu\" \ + }" \ +http://localhost:7778/benchmarks diff --git a/benchmark-agent/test/delete-example.sh b/benchmark-agent/test/delete-example.sh new file mode 100755 index 0000000..f21c697 --- /dev/null +++ b/benchmark-agent/test/delete-example.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +curl -X DELETE http://localhost:7778/benchmarks/busycpu diff --git a/benchmark-agent/test/update-example.sh b/benchmark-agent/test/update-example.sh new file mode 100755 index 0000000..b56a949 --- /dev/null +++ b/benchmark-agent/test/update-example.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl -X PUT -H "Content-Type: application/json" \ +-d "{ \ + \"cpushares\": 256 \ + }" \ +http://localhost:7778/benchmarks/busycpu/resources From 9f41f7195f77620c39950d40dd79bcfba957b94e Mon Sep 17 00:00:00 2001 From: chyeh Date: Sat, 3 Jun 2017 17:57:36 +0800 Subject: [PATCH 2/9] Update `.gitignore` --- benchmark-agent/.gitignore | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/benchmark-agent/.gitignore b/benchmark-agent/.gitignore index 5a35498..183c91c 100644 --- a/benchmark-agent/.gitignore +++ b/benchmark-agent/.gitignore @@ -1 +1,17 @@ -benchmark-agent \ No newline at end of file +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +# Others +benchmark-agent From e2b20162e80f69290dda08ce1c628092df51a300 Mon Sep 17 00:00:00 2001 From: chyeh Date: Sat, 3 Jun 2017 19:03:50 +0800 Subject: [PATCH 3/9] [#1] Use `main.go` --- benchmark-agent/agent.go | 20 -------------------- benchmark-agent/main.go | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 benchmark-agent/main.go diff --git a/benchmark-agent/agent.go b/benchmark-agent/agent.go index b651fe0..05320b0 100644 --- a/benchmark-agent/agent.go +++ b/benchmark-agent/agent.go @@ -244,23 +244,3 @@ func (server *Server) Run() error { return router.Run(":" + server.Port) } - -func main() { - endpoint := "unix:///var/run/docker.sock" - client, err := docker.NewClient(endpoint) - if err != nil { - panic(err) - } - - err = client.Ping() - if err != nil { - glog.Error("Unable to ping docker daemon") - panic(err) - } - - server := NewServer(client, "7778") - err = server.Run() - if err != nil { - panic(err) - } -} diff --git a/benchmark-agent/main.go b/benchmark-agent/main.go new file mode 100644 index 0000000..6822652 --- /dev/null +++ b/benchmark-agent/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/fsouza/go-dockerclient" + "github.com/golang/glog" +) + +func main() { + endpoint := "unix:///var/run/docker.sock" + client, err := docker.NewClient(endpoint) + if err != nil { + panic(err) + } + + err = client.Ping() + if err != nil { + glog.Error("Unable to ping docker daemon") + panic(err) + } + + server := NewServer(client, "7778") + err = server.Run() + if err != nil { + panic(err) + } +} From 266195a183f59c4c585ecff01a6a823f3519bc67 Mon Sep 17 00:00:00 2001 From: chyeh Date: Sat, 3 Jun 2017 19:12:14 +0800 Subject: [PATCH 4/9] [#1] Code refinement --- benchmark-agent/docker.go | 24 +++++++++++++++++ benchmark-agent/{agent.go => gin.go} | 40 +++++++++++++++++----------- benchmark-agent/main.go | 24 ++--------------- 3 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 benchmark-agent/docker.go rename benchmark-agent/{agent.go => gin.go} (97%) diff --git a/benchmark-agent/docker.go b/benchmark-agent/docker.go new file mode 100644 index 0000000..89e958a --- /dev/null +++ b/benchmark-agent/docker.go @@ -0,0 +1,24 @@ +package main + +import ( + docker "github.com/fsouza/go-dockerclient" + "github.com/golang/glog" +) + +var dockerClient *docker.Client + +func initDocker() { + endpoint := "unix:///var/run/docker.sock" + c, err := docker.NewClient(endpoint) + if err != nil { + panic(err) + } + + err = c.Ping() + if err != nil { + glog.Error("Unable to ping docker daemon") + panic(err) + } + + dockerClient = c +} diff --git a/benchmark-agent/agent.go b/benchmark-agent/gin.go similarity index 97% rename from benchmark-agent/agent.go rename to benchmark-agent/gin.go index 05320b0..2902d05 100644 --- a/benchmark-agent/agent.go +++ b/benchmark-agent/gin.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/fsouza/go-dockerclient" + docker "github.com/fsouza/go-dockerclient" "github.com/gin-gonic/gin" "github.com/golang/glog" "github.com/hyperpilotio/container-benchmarks/benchmark-agent/apis" @@ -33,6 +33,24 @@ func NewServer(client *docker.Client, port string) *Server { } } +func (server *Server) Run() error { + //gin.SetMode("release") + router := gin.New() + + // Global middleware + router.Use(gin.Logger()) + router.Use(gin.Recovery()) + + benchmarkGroup := router.Group("/benchmarks") + { + benchmarkGroup.POST("", server.createBenchmark) + benchmarkGroup.DELETE("/:benchmark", server.deleteBenchmark) + benchmarkGroup.PUT("/:benchmark/resources", server.updateResources) + } + + return router.Run(":" + server.Port) +} + func (server *Server) removeContainers(prefix string) { } @@ -227,20 +245,10 @@ func (server *Server) updateResources(c *gin.Context) { }) } -func (server *Server) Run() error { - //gin.SetMode("release") - router := gin.New() - - // Global middleware - router.Use(gin.Logger()) - router.Use(gin.Recovery()) - - benchmarkGroup := router.Group("/benchmarks") - { - benchmarkGroup.POST("", server.createBenchmark) - benchmarkGroup.DELETE("/:benchmark", server.deleteBenchmark) - benchmarkGroup.PUT("/:benchmark/resources", server.updateResources) +func initGin() { + server := NewServer(dockerClient, "7778") + err := server.Run() + if err != nil { + panic(err) } - - return router.Run(":" + server.Port) } diff --git a/benchmark-agent/main.go b/benchmark-agent/main.go index 6822652..dbd823c 100644 --- a/benchmark-agent/main.go +++ b/benchmark-agent/main.go @@ -1,26 +1,6 @@ package main -import ( - "github.com/fsouza/go-dockerclient" - "github.com/golang/glog" -) - func main() { - endpoint := "unix:///var/run/docker.sock" - client, err := docker.NewClient(endpoint) - if err != nil { - panic(err) - } - - err = client.Ping() - if err != nil { - glog.Error("Unable to ping docker daemon") - panic(err) - } - - server := NewServer(client, "7778") - err = server.Run() - if err != nil { - panic(err) - } + initDocker() + initGin() } From f05b98c0991203578642f6f5ea1f9d652ac82592 Mon Sep 17 00:00:00 2001 From: chyeh Date: Sat, 3 Jun 2017 19:53:58 +0800 Subject: [PATCH 5/9] [#1] Use pointer type for the field in `Benchmark` --- benchmark-agent/apis/models.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmark-agent/apis/models.go b/benchmark-agent/apis/models.go index 871bf9c..25dd942 100644 --- a/benchmark-agent/apis/models.go +++ b/benchmark-agent/apis/models.go @@ -6,9 +6,9 @@ type Resources struct { } type Benchmark struct { - Name string `json:"name" binding:"required"` - Count int `json:"count" binding:"required"` - Resources Resources `json:"resources"` - Image string `json:"image" binding:"required"` - Command []string `json:"command"` + Name string `json:"name" binding:"required"` + Count int `json:"count" binding:"required"` + Resources *Resources `json:"resources"` + Image string `json:"image" binding:"required"` + Command []string `json:"command"` } From 2ccdbecbfcdf378ff39ff034dce5061f38d92dcd Mon Sep 17 00:00:00 2001 From: chyeh Date: Sat, 3 Jun 2017 21:59:20 +0800 Subject: [PATCH 6/9] [#1] Integration test by `Ginkgo` --- benchmark-agent/create-example.json | 8 -- benchmark-agent/it_test.go | 112 +++++++++++++++++++ benchmark-agent/test/create-example.sh | 12 -- benchmark-agent/test/delete-example.sh | 3 - benchmark-agent/test/http/http.go | 146 +++++++++++++++++++++++++ benchmark-agent/test/update-example.sh | 7 -- benchmark-agent/update-example.json | 3 - 7 files changed, 258 insertions(+), 33 deletions(-) delete mode 100644 benchmark-agent/create-example.json create mode 100644 benchmark-agent/it_test.go delete mode 100755 benchmark-agent/test/create-example.sh delete mode 100755 benchmark-agent/test/delete-example.sh create mode 100644 benchmark-agent/test/http/http.go delete mode 100755 benchmark-agent/test/update-example.sh delete mode 100644 benchmark-agent/update-example.json diff --git a/benchmark-agent/create-example.json b/benchmark-agent/create-example.json deleted file mode 100644 index 818db22..0000000 --- a/benchmark-agent/create-example.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "busycpu", - "count": 4, - "resources": { - "cpushares": 512 - }, - "image": "hyperpilot/busycpu" -} diff --git a/benchmark-agent/it_test.go b/benchmark-agent/it_test.go new file mode 100644 index 0000000..d3bb5fb --- /dev/null +++ b/benchmark-agent/it_test.go @@ -0,0 +1,112 @@ +package main + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/hyperpilotio/container-benchmarks/benchmark-agent/apis" + testHttp "github.com/hyperpilotio/container-benchmarks/benchmark-agent/test/http" +) + +func TestByGinkgo(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Base Suite") +} + +var httpClientConfig = testHttp.NewClientConfig() + +var _ = Describe("Test creating benchmarks", func() { + AfterEach(func() { + testHttp.Response( + httpClientConfig.NewSling(). + Delete("benchmarks/busycpu"), + ) + }) + + It("creates benchmarks", func() { + req := &apis.Benchmark{ + Name: "busycpu", + Count: 4, + Resources: &apis.Resources{ + CPUShares: 512, + }, + Image: "hyperpilot/busycpu", + } + resp := testHttp.Response( + httpClientConfig.NewSling(). + Post("benchmarks"). + BodyJSON(req), + ) + + jsonBody := resp.JSONBody() + Expect(jsonBody.Get("error").MustBool()).To(BeFalse()) + }) +}) + +var _ = Describe("Test updating benchmarks", func() { + BeforeEach(func() { + req := &apis.Benchmark{ + Name: "busycpu", + Count: 4, + Resources: &apis.Resources{ + CPUShares: 512, + }, + Image: "hyperpilot/busycpu", + } + testHttp.Response( + httpClientConfig.NewSling(). + Post("benchmarks"). + BodyJSON(req), + ) + }) + + AfterEach(func() { + testHttp.Response( + httpClientConfig.NewSling(). + Delete("benchmarks/busycpu"), + ) + }) + + It("updates benchmarks", func() { + req := &apis.Resources{ + CPUShares: 256, + } + resp := testHttp.Response( + httpClientConfig.NewSling(). + Post("benchmarks/busycpu/resources"). + BodyJSON(req), + ) + + jsonBody := resp.JSONBody() + Expect(jsonBody.Get("error").MustBool()).To(BeFalse()) + }) +}) + +var _ = Describe("Test deleting benchmarks", func() { + BeforeEach(func() { + req := &apis.Benchmark{ + Name: "busycpu", + Count: 4, + Resources: &apis.Resources{ + CPUShares: 512, + }, + Image: "hyperpilot/busycpu", + } + testHttp.Response( + httpClientConfig.NewSling(). + Post("benchmarks"). + BodyJSON(req), + ) + }) + + It("deletes benchmarks", func() { + resp := testHttp.Response( + httpClientConfig.NewSling(). + Delete("benchmarks/busycpu"), + ) + jsonBody := resp.JSONBody() + Expect(jsonBody.Get("error").MustBool()).To(BeFalse()) + }) +}) diff --git a/benchmark-agent/test/create-example.sh b/benchmark-agent/test/create-example.sh deleted file mode 100755 index a4cc296..0000000 --- a/benchmark-agent/test/create-example.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -curl -X POST -H "Content-Type: application/json" \ --d "{ \ - \"name\": \"busycpu\", \ - \"count\": 4, \ - \"resources\": { \ - \"cpushares\": 512 - }, \ - \"image\": \"hyperpilot\/busycpu\" \ - }" \ -http://localhost:7778/benchmarks diff --git a/benchmark-agent/test/delete-example.sh b/benchmark-agent/test/delete-example.sh deleted file mode 100755 index f21c697..0000000 --- a/benchmark-agent/test/delete-example.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -curl -X DELETE http://localhost:7778/benchmarks/busycpu diff --git a/benchmark-agent/test/http/http.go b/benchmark-agent/test/http/http.go new file mode 100644 index 0000000..9544a09 --- /dev/null +++ b/benchmark-agent/test/http/http.go @@ -0,0 +1,146 @@ +package http + +import ( + "flag" + "fmt" + "io/ioutil" + "net/http" + + log "github.com/Sirupsen/logrus" + simplejson "github.com/bitly/go-simplejson" + "github.com/dghubble/sling" +) + +// Response sends the HTTP request and reads the body of the HTTP response +func Response(s *sling.Sling) *ResponseExt { + /** + * Set up the HTTP request + */ + req, err := s.Request() + if err != nil { + panic(err) + } + // :~) + + return responseExtByRequest(req) +} + +func responseExtByRequest(req *http.Request) *ResponseExt { + /** + * Send the HTTP request + */ + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + // :~) + + return responseExtByResponse(resp) +} + +func responseExtByResponse(resp *http.Response) *ResponseExt { + /** + * Read the body of the HTTP response + */ + defer resp.Body.Close() + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + panic(err) + } + // :~) + + return &ResponseExt{ + Response: resp, + body: bodyBytes, + } +} + +// ResponseExt is an extended type of `http.Response` +type ResponseExt struct { + Response *http.Response + body []byte +} + +// JSONBody returns the body of the HTTP response in JSON format. It panics if +// any error occurs during the parsing. +func (r *ResponseExt) JSONBody() *simplejson.Json { + jsonResult, err := simplejson.NewJson(r.body) + if err != nil { + panic(err) + } + + return jsonResult +} + +// ClientConfig is the configuration of the http client +type ClientConfig struct { + Ssl bool // host of the http service + Host string // port of the http service + Port uint16 // enable SSL for the http service + Resource string // specify the resource, i.e. `http://:/` + + slingBase *sling.Sling +} + +// NewClientConfig returns a new ClientConfig. The values of the fields +// can be set by the command line: +// Ssl: by `-http.host` +// Host: by `-http.port` +// Port: by `-http.ssl` +// Resource: by `-http.resource` +func NewClientConfig() *ClientConfig { + return newClientConfigByFlag() +} + +func newClientConfigByFlag() *ClientConfig { + var host = flag.String("http.host", "127.0.0.1", "Host of the tested HTTP service") + var port = flag.Int("http.port", 7778, "Port of the tested HTTP service") + var ssl = flag.Bool("http.ssl", false, "Enable SSL for the tested HTTP service") + var resource = flag.String("http.resource", "", "specify the resource, i.e. 'http://:/'") + + flag.Parse() + + config := &ClientConfig{ + Host: *host, + Port: uint16(*port), + Ssl: *ssl, + Resource: *resource, + } + config.slingBase = sling.New().Base( + config.hostAndPort(), + ) + + if config.Resource != "" { + config.slingBase.Path(config.Resource + "/") + } + + log.Infof("Sling URL for testing: %s", config.String()) + + return config +} + +func (c *ClientConfig) hostAndPort() string { + schema := "http" + if c.Ssl { + schema = "https" + } + + return fmt.Sprintf("%s://%s:%d", schema, c.Host, c.Port) +} + +// String gets the URL string the client accesses to +func (c *ClientConfig) String() string { + url := c.hostAndPort() + + if c.Resource != "" { + url += "/" + c.Resource + } + + return url +} + +// NewSling returns a new `Sling` variable based on the `ClientConfig`. +func (c *ClientConfig) NewSling() *sling.Sling { + return c.slingBase.New() +} diff --git a/benchmark-agent/test/update-example.sh b/benchmark-agent/test/update-example.sh deleted file mode 100755 index b56a949..0000000 --- a/benchmark-agent/test/update-example.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -curl -X PUT -H "Content-Type: application/json" \ --d "{ \ - \"cpushares\": 256 \ - }" \ -http://localhost:7778/benchmarks/busycpu/resources diff --git a/benchmark-agent/update-example.json b/benchmark-agent/update-example.json deleted file mode 100644 index ed4a331..0000000 --- a/benchmark-agent/update-example.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cpushares": 256 -} From 6aa41c404cbbc0cdf5e9691b60739ed8f0083905 Mon Sep 17 00:00:00 2001 From: chyeh Date: Sat, 3 Jun 2017 22:52:42 +0800 Subject: [PATCH 7/9] [#1] Rename package: `apis` to `model` --- benchmark-agent/gin.go | 10 +++++----- benchmark-agent/it_test.go | 16 ++++++++-------- .../{apis/models.go => model/model.go} | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) rename benchmark-agent/{apis/models.go => model/model.go} (96%) diff --git a/benchmark-agent/gin.go b/benchmark-agent/gin.go index 2902d05..bfaf9a5 100644 --- a/benchmark-agent/gin.go +++ b/benchmark-agent/gin.go @@ -9,7 +9,7 @@ import ( docker "github.com/fsouza/go-dockerclient" "github.com/gin-gonic/gin" "github.com/golang/glog" - "github.com/hyperpilotio/container-benchmarks/benchmark-agent/apis" + "github.com/hyperpilotio/container-benchmarks/benchmark-agent/model" ) type Server struct { @@ -20,7 +20,7 @@ type Server struct { } type DeployedBenchmark struct { - Benchmark *apis.Benchmark + Benchmark *model.Benchmark NameToId map[string]string } @@ -55,7 +55,7 @@ func (server *Server) removeContainers(prefix string) { } -func (server *Server) deployBenchmark(benchmark *apis.Benchmark) (*DeployedBenchmark, error) { +func (server *Server) deployBenchmark(benchmark *model.Benchmark) (*DeployedBenchmark, error) { hostConfig := &docker.HostConfig{ PublishAllPorts: true, } @@ -126,7 +126,7 @@ func (server *Server) deployBenchmark(benchmark *apis.Benchmark) (*DeployedBench } func (server *Server) createBenchmark(c *gin.Context) { - var benchmark apis.Benchmark + var benchmark model.Benchmark if err := c.BindJSON(&benchmark); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": true, @@ -208,7 +208,7 @@ func (server *Server) updateResources(c *gin.Context) { return } - var resources apis.Resources + var resources model.Resources if err := c.BindJSON(&resources); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": true, diff --git a/benchmark-agent/it_test.go b/benchmark-agent/it_test.go index d3bb5fb..c4b3aa6 100644 --- a/benchmark-agent/it_test.go +++ b/benchmark-agent/it_test.go @@ -6,7 +6,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/hyperpilotio/container-benchmarks/benchmark-agent/apis" + "github.com/hyperpilotio/container-benchmarks/benchmark-agent/model" testHttp "github.com/hyperpilotio/container-benchmarks/benchmark-agent/test/http" ) @@ -26,10 +26,10 @@ var _ = Describe("Test creating benchmarks", func() { }) It("creates benchmarks", func() { - req := &apis.Benchmark{ + req := &model.Benchmark{ Name: "busycpu", Count: 4, - Resources: &apis.Resources{ + Resources: &model.Resources{ CPUShares: 512, }, Image: "hyperpilot/busycpu", @@ -47,10 +47,10 @@ var _ = Describe("Test creating benchmarks", func() { var _ = Describe("Test updating benchmarks", func() { BeforeEach(func() { - req := &apis.Benchmark{ + req := &model.Benchmark{ Name: "busycpu", Count: 4, - Resources: &apis.Resources{ + Resources: &model.Resources{ CPUShares: 512, }, Image: "hyperpilot/busycpu", @@ -70,7 +70,7 @@ var _ = Describe("Test updating benchmarks", func() { }) It("updates benchmarks", func() { - req := &apis.Resources{ + req := &model.Resources{ CPUShares: 256, } resp := testHttp.Response( @@ -86,10 +86,10 @@ var _ = Describe("Test updating benchmarks", func() { var _ = Describe("Test deleting benchmarks", func() { BeforeEach(func() { - req := &apis.Benchmark{ + req := &model.Benchmark{ Name: "busycpu", Count: 4, - Resources: &apis.Resources{ + Resources: &model.Resources{ CPUShares: 512, }, Image: "hyperpilot/busycpu", diff --git a/benchmark-agent/apis/models.go b/benchmark-agent/model/model.go similarity index 96% rename from benchmark-agent/apis/models.go rename to benchmark-agent/model/model.go index 25dd942..aceb3a0 100644 --- a/benchmark-agent/apis/models.go +++ b/benchmark-agent/model/model.go @@ -1,4 +1,4 @@ -package apis +package model type Resources struct { CPUShares int64 `json:"cpushares"` From 248485bc85c947fde4ddbfab23553be6d83447d1 Mon Sep 17 00:00:00 2001 From: chyeh Date: Tue, 6 Jun 2017 23:12:33 +0800 Subject: [PATCH 8/9] [#1] Code refactory In this commit, I refactored the original RESTful APIs. I made the docker client code a package so for the API layer it's an abstraction now. I also reduced the critical section, which is only for the priave field `Client.benchmarks` now. --- benchmark-agent/api.go | 140 +++++++++++++++++ benchmark-agent/docker.go | 17 +-- benchmark-agent/docker/docker.go | 168 +++++++++++++++++++++ benchmark-agent/gin.go | 252 ++----------------------------- benchmark-agent/main.go | 2 +- 5 files changed, 326 insertions(+), 253 deletions(-) create mode 100644 benchmark-agent/api.go create mode 100644 benchmark-agent/docker/docker.go diff --git a/benchmark-agent/api.go b/benchmark-agent/api.go new file mode 100644 index 0000000..58c19b7 --- /dev/null +++ b/benchmark-agent/api.go @@ -0,0 +1,140 @@ +package main + +import ( + "fmt" + "net/http" + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/hyperpilotio/container-benchmarks/benchmark-agent/model" +) + +var corsConfig cors.Config + +func init() { + headers := []string{ + "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "Cache-Control", "X-Requested-With", + "accept", "origin", "Apitoken", + "page-size", "page-pos", "order-by", "page-ptr", "total-count", "page-more", "previous-page", "next-page", + } + + corsConfig = cors.Config{ + AllowMethods: []string{"POST", "OPTIONS", "GET", "PUT", "DELETE", "UPDATE"}, + AllowHeaders: headers, + ExposeHeaders: headers, + AllowCredentials: true, + MaxAge: 12 * time.Hour, + } + corsConfig.AllowAllOrigins = true +} + +type httpServConfig struct { + Mode string + Host string + Port uint16 +} + +func (c *httpServConfig) String() string { + return fmt.Sprintf("%s:%d", c.Host, c.Port) +} + +func initHTTPServ() { + initGin(&httpServConfig{ + Mode: "debug", + Host: "0.0.0.0", + Port: 7778, + }) +} + +func setAPIRoutes(router *gin.Engine) { + router.POST("benchmarks", createBenchmark) + router.DELETE("benchmarks/:benchmark", deleteBenchmark) + router.PUT("benchmarks/:benchmark/resources", updateResources) +} + +func createBenchmark(c *gin.Context) { + var benchmark model.Benchmark + if err := c.BindJSON(&benchmark); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": true, + "data": "Error deserializing benchmark: " + string(err.Error()), + }) + return + } + + if dockerClient.IsCreated(benchmark.Name) { + c.JSON(http.StatusBadRequest, gin.H{ + "error": true, + "data": "Benchmark already created. Please delete benchmark before creating", + }) + return + } + + if err := dockerClient.DeployBenchmark(&benchmark); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": true, + "data": "Failed to deploy benchmark: " + err.Error(), + }) + return + } + + c.JSON(http.StatusAccepted, gin.H{ + "error": false, + }) +} + +func deleteBenchmark(c *gin.Context) { + name := c.Param("benchmark") + depBenchmark := dockerClient.DeployedBenchmark(name) + if depBenchmark == nil { + c.JSON(http.StatusNotFound, gin.H{ + "error": false, + }) + return + } + + if err := dockerClient.RemoveDeployedBenchmark(depBenchmark); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": true, + "data": "Unable to remove container: " + err.Error(), + }) + return + } + + c.JSON(http.StatusAccepted, gin.H{ + "error": false, + }) +} + +func updateResources(c *gin.Context) { + name := c.Param("benchmark") + resources := &model.Resources{} + if err := c.BindJSON(resources); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": true, + "data": "Unable to deserialize resources: " + err.Error(), + }) + return + } + + depBenchmark := dockerClient.DeployedBenchmark(name) + if depBenchmark == nil { + c.JSON(http.StatusNotFound, gin.H{ + "error": false, + }) + return + } + + if err := dockerClient.UpdateResources(depBenchmark, resources); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": true, + "data": "Unable to update resources: " + err.Error(), + }) + return + } + + c.JSON(http.StatusAccepted, gin.H{ + "error": false, + }) +} diff --git a/benchmark-agent/docker.go b/benchmark-agent/docker.go index 89e958a..adecfb7 100644 --- a/benchmark-agent/docker.go +++ b/benchmark-agent/docker.go @@ -1,24 +1,11 @@ package main import ( - docker "github.com/fsouza/go-dockerclient" - "github.com/golang/glog" + "github.com/hyperpilotio/container-benchmarks/benchmark-agent/docker" ) var dockerClient *docker.Client func initDocker() { - endpoint := "unix:///var/run/docker.sock" - c, err := docker.NewClient(endpoint) - if err != nil { - panic(err) - } - - err = c.Ping() - if err != nil { - glog.Error("Unable to ping docker daemon") - panic(err) - } - - dockerClient = c + dockerClient = docker.NewClient() } diff --git a/benchmark-agent/docker/docker.go b/benchmark-agent/docker/docker.go new file mode 100644 index 0000000..efd2975 --- /dev/null +++ b/benchmark-agent/docker/docker.go @@ -0,0 +1,168 @@ +package docker + +import ( + "strconv" + "strings" + "sync" + + logger "github.com/Sirupsen/logrus" + docker "github.com/fsouza/go-dockerclient" + "github.com/hyperpilotio/container-benchmarks/benchmark-agent/model" +) + +type Client struct { + c *docker.Client + benchmarks map[string]*DeployedBenchmark + mutex sync.RWMutex +} + +type DeployedBenchmark struct { + benchmark *model.Benchmark + nameToID map[string]string +} + +func NewClient() *Client { + endpoint := "unix:///var/run/docker.sock" + c, err := docker.NewClient(endpoint) + if err != nil { + panic(err) + } + + err = c.Ping() + if err != nil { + logger.Errorln("Unable to ping docker daemon") + panic(err) + } + + return &Client{c: c, benchmarks: make(map[string]*DeployedBenchmark)} +} + +func (client *Client) IsCreated(name string) bool { + client.mutex.RLock() + defer client.mutex.RUnlock() + if _, ok := client.benchmarks[name]; ok { + return true + } + return false +} + +func (client *Client) DeployedBenchmark(name string) *DeployedBenchmark { + client.mutex.RLock() + defer client.mutex.RUnlock() + if v, ok := client.benchmarks[name]; ok { + return v + } + return nil +} + +func (client *Client) DeployBenchmark(benchmark *model.Benchmark) error { + hostConfig := &docker.HostConfig{ + PublishAllPorts: true, + } + + deployed := &DeployedBenchmark{ + benchmark: benchmark, + nameToID: make(map[string]string), + } + + logger.Infof("Deploying benchmark: %+v", benchmark) + + parts := strings.Split(benchmark.Image, ":") + tag := "latest" + if len(parts) > 1 { + tag = parts[1] + } + + if err := client.c.PullImage(docker.PullImageOptions{ + Repository: parts[0], + Tag: tag, + }, + docker.AuthConfiguration{}, + ); err != nil { + return err + } + + for i := 1; i <= benchmark.Count; i++ { + config := &docker.Config{ + Image: benchmark.Image, + } + + if benchmark.Command != nil { + config.Cmd = benchmark.Command + } + + if benchmark.Resources.CPUShares > 0 { + config.CPUShares = benchmark.Resources.CPUShares + } + + if benchmark.Resources.Memory > 0 { + config.Memory = benchmark.Resources.Memory + } + + containerName := benchmark.Name + strconv.Itoa(i) + container, err := client.c.CreateContainer(docker.CreateContainerOptions{ + Name: containerName, + Config: config, + HostConfig: hostConfig, + }) + if err != nil { + // Clean up + client.removeContainers(benchmark.Name) + return err + } + + deployed.nameToID[containerName] = container.ID + + err = client.c.StartContainer(container.ID, hostConfig) + if err != nil { + // Clean up + client.removeContainers(benchmark.Name) + return err + } + } + + client.mutex.Lock() + defer client.mutex.Unlock() + client.benchmarks[benchmark.Name] = deployed + return nil +} + +func (client *Client) removeContainers(prefix string) { + +} + +func (client *Client) RemoveDeployedBenchmark(b *DeployedBenchmark) error { + for _, id := range b.nameToID { + if err := client.c.RemoveContainer(docker.RemoveContainerOptions{ + ID: id, + Force: true, + RemoveVolumes: true, + }); err != nil { + return err + } + } + client.mutex.Lock() + defer client.mutex.Unlock() + delete(client.benchmarks, b.benchmark.Name) + return nil +} + +func (client *Client) UpdateResources(b *DeployedBenchmark, r *model.Resources) error { + updateOptions := docker.UpdateContainerOptions{} + if r.CPUShares > 0 { + updateOptions.CPUShares = int(r.CPUShares) + } + + if r.Memory > 0 { + updateOptions.Memory = int(r.Memory) + } + + logger.Infoln("Updating resources for benchmark", b.benchmark.Name) + for _, id := range b.nameToID { + logger.Infoln("Updating container ID %s, %+v", id, updateOptions) + if err := client.c.UpdateContainer(id, updateOptions); err != nil { + return err + } + } + return nil +} diff --git a/benchmark-agent/gin.go b/benchmark-agent/gin.go index bfaf9a5..65d1d15 100644 --- a/benchmark-agent/gin.go +++ b/benchmark-agent/gin.go @@ -1,254 +1,32 @@ package main import ( - "net/http" - "strconv" - "strings" - "sync" - - docker "github.com/fsouza/go-dockerclient" + logger "github.com/Sirupsen/logrus" + "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" - "github.com/golang/glog" - "github.com/hyperpilotio/container-benchmarks/benchmark-agent/model" ) -type Server struct { - Port string - Benchmarks map[string]*DeployedBenchmark - mutex *sync.Mutex - dockerClient *docker.Client -} - -type DeployedBenchmark struct { - Benchmark *model.Benchmark - NameToId map[string]string +func initGin(c *httpServConfig) { + router := NewDefaultJsonEngine(c) + logger.Infof("Going to start web service. Listen: %s", c) + mustStartAPIService(router, c) } -func NewServer(client *docker.Client, port string) *Server { - return &Server{ - dockerClient: client, - Port: port, - mutex: &sync.Mutex{}, - Benchmarks: make(map[string]*DeployedBenchmark), +func mustStartAPIService(router *gin.Engine, c *httpServConfig) { + setAPIRoutes(router) + if err := router.Run(c.String()); err != nil { + logger.Panicf("Cannot start web service: %v", err) } } -func (server *Server) Run() error { - //gin.SetMode("release") +func NewDefaultJsonEngine(c *httpServConfig) *gin.Engine { + gin.SetMode(c.Mode) + router := gin.New() - // Global middleware + router.Use(cors.New(corsConfig)) router.Use(gin.Logger()) router.Use(gin.Recovery()) - benchmarkGroup := router.Group("/benchmarks") - { - benchmarkGroup.POST("", server.createBenchmark) - benchmarkGroup.DELETE("/:benchmark", server.deleteBenchmark) - benchmarkGroup.PUT("/:benchmark/resources", server.updateResources) - } - - return router.Run(":" + server.Port) -} - -func (server *Server) removeContainers(prefix string) { - -} - -func (server *Server) deployBenchmark(benchmark *model.Benchmark) (*DeployedBenchmark, error) { - hostConfig := &docker.HostConfig{ - PublishAllPorts: true, - } - - deployed := &DeployedBenchmark{ - Benchmark: benchmark, - NameToId: make(map[string]string), - } - - glog.Infof("Deploying benchmark: %+v", benchmark) - - parts := strings.Split(benchmark.Image, ":") - tag := "latest" - if len(parts) > 1 { - tag = parts[1] - } - - err := server.dockerClient.PullImage(docker.PullImageOptions{ - Repository: parts[0], - Tag: tag, - }, docker.AuthConfiguration{}) - - if err != nil { - return nil, err - } - - for i := 1; i <= benchmark.Count; i++ { - config := &docker.Config{ - Image: benchmark.Image, - } - - if benchmark.Command != nil { - config.Cmd = benchmark.Command - } - - if benchmark.Resources.CPUShares > 0 { - config.CPUShares = benchmark.Resources.CPUShares - } - - if benchmark.Resources.Memory > 0 { - config.Memory = benchmark.Resources.Memory - } - - containerName := benchmark.Name + strconv.Itoa(i) - container, err := server.dockerClient.CreateContainer(docker.CreateContainerOptions{ - Name: containerName, - Config: config, - HostConfig: hostConfig, - }) - - if err != nil { - // Clean up - server.removeContainers(benchmark.Name) - return nil, err - } - - deployed.NameToId[containerName] = container.ID - - err = server.dockerClient.StartContainer(container.ID, hostConfig) - if err != nil { - // Clean up - server.removeContainers(benchmark.Name) - return nil, err - } - } - - return deployed, nil -} - -func (server *Server) createBenchmark(c *gin.Context) { - var benchmark model.Benchmark - if err := c.BindJSON(&benchmark); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": true, - "data": "Error deserializing benchmark: " + string(err.Error()), - }) - return - } - - server.mutex.Lock() - defer server.mutex.Unlock() - if _, ok := server.Benchmarks[benchmark.Name]; ok { - c.JSON(http.StatusBadRequest, gin.H{ - "error": true, - "data": "Benchmark already created. Please delete benchmark before creating", - }) - return - } - - deployed, err := server.deployBenchmark(&benchmark) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": true, - "data": "Failed to deploy benchmark: " + err.Error(), - }) - return - } - - server.Benchmarks[benchmark.Name] = deployed - c.JSON(http.StatusAccepted, gin.H{ - "error": false, - }) -} - -func (server *Server) deleteBenchmark(c *gin.Context) { - benchmarkName := c.Param("benchmark") - server.mutex.Lock() - defer server.mutex.Unlock() - - deployed, ok := server.Benchmarks[benchmarkName] - if !ok { - c.JSON(http.StatusNotFound, gin.H{ - "error": false, - }) - return - } - - for i := 1; i <= deployed.Benchmark.Count; i++ { - err := server.dockerClient.RemoveContainer(docker.RemoveContainerOptions{ - ID: deployed.NameToId[deployed.Benchmark.Name+strconv.Itoa(i)], - Force: true, - RemoveVolumes: true, - }) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": true, - "data": "Unable to remove container: " + err.Error(), - }) - return - } - } - - delete(server.Benchmarks, deployed.Benchmark.Name) - - c.JSON(http.StatusAccepted, gin.H{ - "error": false, - }) -} - -func (server *Server) updateResources(c *gin.Context) { - benchmarkName := c.Param("benchmark") - server.mutex.Lock() - defer server.mutex.Unlock() - - deployed, ok := server.Benchmarks[benchmarkName] - if !ok { - c.JSON(http.StatusNotFound, gin.H{ - "error": false, - }) - return - } - - var resources model.Resources - if err := c.BindJSON(&resources); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": true, - "data": "Unable to deserialize resources: " + err.Error(), - }) - return - } - - updateOptions := docker.UpdateContainerOptions{} - if resources.CPUShares > 0 { - updateOptions.CPUShares = int(resources.CPUShares) - } - - if resources.Memory > 0 { - updateOptions.Memory = int(resources.Memory) - } - - glog.Infof("Updating resources for benchmark", deployed.Benchmark.Name) - for i := 1; i <= deployed.Benchmark.Count; i++ { - containerId := deployed.NameToId[deployed.Benchmark.Name+strconv.Itoa(i)] - glog.Infof("Updating container ID %s, %+v", containerId, updateOptions) - err := server.dockerClient.UpdateContainer(containerId, updateOptions) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": true, - "data": "Unable to update resources: " + err.Error(), - }) - return - } - } - - c.JSON(http.StatusAccepted, gin.H{ - "error": false, - }) -} - -func initGin() { - server := NewServer(dockerClient, "7778") - err := server.Run() - if err != nil { - panic(err) - } + return router } diff --git a/benchmark-agent/main.go b/benchmark-agent/main.go index dbd823c..0298e58 100644 --- a/benchmark-agent/main.go +++ b/benchmark-agent/main.go @@ -2,5 +2,5 @@ package main func main() { initDocker() - initGin() + initHTTPServ() } From b422f35623f4175393ecbb71330ad5cec7c128f6 Mon Sep 17 00:00:00 2001 From: chyeh Date: Tue, 6 Jun 2017 23:24:56 +0800 Subject: [PATCH 9/9] [#1] Add field `intensity` for each benchmark --- benchmark-agent/it_test.go | 9 ++++++--- benchmark-agent/model/model.go | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/benchmark-agent/it_test.go b/benchmark-agent/it_test.go index c4b3aa6..c89b968 100644 --- a/benchmark-agent/it_test.go +++ b/benchmark-agent/it_test.go @@ -32,7 +32,8 @@ var _ = Describe("Test creating benchmarks", func() { Resources: &model.Resources{ CPUShares: 512, }, - Image: "hyperpilot/busycpu", + Image: "hyperpilot/busycpu", + Intensity: 100, } resp := testHttp.Response( httpClientConfig.NewSling(). @@ -53,7 +54,8 @@ var _ = Describe("Test updating benchmarks", func() { Resources: &model.Resources{ CPUShares: 512, }, - Image: "hyperpilot/busycpu", + Image: "hyperpilot/busycpu", + Intensity: 100, } testHttp.Response( httpClientConfig.NewSling(). @@ -92,7 +94,8 @@ var _ = Describe("Test deleting benchmarks", func() { Resources: &model.Resources{ CPUShares: 512, }, - Image: "hyperpilot/busycpu", + Image: "hyperpilot/busycpu", + Intensity: 100, } testHttp.Response( httpClientConfig.NewSling(). diff --git a/benchmark-agent/model/model.go b/benchmark-agent/model/model.go index aceb3a0..ada5c47 100644 --- a/benchmark-agent/model/model.go +++ b/benchmark-agent/model/model.go @@ -11,4 +11,5 @@ type Benchmark struct { Resources *Resources `json:"resources"` Image string `json:"image" binding:"required"` Command []string `json:"command"` + Intensity int `json:"intensity"` }