diff --git a/README.md b/README.md index aad324d..20da977 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -##GLuTEN - Go LoadTEstiNg [http://acronymcreator.net/](http://acronymcreator.net/) +## GLuTEN - Go LoadTEstiNg +Предназначен для обучения Go + Проект представляет собой распределенную систему нагрузочного тестирования. Это немного громко сказано - пока она сможет делать много GET запросов на целевой ендпоинт с нескольких машин, при этом нагрузку можно регулировать -###Архитектура +### Архитектура Система состоит из 3-х компонент - server @@ -9,10 +11,10 @@ - slave - command line application - запускает задачи -###Server +### Server Несколько нод, которые будут создавать нагрузку. Должна быть хотя бы одна `master` нода. Каждая нода стартует из консоли -####Master +#### Master Cтарт мастера ``` @@ -25,7 +27,7 @@ gluten-server --master-mode --web-port 8888 --rpc-port 9090 - С другими частями системы общается через порт заданный `--rpc-port` - Мастер должен знать сколько у него слейвов. После того как на master пришла задача, на базе этих знаний он разбивает ее на подзадачи для каждого из слейвов и отсылает их посредством RPC -####Slave +#### Slave Старт слэйва ``` @@ -34,7 +36,7 @@ gluten-server --slave-mode --master 127.0.0.1:9090 --rpc-port - с параметрами думаю все понятно, описывать не буду - после того как пришла задача, нужно равномерно нагрузить целевой ендпоинт. Тут нужно прикрутить многопоточность -###Command line app +### Command line app ``` gluten --master 192.168.1.1:8888 ... @@ -48,17 +50,10 @@ gluten --master 192.168.1.1:8888 ... - после отправки задачи, работа утилиты сразу завершается и печатается ссылка на веб-интерфейс мастера - функционал этой утилиты можно продублировать в веб интерфейсе мастера, чтобы задачи можно было создавать из него -###Технологии -RPC нужно реализовать посредством [`Thrift`](https://thrift.apache.org/), т.к. он используется везде в `4Tree`. Как замену можно использовать [`Protobuf+gRPC`](https://grpc.io/) - как по мне, то это лучший вариант +### Технологии +RPC желательно реализовать посредством [`Thrift`](https://thrift.apache.org/), т.к. он используется на одном из проектов. Как замену можно использовать [`Protobuf+gRPC`](https://grpc.io/) - как по мне, то это лучший вариант Статистика хранится в базе - кто ее будет сохранять, только мастер или слэйвы тоже - нужно смотреть как лучше. База - Postgres или Mongo -###Структура проекта -Нужно уточнить где это хостить, может на нашем гитлабе? -``` -$GOPATH/src/github.com/instinctools/gluten - |---server - |---cmd -``` Нужно прочитать про вендоринг и попробовать его использовать \ No newline at end of file diff --git a/cli/client/json_file_service.go b/cli/client/json_file_service.go index 2c36301..3697268 100644 --- a/cli/client/json_file_service.go +++ b/cli/client/json_file_service.go @@ -30,28 +30,33 @@ func ReadJSONFile(pathToFile string) string { } func AutoGenerateConfig(filename string) { - //TODO - generateJSON should be a string, don't need to marshal it to string - json := "{\"Name\": \"Project1\"," -// + "\"Scenarios\": [{" -// +"\"Name\": \"Scenario1\"," -// +"\"Cases\": [{" -// +"\"Name\": \"Case1\"," -// +"\"Steps\": [{" -// +"\"Name\": \"G1\"," -// +"\"Type\": \"GetRequestStep\"," -// +"\"Parameters\": {\"URL\": \"https://google.com\"}," -// +"\"SubSteps\" : [{" -// +"\"Name\": \"P1\"," -// +"\"Type\": \"GetRequestStep\"," -// +"\"Parameters\": {\"URL\": \"https://google.com\"}" -// +"}]}," -// +"{\"Name\": \"G2\"," -// +"\"Type\": \"GetRequestStep\"," -// +"\"Parameters\": {\"URL\": \"https://google.com\"}" -// +"}]" -// +"}]" -// +"}]" -// +"}" + json := `{ + "Name": "Project1", + "Scenarios": [{ + "Name": "Scenario1", + "Cases": [{ + "Name": "Case1", + "Steps": [{ + "Name": "G1", + "Type": "GET_REQUEST_STEP", + "Parameters": {"URL": "https://google.com"}, + "SubSteps" : [ + { + "Name": "P1", + "Type": "GET_REQUEST_STEP", + "Parameters": {"URL": "https://google.com"} + } + ] + }, + { + "Name": "G2", + "Type": "GET_REQUEST_STEP", + "Parameters": {"URL": "https://google.com"} + } + ] + }] + }] + }` err := ioutil.WriteFile(filename, []byte(json), 0644) if err != nil { logging.WithFields(logging.Fields{ diff --git a/core/runners.go b/core/runners.go index 5fb9781..653e073 100644 --- a/core/runners.go +++ b/core/runners.go @@ -10,7 +10,7 @@ var ( ) type DefaultRunner struct { - hander ResultHandler + handler ResultHandler } func NewDefaultRunner(handler ResultHandler) TestRunner { @@ -48,7 +48,7 @@ func (runner *DefaultRunner) run1(context *Execution, c interface{}) error { case TestStep: step := c.(TestStep) metrics := step.Run(context) - runner.hander.Handle(StepResult{ + runner.handler.Handle(StepResult{ ExecutionID: context.ID, Metrics: metrics, Status: "Completed", diff --git a/core/runners_test.go b/core/runners_test.go index 19dd57f..7469714 100644 --- a/core/runners_test.go +++ b/core/runners_test.go @@ -10,16 +10,18 @@ import ( ) func TestProjectWorkflow(t *testing.T) { + params := make(map[string]interface{}) + params["URL"] = "http://google.com" case1 := core.TestCase{ - Name: "Case1", - Steps: []core.Step{ - steps.NewGetRequestStep("Step1", "http://google.com"), + Common: core.Common{"Case1"}, + Steps: []core.TestStep{ + steps.NewStep("GET_REQUEST_STEP", "G1", params, nil), }} - scenario1 := core.Scenario{Name: "Sc1"} + scenario1 := core.TestScenario{Common: core.Common{"Sc1"}} scenario1.Add(case1) - project1 := &core.Project{Name: "Project1"} + project1 := &core.Project{Common: core.Common{"Project1"}} project1.Add(scenario1) resultHandler := &ResultHandlerMock{} @@ -27,25 +29,22 @@ func TestProjectWorkflow(t *testing.T) { stepResult := args.Get(0).(core.StepResult) resultHandler.StepResults = append(resultHandler.StepResults, stepResult) }) - runner := core.DefaultRunner{ - Handler: resultHandler, - } - assert.Equal(t, 1, len(project1.GetSteps())) - runner.Run(project1) + runner := core.NewDefaultRunner(resultHandler) + assert.Equal(t, 1, len(project1.GetAllSteps())) + execution := core.Execution{ID:"1"} + runner.Run(&execution, project1) resultHandler.AssertNumberOfCalls(t, "Handle", 1) assert.Equal(t, 1, len(resultHandler.StepResults)) stepResult := resultHandler.StepResults[0] assert.NotEmpty(t, stepResult.ExecutionID) - assert.Equal(t, "OK", stepResult.Status) - assert.NoError(t, stepResult.Error) - assert.Equal(t, case1.Steps[0], stepResult.Step) + assert.Equal(t, "Completed", stepResult.Status) + assert.Equal(t, case1.Steps[0].GetStepType(), stepResult.StepType) assert.Equal(t, 1, len(stepResult.Metrics)) metric := stepResult.Metrics[0] - assert.Equal(t, "TIME", metric.Key) - assert.Equal(t, 100, metric.Val) + assert.Equal(t, "STATUS", metric.Key) } diff --git a/shared/rpc/cli/rpcService.pb.go b/shared/rpc/cli/rpcService.pb.go index a7d378f..01c77d0 100644 --- a/shared/rpc/cli/rpcService.pb.go +++ b/shared/rpc/cli/rpcService.pb.go @@ -15,7 +15,7 @@ It has these top-level messages: TestCase Step */ -package rpc +package cli import proto "github.com/golang/protobuf/proto" diff --git a/shared/rpc/cli/rpcService.proto b/shared/rpc/cli/rpcService.proto index 26418da..406a76a 100644 --- a/shared/rpc/cli/rpcService.proto +++ b/shared/rpc/cli/rpcService.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package rpc; +package cli; service ProtoService { rpc SendConfig (Project) returns (ResponseMessage) {} diff --git a/shared/rpc/master/rpcService.pb.go b/shared/rpc/master/rpcService.pb.go index c7bf775..32c57e6 100644 --- a/shared/rpc/master/rpcService.pb.go +++ b/shared/rpc/master/rpcService.pb.go @@ -13,7 +13,7 @@ It has these top-level messages: Execution ResponseMessage */ -package slave +package master import proto "github.com/golang/protobuf/proto" diff --git a/shared/rpc/master/rpcService.proto b/shared/rpc/master/rpcService.proto index f3b319c..3b08698 100644 --- a/shared/rpc/master/rpcService.proto +++ b/shared/rpc/master/rpcService.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package slave; +package master; service ProtoService { rpc SendMessage (Step) returns (ResponseMessage) {} diff --git a/shared/utils/proto_utils.go b/shared/utils/proto_utils.go index 12f5f2c..a755db7 100644 --- a/shared/utils/proto_utils.go +++ b/shared/utils/proto_utils.go @@ -3,14 +3,14 @@ package utils import ( "bitbucket.org/instinctools/gluten/core" "bitbucket.org/instinctools/gluten/core/steps" - pb "bitbucket.org/instinctools/gluten/shared/rpc/cli" - pm "bitbucket.org/instinctools/gluten/shared/rpc/master" + rpcCli "bitbucket.org/instinctools/gluten/shared/rpc/cli" + rpcMaster "bitbucket.org/instinctools/gluten/shared/rpc/master" "encoding/json" "strconv" ) -func DeserializeJsonToProto(jsonProject string) *pb.Project { - deserializedProject := pb.Project{} +func DeserializeJsonToProto(jsonProject string) *rpcCli.Project { + deserializedProject := rpcCli.Project{} err := json.Unmarshal([]byte(jsonProject), &deserializedProject) if err != nil { panic(err) @@ -18,7 +18,7 @@ func DeserializeJsonToProto(jsonProject string) *pb.Project { return &deserializedProject } -func ParseProto2Project(pProject *pb.Project) *core.Project { +func ParseProto2Project(pProject *rpcCli.Project) *core.Project { testProject := &core.Project{Common: core.Common{pProject.Name}} for _, pScenario := range pProject.GetScenarios() { testScenario := core.TestScenario{Common: core.Common{pScenario.Name}} @@ -35,22 +35,22 @@ func ParseProto2Project(pProject *pb.Project) *core.Project { return testProject } -func ParseStep2Proto(context *core.Execution, step core.TestStep) *pm.Step { - var pSubSteps []*pm.Step +func ParseStep2Proto(context *core.Execution, step core.TestStep) *rpcMaster.Step { + var pSubSteps []*rpcMaster.Step for _, subStep := range step.GetSubSteps() { pSubStep := ParseStep2Proto(context, subStep) pSubSteps = append(pSubSteps, pSubStep) } sMap := parsIMap(step.GetParams()) - return &pm.Step{ + return &rpcMaster.Step{ Name: step.GetCommon().Name, Type: step.GetStepType(), Parameters: sMap, SubSteps: pSubSteps, - Exec: &pm.Execution{ID: context.ID, Status: context.Status}} + Exec: &rpcMaster.Execution{ID: context.ID, Status: context.Status}} } -func ParseCliProto2Step(pStep *pb.Step) core.TestStep { +func ParseCliProto2Step(pStep *rpcCli.Step) core.TestStep { var subSteps []core.TestStep for _, pSubStep := range pStep.GetSubSteps() { subStep := ParseCliProto2Step(pSubStep) @@ -61,7 +61,7 @@ func ParseCliProto2Step(pStep *pb.Step) core.TestStep { return step } -func ParseMasterProto2Step(pStep *pm.Step) (*core.Execution, core.TestStep) { +func ParseMasterProto2Step(pStep *rpcMaster.Step) (*core.Execution, core.TestStep) { var subSteps []core.TestStep for _, pSubStep := range pStep.GetSubSteps() { _, subStep := ParseMasterProto2Step(pSubStep) diff --git a/shared/utils/proto_utils_test.go b/shared/utils/proto_utils_test.go new file mode 100644 index 0000000..15581cc --- /dev/null +++ b/shared/utils/proto_utils_test.go @@ -0,0 +1,90 @@ +package utils_test + +import ( + "bitbucket.org/instinctools/gluten/core" + "bitbucket.org/instinctools/gluten/core/steps" + rpcCli "bitbucket.org/instinctools/gluten/shared/rpc/cli" + "bitbucket.org/instinctools/gluten/shared/utils" + assert "github.com/stretchr/testify/require" + "testing" +) + +func TestProtoUtils(t *testing.T) { + pProject := ProtoJsonDeserializerTest(t) + Proto2ProjectTest(t, pProject) + Step2ProtoTest(t) + +} + +func ProtoJsonDeserializerTest(t *testing.T) *rpcCli.Project { + project := utils.DeserializeJsonToProto(getTestProjectJson()) + assert.NotEmpty(t, project) + assert.Equal(t, "Project1", project.Name) + assert.Equal(t, "Scenario1", project.Scenarios[0].Name) + assert.Equal(t, "Case1", project.Scenarios[0].Cases[0].Name) + assert.Equal(t, "G1", project.Scenarios[0].Cases[0].Steps[0].Name) + assert.Equal(t, "G1.1", project.Scenarios[0].Cases[0].Steps[0].SubSteps[0].Name) + assert.Equal(t, "G2", project.Scenarios[0].Cases[0].Steps[1].Name) + return project +} + +func Proto2ProjectTest(t *testing.T, pProject *rpcCli.Project) { + project := utils.ParseProto2Project(pProject) + assert.NotEmpty(t, project) + assert.Equal(t, "Project1", project.Name) + assert.Equal(t, "Scenario1", project.Scenarios[0].Name) + assert.Equal(t, "Case1", project.Scenarios[0].Cases[0].Name) + assert.Equal(t, "G1", project.Scenarios[0].Cases[0].Steps[0].GetCommon().Name) + assert.Equal(t, "G1.1", project.Scenarios[0].Cases[0].Steps[0].GetSubSteps()[0].GetCommon().Name) + assert.Equal(t, "G2", project.Scenarios[0].Cases[0].Steps[1].GetCommon().Name) +} + +func Step2ProtoTest(t *testing.T) { + step := getTestStep() + execution := core.Execution{ID:"1"} + pStep := utils.ParseStep2Proto(&execution, step) + assert.NotEmpty(t, pStep) + +} + +func getTestStep() core.TestStep { + params := make(map[string]interface{}) + params["URL"] = "https://google.com" + subStep := steps.NewStep("GET_REQUEST_STEP", "1.1GS", params, nil) + var subSteps []core.TestStep + subSteps = append(subSteps, subStep) + step := steps.NewStep("GET_REQUEST_STEP", "1GS", params, subSteps) + return step +} + +func getTestProjectJson() string { + json := `{ + "Name": "Project1", + "Scenarios": [{ + "Name": "Scenario1", + "Cases": [{ + "Name": "Case1", + "Steps": [{ + "Name": "G1", + "Type": "GET_REQUEST_STEP", + "Parameters": {"URL": "https://google.com"}, + "SubSteps" : [ + { + "Name": "G1.1", + "Type": "GET_REQUEST_STEP", + "Parameters": {"URL": "https://google.com"} + } + ] + }, + { + "Name": "G2", + "Type": "GET_REQUEST_STEP", + "Parameters": {"URL": "https://google.com"} + } + ] + }] + }] + }` + + return json +}