Skip to content
This repository was archived by the owner on May 13, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 10 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
##GLuTEN - Go LoadTEstiNg [http://acronymcreator.net/](http://acronymcreator.net/)
## GLuTEN - Go LoadTEstiNg
Предназначен для обучения Go

Проект представляет собой распределенную систему нагрузочного тестирования. Это немного громко сказано - пока она сможет делать много GET запросов на целевой ендпоинт с нескольких машин, при этом нагрузку можно регулировать

###Архитектура
### Архитектура
Система состоит из 3-х компонент

- server
- master
- slave
- command line application - запускает задачи

###Server
### Server
Несколько нод, которые будут создавать нагрузку. Должна быть хотя бы одна `master` нода. Каждая нода стартует из консоли

####Master
#### Master
Cтарт мастера

```
Expand All @@ -25,7 +27,7 @@ gluten-server --master-mode --web-port 8888 --rpc-port 9090
- С другими частями системы общается через порт заданный `--rpc-port`
- Мастер должен знать сколько у него слейвов. После того как на master пришла задача, на базе этих знаний он разбивает ее на подзадачи для каждого из слейвов и отсылает их посредством RPC

####Slave
#### Slave
Старт слэйва

```
Expand All @@ -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 ...
Expand All @@ -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
```
Нужно прочитать про вендоринг и попробовать его использовать
49 changes: 27 additions & 22 deletions cli/client/json_file_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
4 changes: 2 additions & 2 deletions core/runners.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var (
)

type DefaultRunner struct {
hander ResultHandler
handler ResultHandler
}

func NewDefaultRunner(handler ResultHandler) TestRunner {
Expand Down Expand Up @@ -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",
Expand Down
29 changes: 14 additions & 15 deletions core/runners_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,41 @@ 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{}
resultHandler.On("Handle", mock.Anything).Return().Run(func(args mock.Arguments) {
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)

}

Expand Down
2 changes: 1 addition & 1 deletion shared/rpc/cli/rpcService.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion shared/rpc/cli/rpcService.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
syntax = "proto3";

package rpc;
package cli;

service ProtoService {
rpc SendConfig (Project) returns (ResponseMessage) {}
Expand Down
2 changes: 1 addition & 1 deletion shared/rpc/master/rpcService.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion shared/rpc/master/rpcService.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
syntax = "proto3";

package slave;
package master;

service ProtoService {
rpc SendMessage (Step) returns (ResponseMessage) {}
Expand Down
22 changes: 11 additions & 11 deletions shared/utils/proto_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ 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)
}
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}}
Expand All @@ -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)
Expand All @@ -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)
Expand Down
90 changes: 90 additions & 0 deletions shared/utils/proto_utils_test.go
Original file line number Diff line number Diff line change
@@ -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
}