diff --git a/accountservice/Dockerfile b/accountservice/Dockerfile deleted file mode 100644 index 9f596c0..0000000 --- a/accountservice/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM iron/base -EXPOSE 6767 - -ADD accountservice-linux-amd64 / - -ENTRYPOINT ["./accountservice-linux-amd64"] \ No newline at end of file diff --git a/accountservice/dbclient/boltclient.go b/accountservice/dbclient/boltclient.go deleted file mode 100644 index 43123de..0000000 --- a/accountservice/dbclient/boltclient.go +++ /dev/null @@ -1,107 +0,0 @@ -package dbclient - -import ( - "github.com/boltdb/bolt" - "log" - "strconv" - "github.com/callistaenterprise/goblog/accountservice/model" - "encoding/json" - "fmt" -) - - - -type IBoltClient interface { - OpenBoltDb() - QueryAccount(accountId string) (model.Account, error) - Seed() -} - -// Real implementation -type BoltClient struct { - boltDB *bolt.DB -} - -func (bc *BoltClient) OpenBoltDb() { - var err error - bc.boltDB, err = bolt.Open("accounts.db", 0600, nil) - if err != nil { - log.Fatal(err) - } -} - -func (bc *BoltClient) QueryAccount(accountId string) (model.Account, error) { - // Allocate an empty Account instance we'll let json.Unmarhal populate for us in a bit. - account := model.Account{} - - // Read an object from the bucket using boltDB.View - err := bc.boltDB.View(func(tx *bolt.Tx) error { - // Read the bucket from the DB - b := tx.Bucket([]byte("AccountBucket")) - - // Read the value identified by our accountId supplied as []byte - accountBytes := b.Get([]byte(accountId)) - if accountBytes == nil { - return fmt.Errorf("No account found for " + accountId) - } - // Unmarshal the returned bytes into the account struct we created at - // the top of the function - json.Unmarshal(accountBytes, &account) - - // Return nil to indicate nothing went wrong, e.g no error - return nil - }) - // If there were an error, return the error - if err != nil { - return model.Account{}, err - } - // Return the Account struct and nil as error. - return account, nil -} - - -// Start seeding accounts -func (bc *BoltClient) Seed() { - bc.initializeBucket() - bc.seedAccounts() -} - -// Creates an "AccountBucket" in our BoltDB. It will overwrite any existing bucket of the same name. -func (bc *BoltClient) initializeBucket() { - bc.boltDB.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucket([]byte("AccountBucket")) - if err != nil { - return fmt.Errorf("create bucket failed: %s", err) - } - return nil - }) -} - -// Seed (n) make-believe account objects into the AcountBucket bucket. -func (bc *BoltClient) seedAccounts() { - - total := 100 - for i := 0; i < total; i++ { - - // Generate a key 10000 or larger - key := strconv.Itoa(10000 + i) - - // Create an instance of our Account struct - acc := model.Account{ - Id: key, - Name: "Person_" + strconv.Itoa(i), - } - - // Serialize the struct to JSON - jsonBytes, _ := json.Marshal(acc) - - // Write the data to the AccountBucket - bc.boltDB.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("AccountBucket")) - err := b.Put([]byte(key), jsonBytes) - return err - }) - } - fmt.Printf("Seeded %v fake accounts...\n", total) -} - diff --git a/accountservice/dbclient/mockclient.go b/accountservice/dbclient/mockclient.go deleted file mode 100644 index 1cad809..0000000 --- a/accountservice/dbclient/mockclient.go +++ /dev/null @@ -1,24 +0,0 @@ -package dbclient - -import ( - "github.com/stretchr/testify/mock" - "github.com/callistaenterprise/goblog/accountservice/model" -) - -// MockBoltClient is a mock implementation of a datastore client for testing purposes -type MockBoltClient struct { - mock.Mock -} - -func (m *MockBoltClient) QueryAccount(accountId string) (model.Account, error) { - args := m.Mock.Called(accountId) - return args.Get(0).(model.Account), args.Error(1) -} - -func (m *MockBoltClient) OpenBoltDb() { - // Does nothing -} - -func (m *MockBoltClient) Seed() { - // Does nothing -} diff --git a/accountservice/main.go b/accountservice/main.go index 16760fd..9a96b44 100644 --- a/accountservice/main.go +++ b/accountservice/main.go @@ -3,19 +3,12 @@ package main import ( "fmt" "github.com/callistaenterprise/goblog/accountservice/service" - "github.com/callistaenterprise/goblog/accountservice/dbclient" ) var appName = "accountservice" func main() { fmt.Printf("Starting %v\n", appName) - initializeBoltClient() service.StartWebServer("6767") } -func initializeBoltClient() { - service.DBClient = &dbclient.BoltClient{} - service.DBClient.OpenBoltDb() - service.DBClient.Seed() -} diff --git a/accountservice/model/account.go b/accountservice/model/account.go deleted file mode 100644 index 87da3c2..0000000 --- a/accountservice/model/account.go +++ /dev/null @@ -1,10 +0,0 @@ -package model - -type Account struct { - Id string `json:"id"` - Name string `json:"name"` -} - -func (a *Account) ToString() string { - return a.Id + " " + a.Name -} \ No newline at end of file diff --git a/accountservice/service/handlers.go b/accountservice/service/handlers.go deleted file mode 100644 index 3895e2b..0000000 --- a/accountservice/service/handlers.go +++ /dev/null @@ -1,35 +0,0 @@ -package service - -import ( - "net/http" - "github.com/gorilla/mux" - "encoding/json" - "strconv" - "github.com/callistaenterprise/goblog/accountservice/dbclient" - "fmt" -) - -var DBClient dbclient.IBoltClient - -func GetAccount(w http.ResponseWriter, r *http.Request) { - - // Read the 'accountId' path parameter from the mux map - var accountId = mux.Vars(r)["accountId"] - - // Read the account struct BoltDB - account, err := DBClient.QueryAccount(accountId) - - // If err, return a 404 - if err != nil { - fmt.Println("Some error occured serving " + accountId + ": " + err.Error()) - w.WriteHeader(http.StatusNotFound) - return - } - - // If found, marshal into JSON, write headers and content - data, _ := json.Marshal(account) - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Content-Length", strconv.Itoa(len(data))) - w.WriteHeader(http.StatusOK) - w.Write(data) -} \ No newline at end of file diff --git a/accountservice/service/handlers_test.go b/accountservice/service/handlers_test.go deleted file mode 100644 index f8d8aa6..0000000 --- a/accountservice/service/handlers_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package service - -import ( - . "github.com/smartystreets/goconvey/convey" - "testing" - "net/http/httptest" - "github.com/callistaenterprise/goblog/accountservice/dbclient" - "github.com/callistaenterprise/goblog/accountservice/model" - "fmt" - "encoding/json" -) - - - - -func TestGetAccount(t *testing.T) { - mockRepo := &dbclient.MockBoltClient{} - - mockRepo.On("QueryAccount", "123").Return(model.Account{Id:"123", Name:"Person_123"}, nil) - mockRepo.On("QueryAccount", "456").Return(model.Account{}, fmt.Errorf("Some error")) - DBClient = mockRepo - - Convey("Given a HTTP request for /accounts/123", t, func() { - req := httptest.NewRequest("GET", "/accounts/123", nil) - resp := httptest.NewRecorder() - - Convey("When the request is handled by the Router", func() { - NewRouter().ServeHTTP(resp, req) - - Convey("Then the response should be a 200", func() { - So(resp.Code, ShouldEqual, 200) - - account := model.Account{} - json.Unmarshal(resp.Body.Bytes(), &account) - So(account.Id, ShouldEqual, "123") - So(account.Name, ShouldEqual, "Person_123") - }) - }) - }) - - Convey("Given a HTTP request for /accounts/456", t, func() { - req := httptest.NewRequest("GET", "/accounts/456", nil) - resp := httptest.NewRecorder() - - Convey("When the request is handled by the Router", func() { - NewRouter().ServeHTTP(resp, req) - - Convey("Then the response should be a 404", func() { - So(resp.Code, ShouldEqual, 404) - }) - }) - }) -} - -func TestGetAccountWrongPath(t *testing.T) { - - Convey("Given a HTTP request for /invalid/123", t, func() { - req := httptest.NewRequest("GET", "/invalid/123", nil) - resp := httptest.NewRecorder() - - Convey("When the request is handled by the Router", func() { - NewRouter().ServeHTTP(resp, req) - - Convey("Then the response should be a 404", func() { - So(resp.Code, ShouldEqual, 404) - }) - }) - }) -} \ No newline at end of file diff --git a/accountservice/service/routes.go b/accountservice/service/routes.go index dbbfa40..00044f6 100644 --- a/accountservice/service/routes.go +++ b/accountservice/service/routes.go @@ -20,6 +20,9 @@ var routes = Routes{ "GetAccount", // Name "GET", // HTTP method "/accounts/{accountId}", // Route pattern - GetAccount, + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.Write([]byte("{\"result\":\"OK\"}")) + }, }, } diff --git a/copyall.sh b/copyall.sh index 92347eb..55698cd 100755 --- a/copyall.sh +++ b/copyall.sh @@ -5,5 +5,3 @@ export CGO_ENABLED=0 cd accountservice;go get;go build -o accountservice-linux-amd64;echo built `pwd`;cd .. export GOOS=darwin - -docker build -t someprefix/accountservice accountservice/ \ No newline at end of file diff --git a/loadtest/README.md b/loadtest/README.md new file mode 100644 index 0000000..e139db0 --- /dev/null +++ b/loadtest/README.md @@ -0,0 +1,5 @@ +# Load test for the Go blog series + +### Usage + + mvn gatling:execute -Dusers=1000 -Dduration=30 -DbaseUrl=http://192.168.99.100:6767 \ No newline at end of file diff --git a/loadtest/pom.xml b/loadtest/pom.xml new file mode 100644 index 0000000..7d37129 --- /dev/null +++ b/loadtest/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + se.callistaenterprise.goblog + loadtest + 1.0-SNAPSHOT + + + + excilys + Excilys Repository + http://repository.excilys.com/content/groups/public + + + + + + excilys + Excilys Repository + http://repository.excilys.com/content/groups/public + + + + + 2.1.7 + 2.1.7 + + + + + io.gatling.highcharts + gatling-charts-highcharts + ${gatling.version} + test + + + + + + + io.gatling + gatling-maven-plugin + ${gatling-plugin.version} + + gatling + ${simulationClass} + + -Dusers=1 + -DbaseUrl=http://localhost:6767 + -Dduration=30 + + + + + test + + execute + + + + + + + diff --git a/loadtest/src/test/resources/application.conf b/loadtest/src/test/resources/application.conf new file mode 100755 index 0000000..caa0769 --- /dev/null +++ b/loadtest/src/test/resources/application.conf @@ -0,0 +1,9 @@ +#################################### +# Akka Actor Config File # +#################################### + +akka { + scheduler { + tick-duration = 50ms + } +} diff --git a/loadtest/src/test/resources/data/accounts.csv b/loadtest/src/test/resources/data/accounts.csv new file mode 100644 index 0000000..4ab339b --- /dev/null +++ b/loadtest/src/test/resources/data/accounts.csv @@ -0,0 +1,31 @@ +accountId +10000 +10001 +10002 +10003 +10004 +10005 +10006 +10007 +10008 +10009 +10010 +10011 +10012 +10013 +10014 +10015 +10016 +10017 +10018 +10019 +10020 +10021 +10022 +10023 +10024 +10025 +10026 +10027 +10028 +10029 diff --git a/loadtest/src/test/resources/gatling.conf b/loadtest/src/test/resources/gatling.conf new file mode 100755 index 0000000..6417340 --- /dev/null +++ b/loadtest/src/test/resources/gatling.conf @@ -0,0 +1,88 @@ +######################### +# Gatling Configuration # +######################### + +# This file contains all the settings configurable for Gatling with their default values + +gatling { + core { + #outputDirectoryBaseName = "" + #runDescription = "" + #encoding = "utf-8" # encoding for every file manipulation made in gatling + #simulationClass = "" + extract { + regex { + #cache = true + } + xpath { + #cache = true + } + jsonPath { + #cache = true + } + css { + #engine = jodd # can change to jsoup + } + } + timeOut { + #simulation = 86400 # in s + #actor = 5 # in s + } + directory { + #data = user-files/data + #requestBodies = user-files/request-bodies + #simulations = user-files/simulations + #reportsOnly = "" + #binaries = "" + #results = results + } + } + charting { + #noReports = false + #maxPlotPerSeries = 1000 + #accuracy = 10 # in ms + indicators { + #lowerBound = 800 # in ms + #higherBound = 1200 # in ms + #percentile1 = 95 # in percents + #percentile2 = 99 # in percents + } + } + http { + #allowPoolingConnection = true + #allowSslConnectionPool = true + #compressionEnabled = true # Set if compression should be supported or not + #connectionTimeout = 60000 # Timeout of the connection to the server (ms) + #idleConnectionInPoolTimeoutInMs = 60000 + #idleConnectionTimeoutInMs = 60000 + #maxConnectionLifeTimeInMs = -1 # max duration a connection can stay open + #ioThreadMultiplier = 2 + #maximumConnectionsPerHost = -1 + #maximumConnectionsTotal = -1 + #maxRetry = 4 # number of times that a request should be tried again + #requestCompressionLevel = -1 + #requestTimeoutInMs = 60000 # Timeout of the requests (ms) + #useProxyProperties = false + #userAgent = "NING/1.0" + #useRawUrl = false + #warmUpUrl = "http://goo.gl/wqthq" + #rfc6265CookieEncoding = true # use rfc6265 cookie encoding style + } + data { + #writers = "console, file" + #reader = file + console { + #light = false + } + file { + bufferSize = 8192 + } + graphite { + #light = false + #host = "localhost" + #port = 2003 + #rootPathPrefix = "gatling" + #bucketWidth = 100 + } + } +} diff --git a/loadtest/src/test/resources/logback.xml b/loadtest/src/test/resources/logback.xml new file mode 100755 index 0000000..756e2a4 --- /dev/null +++ b/loadtest/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/loadtest/src/test/scala/se/callistaenterprise/goblog/Conf.scala b/loadtest/src/test/scala/se/callistaenterprise/goblog/Conf.scala new file mode 100644 index 0000000..21bf62e --- /dev/null +++ b/loadtest/src/test/scala/se/callistaenterprise/goblog/Conf.scala @@ -0,0 +1,12 @@ +package se.callistaenterprise.goblog + +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.jdbc.Predef._ + +object Conf { + var users = System.getProperty("users", "2").toInt + val baseUrl = System.getProperty("baseUrl", "http://localhost:6767") + var httpConf = http.baseURL(baseUrl) + var duration = System.getProperty("duration", "30").toInt +} \ No newline at end of file diff --git a/loadtest/src/test/scala/se/callistaenterprise/goblog/Headers.scala b/loadtest/src/test/scala/se/callistaenterprise/goblog/Headers.scala new file mode 100644 index 0000000..34fbb3e --- /dev/null +++ b/loadtest/src/test/scala/se/callistaenterprise/goblog/Headers.scala @@ -0,0 +1,8 @@ +package se.callistaenterprise.goblog + +object Headers { + // HTTP Headers + val http_header = Map( + "Accept" -> "application/json;") + + } \ No newline at end of file diff --git a/loadtest/src/test/scala/se/callistaenterprise/goblog/LoadTest.scala b/loadtest/src/test/scala/se/callistaenterprise/goblog/LoadTest.scala new file mode 100644 index 0000000..9b263d6 --- /dev/null +++ b/loadtest/src/test/scala/se/callistaenterprise/goblog/LoadTest.scala @@ -0,0 +1,13 @@ +package se.callistaenterprise.goblog + +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.jdbc.Predef._ +import scala.concurrent.duration._ + +class LoadTest extends Simulation { + + setUp( + Scenarios.scn_Browse.inject(rampUsers(Conf.users) over (Scenarios.rampUpTimeSecs seconds)).protocols(Conf.httpConf) + ) +} \ No newline at end of file diff --git a/loadtest/src/test/scala/se/callistaenterprise/goblog/Scenarios.scala b/loadtest/src/test/scala/se/callistaenterprise/goblog/Scenarios.scala new file mode 100644 index 0000000..8b4bde2 --- /dev/null +++ b/loadtest/src/test/scala/se/callistaenterprise/goblog/Scenarios.scala @@ -0,0 +1,29 @@ +package se.callistaenterprise.goblog + +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.jdbc.Predef._ +import scala.concurrent.duration._ + +object Scenarios { + + val rampUpTimeSecs = 10 + + /* + * HTTP scenarios + */ + + // Browse + val browse_guids = csv("accounts.csv").circular + val scn_Browse = scenario("GetAccounts") + .during(Conf.duration) { + feed(browse_guids) + .exec( + http("GetAccount") + .get("/accounts/" + "${accountId}") + .headers(Headers.http_header) + .check(status.is(200)) + ) + .pause(1) + } +} \ No newline at end of file