diff --git a/.mapping.json b/.mapping.json index c8d6fe3f7..bd99a754e 100644 --- a/.mapping.json +++ b/.mapping.json @@ -288,6 +288,7 @@ "lib/netutil/mocks/dns_cache.go":"load/projects/pandora/lib/netutil/mocks/dns_cache.go", "lib/netutil/netutil_suite_test.go":"load/projects/pandora/lib/netutil/netutil_suite_test.go", "lib/netutil/validator.go":"load/projects/pandora/lib/netutil/validator.go", + "lib/pointer/pointer.go":"load/projects/pandora/lib/pointer/pointer.go", "lib/str/format.go":"load/projects/pandora/lib/str/format.go", "lib/str/format_test.go":"load/projects/pandora/lib/str/format_test.go", "lib/str/string.go":"load/projects/pandora/lib/str/string.go", diff --git a/components/providers/http_scenario/ammo_config.go b/components/providers/http_scenario/ammo_config.go index 5428b676d..d58e26422 100644 --- a/components/providers/http_scenario/ammo_config.go +++ b/components/providers/http_scenario/ammo_config.go @@ -14,7 +14,7 @@ type ScenarioConfig struct { Name string Weight int64 MinWaitingTime int64 `config:"min_waiting_time"` - Shoots []string + Requests []string } type RequestConfig struct { diff --git a/components/providers/http_scenario/ammo_hcl.go b/components/providers/http_scenario/ammo_hcl.go index bb541b6df..e8523c07e 100644 --- a/components/providers/http_scenario/ammo_hcl.go +++ b/components/providers/http_scenario/ammo_hcl.go @@ -40,9 +40,9 @@ type RequestHCL struct { type ScenarioHCL struct { Name string `hcl:"name,label"` - Weight int64 `hcl:"weight"` - MinWaitingTime int64 `hcl:"min_waiting_time"` - Shoots []string `hcl:"shoot"` + Weight *int64 `hcl:"weight"` + MinWaitingTime *int64 `hcl:"min_waiting_time"` + Requests []string `hcl:"requests"` } type AssertSizeHCL struct { @@ -204,7 +204,16 @@ func ConvertHCLToAmmo(ammo AmmoHCL, fs afero.Fs) (AmmoConfig, error) { if len(ammo.Scenarios) > 0 { scenarios = make([]ScenarioConfig, len(ammo.Scenarios)) for i, s := range ammo.Scenarios { - scenarios[i] = ScenarioConfig(s) + scenarios[i] = ScenarioConfig{ + Name: s.Name, + Requests: s.Requests, + } + if s.Weight != nil { + scenarios[i].Weight = *s.Weight + } + if s.MinWaitingTime != nil { + scenarios[i].MinWaitingTime = *s.MinWaitingTime + } } } @@ -346,7 +355,14 @@ func ConvertAmmoToHCL(ammo AmmoConfig) (AmmoHCL, error) { if len(ammo.Scenarios) > 0 { scenarios = make([]ScenarioHCL, len(ammo.Scenarios)) for i, s := range ammo.Scenarios { - scenarios[i] = ScenarioHCL(s) + weight := s.Weight + minWaitingTime := s.MinWaitingTime + scenarios[i] = ScenarioHCL{ + Name: s.Name, + Requests: s.Requests, + Weight: &weight, + MinWaitingTime: &minWaitingTime, + } } } diff --git a/components/providers/http_scenario/ammo_hcl_test.go b/components/providers/http_scenario/ammo_hcl_test.go index 5e0aca7bc..09cbbd7a8 100644 --- a/components/providers/http_scenario/ammo_hcl_test.go +++ b/components/providers/http_scenario/ammo_hcl_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/yandex/pandora/components/providers/http_scenario/postprocessor" "github.com/yandex/pandora/core/plugin/pluginconfig" + "github.com/yandex/pandora/lib/pointer" ) func Test_convertingYamlToHCL(t *testing.T) { @@ -46,19 +47,18 @@ func Test_convertingYamlToHCL(t *testing.T) { } func Example_encodeAmmoHCLVariablesSources() { - stringPointer := func(in string) *string { return &in } app := AmmoHCL{ VariableSources: []SourceHCL{ { Type: "file/csv", Name: "user_srs", - File: stringPointer("users.json"), + File: pointer.ToString("users.json"), Fields: &([]string{"id", "name", "email"}), }, { Type: "file/json", Name: "data_srs", - File: stringPointer("datas.json"), + File: pointer.ToString("datas.json"), Fields: &([]string{"id", "name", "email"}), }, }, @@ -82,7 +82,6 @@ func Example_encodeAmmoHCLVariablesSources() { } func Test_decodeHCL(t *testing.T) { - fs := afero.NewOsFs() file, err := fs.Open("decode_sample_config_test.hcl") require.NoError(t, err) @@ -91,14 +90,22 @@ func Test_decodeHCL(t *testing.T) { ammoHCL, err := ParseHCLFile(file) require.NoError(t, err) - assert.Equal(t, "scenario1", ammoHCL.Scenarios[0].Name) - assert.Len(t, ammoHCL.Scenarios[0].Shoots, 5) - assert.Equal(t, "scenario2", ammoHCL.Scenarios[1].Name) - assert.Len(t, ammoHCL.Scenarios[1].Shoots, 5) + assert.Len(t, ammoHCL.Scenarios, 2) + assert.Equal(t, ammoHCL.Scenarios[0], ScenarioHCL{ + Name: "scenario1", + Weight: pointer.ToInt64(50), + MinWaitingTime: pointer.ToInt64(500), + Requests: []string{"auth_req(1)", "sleep(100)", "list_req(1)", "sleep(100)", "item_req(3)"}, + }) + assert.Equal(t, ammoHCL.Scenarios[1], ScenarioHCL{ + Name: "scenario2", + Weight: nil, + MinWaitingTime: nil, + Requests: []string{"auth_req(1)", "sleep(100)", "list_req(1)", "sleep(100)", "item_req(2)"}, + }) } func TestConvertHCLToAmmo(t *testing.T) { - stringPointer := func(in string) *string { return &in } fs := afero.NewMemMapFs() templater := "html" tests := []struct { @@ -111,7 +118,7 @@ func TestConvertHCLToAmmo(t *testing.T) { name: "BasicConversion", ammo: AmmoHCL{ VariableSources: []SourceHCL{ - {Name: "source1", Type: "file/json", File: stringPointer("data.json")}, + {Name: "source1", Type: "file/json", File: pointer.ToString("data.json")}, }, Requests: []RequestHCL{ { @@ -126,7 +133,7 @@ func TestConvertHCLToAmmo(t *testing.T) { }, }, Scenarios: []ScenarioHCL{ - {Name: "scenario1", Weight: 1, MinWaitingTime: 1000, Shoots: []string{"shoot1"}}, + {Name: "scenario1", Weight: pointer.ToInt64(1), MinWaitingTime: pointer.ToInt64(1000), Requests: []string{"shoot1"}}, }, }, want: AmmoConfig{ @@ -147,7 +154,7 @@ func TestConvertHCLToAmmo(t *testing.T) { }, }, Scenarios: []ScenarioConfig{ - {Name: "scenario1", Weight: 1, MinWaitingTime: 1000, Shoots: []string{"shoot1"}}, + {Name: "scenario1", Weight: 1, MinWaitingTime: 1000, Requests: []string{"shoot1"}}, }, }, wantErr: false, @@ -156,7 +163,7 @@ func TestConvertHCLToAmmo(t *testing.T) { name: "UnsupportedVariableSourceType", ammo: AmmoHCL{ VariableSources: []SourceHCL{ - {Name: "source1", Type: "unknown", File: stringPointer("data.csv")}, + {Name: "source1", Type: "unknown", File: pointer.ToString("data.csv")}, }, }, want: AmmoConfig{}, @@ -181,8 +188,8 @@ func TestConvertHCLToAmmo(t *testing.T) { name: "MultipleVariableSources", ammo: AmmoHCL{ VariableSources: []SourceHCL{ - {Name: "source1", Type: "file/json", File: stringPointer("data.json")}, - {Name: "source2", Type: "file/csv", File: stringPointer("data.csv")}, + {Name: "source1", Type: "file/json", File: pointer.ToString("data.json")}, + {Name: "source2", Type: "file/csv", File: pointer.ToString("data.csv")}, }, }, want: AmmoConfig{ @@ -215,15 +222,15 @@ func TestConvertHCLToAmmo(t *testing.T) { Scenarios: []ScenarioHCL{ { Name: "scenario1", - Weight: 2, - MinWaitingTime: 2000, - Shoots: []string{"shoot1", "shoot2"}, + Weight: pointer.ToInt64(2), + MinWaitingTime: pointer.ToInt64(2000), + Requests: []string{"shoot1", "shoot2"}, }, { Name: "scenario2", - Weight: 1, - MinWaitingTime: 1000, - Shoots: []string{"shoot3"}, + Weight: pointer.ToInt64(1), + MinWaitingTime: pointer.ToInt64(1000), + Requests: []string{"shoot3"}, }, }, }, @@ -233,13 +240,13 @@ func TestConvertHCLToAmmo(t *testing.T) { Name: "scenario1", Weight: 2, MinWaitingTime: 2000, - Shoots: []string{"shoot1", "shoot2"}, + Requests: []string{"shoot1", "shoot2"}, }, { Name: "scenario2", Weight: 1, MinWaitingTime: 1000, - Shoots: []string{"shoot3"}, + Requests: []string{"shoot3"}, }, }, }, @@ -272,7 +279,6 @@ func (u unsupportedPostprocessor) Process(_ *http.Response, _ io.Reader) (map[st } func TestConvertAmmoToHCL(t *testing.T) { - stringPointer := func(in string) *string { return &in } False := false True := true delimiter := "," @@ -292,18 +298,18 @@ func TestConvertAmmoToHCL(t *testing.T) { {Name: "req1", Method: "GET", URI: "/api"}, }, Scenarios: []ScenarioConfig{ - {Name: "scenario1", Weight: 1, MinWaitingTime: 1000, Shoots: []string{"shoot1"}}, + {Name: "scenario1", Weight: 1, MinWaitingTime: 1000, Requests: []string{"shoot1"}}, }, }, want: AmmoHCL{ VariableSources: []SourceHCL{ - {Name: "source1", Type: "file/json", File: stringPointer("data.json")}, + {Name: "source1", Type: "file/json", File: pointer.ToString("data.json")}, }, Requests: []RequestHCL{ - {Name: "req1", Method: "GET", URI: "/api", Templater: stringPointer("text")}, + {Name: "req1", Method: "GET", URI: "/api", Templater: pointer.ToString("text")}, }, Scenarios: []ScenarioHCL{ - {Name: "scenario1", Weight: 1, MinWaitingTime: 1000, Shoots: []string{"shoot1"}}, + {Name: "scenario1", Weight: pointer.ToInt64(1), MinWaitingTime: pointer.ToInt64(1000), Requests: []string{"shoot1"}}, }, }, wantErr: false, @@ -343,8 +349,8 @@ func TestConvertAmmoToHCL(t *testing.T) { }, want: AmmoHCL{ VariableSources: []SourceHCL{ - {Name: "source1", Type: "file/json", File: stringPointer("data.json")}, - {Name: "source2", Type: "file/csv", File: stringPointer("data.csv"), IgnoreFirstLine: &False, Delimiter: &delimiter, Fields: nil}, + {Name: "source1", Type: "file/json", File: pointer.ToString("data.json")}, + {Name: "source2", Type: "file/csv", File: pointer.ToString("data.csv"), IgnoreFirstLine: &False, Delimiter: &delimiter, Fields: nil}, }, }, wantErr: false, @@ -360,9 +366,9 @@ func TestConvertAmmoToHCL(t *testing.T) { }, want: AmmoHCL{ VariableSources: []SourceHCL{ - {Name: "source2", Type: "file/csv", File: stringPointer("data.csv"), IgnoreFirstLine: &True, Delimiter: &delimiter, Fields: &([]string{"field1", "field2"})}, - {Name: "source2", Type: "file/csv", File: stringPointer("data.csv"), IgnoreFirstLine: &True, Delimiter: &delimiter, Fields: &([]string{"field3", "field4"})}, - {Name: "source1", Type: "file/json", File: stringPointer("data.json")}, + {Name: "source2", Type: "file/csv", File: pointer.ToString("data.csv"), IgnoreFirstLine: &True, Delimiter: &delimiter, Fields: &([]string{"field1", "field2"})}, + {Name: "source2", Type: "file/csv", File: pointer.ToString("data.csv"), IgnoreFirstLine: &True, Delimiter: &delimiter, Fields: &([]string{"field3", "field4"})}, + {Name: "source1", Type: "file/json", File: pointer.ToString("data.json")}, }, }, wantErr: false, @@ -377,8 +383,8 @@ func TestConvertAmmoToHCL(t *testing.T) { }, want: AmmoHCL{ Requests: []RequestHCL{ - {Name: "req1", Method: "GET", URI: "/api/1", Templater: stringPointer("text")}, - {Name: "req2", Method: "POST", URI: "/api/2", Templater: stringPointer("html")}, + {Name: "req1", Method: "GET", URI: "/api/1", Templater: pointer.ToString("text")}, + {Name: "req2", Method: "POST", URI: "/api/2", Templater: pointer.ToString("html")}, }, }, wantErr: false, @@ -387,14 +393,14 @@ func TestConvertAmmoToHCL(t *testing.T) { name: "ComplexScenario", ammo: AmmoConfig{ Scenarios: []ScenarioConfig{ - {Name: "scenario1", Weight: 2, MinWaitingTime: 2000, Shoots: []string{"shoot1", "shoot2"}}, - {Name: "scenario2", Weight: 1, MinWaitingTime: 1000, Shoots: []string{"shoot3"}}, + {Name: "scenario1", Weight: 2, MinWaitingTime: 2000, Requests: []string{"shoot1", "shoot2"}}, + {Name: "scenario2", Weight: 1, MinWaitingTime: 1000, Requests: []string{"shoot3"}}, }, }, want: AmmoHCL{ Scenarios: []ScenarioHCL{ - {Name: "scenario1", Weight: 2, MinWaitingTime: 2000, Shoots: []string{"shoot1", "shoot2"}}, - {Name: "scenario2", Weight: 1, MinWaitingTime: 1000, Shoots: []string{"shoot3"}}, + {Name: "scenario1", Weight: pointer.ToInt64(2), MinWaitingTime: pointer.ToInt64(2000), Requests: []string{"shoot1", "shoot2"}}, + {Name: "scenario2", Weight: pointer.ToInt64(1), MinWaitingTime: pointer.ToInt64(1000), Requests: []string{"shoot3"}}, }, }, wantErr: false, diff --git a/components/providers/http_scenario/decode.go b/components/providers/http_scenario/decode.go index 949d3b795..fbed6f143 100644 --- a/components/providers/http_scenario/decode.go +++ b/components/providers/http_scenario/decode.go @@ -70,7 +70,7 @@ func decodeAmmo(cfg AmmoConfig, storage SourceStorage) ([]*Ammo, error) { func convertScenarioToAmmo(sc ScenarioConfig, reqs map[string]RequestConfig) (*Ammo, error) { iter := mp.NewNextIterator(time.Now().UnixNano()) result := &Ammo{name: sc.Name, minWaitingTime: time.Millisecond * time.Duration(sc.MinWaitingTime)} - for _, sh := range sc.Shoots { + for _, sh := range sc.Requests { name, cnt, err := parseShootName(sh) if err != nil { return nil, fmt.Errorf("failed to parse shoot %s: %w", sh, err) @@ -142,9 +142,12 @@ func spreadNames(input []ScenarioConfig) (map[string]int, int) { scenarioRegistry := map[string]ScenarioConfig{} weights := make([]int64, len(input)) - for i, sc := range input { - scenarioRegistry[sc.Name] = sc - weights[i] = sc.Weight + for i := range input { + scenarioRegistry[input[i].Name] = input[i] + if input[i].Weight == 0 { + input[i].Weight = 1 + } + weights[i] = input[i].Weight } div := math.GCDM(weights...) diff --git a/components/providers/http_scenario/decode_sample_config_test.golden.hcl b/components/providers/http_scenario/decode_sample_config_test.golden.hcl index 0eac86dda..3c61c1278 100644 --- a/components/providers/http_scenario/decode_sample_config_test.golden.hcl +++ b/components/providers/http_scenario/decode_sample_config_test.golden.hcl @@ -108,10 +108,10 @@ request "item_req" { scenario "scenario1" { weight = 50 min_waiting_time = 500 - shoot = ["auth_req(1)", "sleep(100)", "list_req(1)", "sleep(100)", "item_req(3)"] + requests = ["auth_req(1)", "sleep(100)", "list_req(1)", "sleep(100)", "item_req(3)"] } scenario "scenario2" { weight = 40 min_waiting_time = 400 - shoot = ["auth_req(2)", "sleep(200)", "list_req(2)", "sleep(200)", "item_req(4)"] + requests = ["auth_req(2)", "sleep(200)", "list_req(2, 100)", "sleep(200)", "item_req(4)"] } diff --git a/components/providers/http_scenario/decode_sample_config_test.hcl b/components/providers/http_scenario/decode_sample_config_test.hcl index e1c161551..1522187dc 100644 --- a/components/providers/http_scenario/decode_sample_config_test.hcl +++ b/components/providers/http_scenario/decode_sample_config_test.hcl @@ -101,7 +101,7 @@ EOF scenario "scenario1" { weight = 50 min_waiting_time = 500 - shoot = [ + requests = [ "auth_req(1)", "sleep(100)", "list_req(1)", @@ -110,9 +110,7 @@ scenario "scenario1" { ] } scenario "scenario2" { - weight = 50 - min_waiting_time = 500 - shoot = [ + requests = [ "auth_req(1)", "sleep(100)", "list_req(1)", diff --git a/components/providers/http_scenario/decode_sample_config_test.yml b/components/providers/http_scenario/decode_sample_config_test.yml index 656111c9a..e8c575c48 100644 --- a/components/providers/http_scenario/decode_sample_config_test.yml +++ b/components/providers/http_scenario/decode_sample_config_test.yml @@ -88,7 +88,7 @@ scenarios: - name: scenario1 weight: 50 min_waiting_time: 500 - shoots: [ + requests: [ auth_req(1), sleep(100), list_req(1), @@ -98,10 +98,10 @@ scenarios: - name: scenario2 weight: 40 min_waiting_time: 400 - shoots: [ + requests: [ auth_req(2), sleep(200), - list_req(2), + "list_req(2, 100)", sleep(200), item_req(4) ] \ No newline at end of file diff --git a/components/providers/http_scenario/decode_test.go b/components/providers/http_scenario/decode_test.go index abc446f6d..2488ad0de 100644 --- a/components/providers/http_scenario/decode_test.go +++ b/components/providers/http_scenario/decode_test.go @@ -67,6 +67,18 @@ func Test_spreadNames(t *testing.T) { want: map[string]int{"a": 1}, wantTotal: 1, }, + { + name: "", + input: []ScenarioConfig{{Name: "a", Weight: 0}}, + want: map[string]int{"a": 1}, + wantTotal: 1, + }, + { + name: "", + input: []ScenarioConfig{{Name: "a", Weight: 0}, {Name: "b", Weight: 1}}, + want: map[string]int{"a": 1, "b": 1}, + wantTotal: 2, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -79,23 +91,24 @@ func Test_spreadNames(t *testing.T) { func TestParseShootName(t *testing.T) { testCases := []struct { - input string - wantName string - wantCnt int - wantErr bool + input string + wantName string + wantCnt int + wantSleep int + wantErr bool }{ - {"shoot", "shoot", 1, false}, - {"shoot(5)", "shoot", 5, false}, - {"shoot(3,4,5)", "shoot", 3, false}, - {"shoot(5,6)", "shoot", 5, false}, - {"space test(7)", "space test", 7, false}, - {"symbol#(3)", "symbol#", 3, false}, - {"shoot( 9 )", "shoot", 9, false}, - {"shoot (6)", "shoot", 6, false}, - {"shoot()", "shoot", 1, false}, - {"shoot(abc)", "", 0, true}, - {"shoot(6", "", 0, true}, - {"shoot(6),", "", 0, true}, + {"shoot", "shoot", 1, 0, false}, + {"shoot(5)", "shoot", 5, 0, false}, + {"shoot(3,4,5)", "shoot", 3, 4, false}, + {"shoot(5,6)", "shoot", 5, 6, false}, + {"space test(7)", "space test", 7, 0, false}, + {"symbol#(3)", "symbol#", 3, 0, false}, + {"shoot( 9 )", "shoot", 9, 0, false}, + {"shoot (6)", "shoot", 6, 0, false}, + {"shoot()", "shoot", 1, 0, false}, + {"shoot(abc)", "", 0, 0, true}, + {"shoot(6", "", 0, 0, true}, + {"shoot(6),", "", 0, 0, true}, } for _, tc := range testCases { @@ -142,12 +155,12 @@ func Test_convertScenarioToAmmo(t *testing.T) { wantErr bool }{ { - name: "", + name: "default", sc: ScenarioConfig{ Name: "testScenario", Weight: 1, MinWaitingTime: 1000, - Shoots: []string{ + Requests: []string{ "req1", "req2", "req2(2)", @@ -166,13 +179,39 @@ func Test_convertScenarioToAmmo(t *testing.T) { }, wantErr: false, }, + { + name: "with cycle sleep", + sc: ScenarioConfig{ + Name: "testScenario", + Weight: 1, + MinWaitingTime: 1000, + Requests: []string{ + "req1", + "req2", + "req2(3, 100)", + "sleep(500)", + }, + }, + want: &Ammo{ + name: "testScenario", + minWaitingTime: time.Millisecond * 1000, + Requests: []Request{ + convertConfigToRequestWithSleep(req1, 0), + convertConfigToRequestWithSleep(req2, 0), + convertConfigToRequestWithSleep(req2, 0), + convertConfigToRequestWithSleep(req2, 0), + convertConfigToRequestWithSleep(req2, time.Millisecond*500), + }, + }, + wantErr: false, + }, { name: "Scenario with unknown request", sc: ScenarioConfig{ Name: "unknownScenario", Weight: 1, MinWaitingTime: 1000, - Shoots: []string{ + Requests: []string{ "unknownReq", }, }, diff --git a/lib/pointer/pointer.go b/lib/pointer/pointer.go new file mode 100644 index 000000000..ccf1e9dd4 --- /dev/null +++ b/lib/pointer/pointer.go @@ -0,0 +1,13 @@ +package pointer + +func ToString(s string) *string { + return &s +} + +func ToBool(b bool) *bool { + return &b +} + +func ToInt64(b int64) *int64 { + return &b +}