Path: blob/main/pkg/metrics/instance/group_manager_test.go
4094 views
package instance12import (3"fmt"4"strings"5"testing"67"github.com/stretchr/testify/require"8)910func TestGroupManager_ListInstances_Configs(t *testing.T) {11gm := NewGroupManager(newFakeManager())1213// Create two configs in the same group and one in another14// group.15configs := []string{16`17name: configA18scrape_configs: []19remote_write: []`,20`21name: configB22scrape_configs: []23remote_write: []`,24`25name: configC26scrape_configs: []27remote_write:28- url: http://localhost:9090`,29}3031for _, cfg := range configs {32c := testUnmarshalConfig(t, cfg)33err := gm.ApplyConfig(c)34require.NoError(t, err)35}3637// ListInstances should return our grouped instances38insts := gm.ListInstances()39require.Equal(t, 2, len(insts))4041// ...but ListConfigs should return the ungrouped configs.42confs := gm.ListConfigs()43require.Equal(t, 3, len(confs))44require.Containsf(t, confs, "configA", "configA not in confs")45require.Containsf(t, confs, "configB", "configB not in confs")46require.Containsf(t, confs, "configC", "configC not in confs")47}4849func testUnmarshalConfig(t *testing.T, cfg string) Config {50c, err := UnmarshalConfig(strings.NewReader(cfg))51require.NoError(t, err)52return *c53}5455func TestGroupManager_ApplyConfig(t *testing.T) {56t.Run("combining configs", func(t *testing.T) {57inner := newFakeManager()58gm := NewGroupManager(inner)59err := gm.ApplyConfig(testUnmarshalConfig(t, `60name: configA61scrape_configs: []62remote_write: []63`))64require.NoError(t, err)6566err = gm.ApplyConfig(testUnmarshalConfig(t, `67name: configB68scrape_configs:69- job_name: test_job70static_configs:71- targets: [127.0.0.1:12345]72remote_write: []73`))74require.NoError(t, err)7576require.Equal(t, 1, len(gm.groups))77require.Equal(t, 2, len(gm.groupLookup))7879// Check the underlying grouped config and make sure it was updated.80expect := testUnmarshalConfig(t, fmt.Sprintf(`81name: %s82scrape_configs:83- job_name: test_job84static_configs:85- targets: [127.0.0.1:12345]86remote_write: []87`, gm.groupLookup["configA"]))8889innerConfigs := inner.ListConfigs()90require.Equal(t, 1, len(innerConfigs))91require.Equal(t, expect, innerConfigs[gm.groupLookup["configA"]])92})9394t.Run("updating existing config within group", func(t *testing.T) {95inner := newFakeManager()96gm := NewGroupManager(inner)97err := gm.ApplyConfig(testUnmarshalConfig(t, `98name: configA99scrape_configs: []100remote_write: []101`))102require.NoError(t, err)103require.Equal(t, 1, len(gm.groups))104require.Equal(t, 1, len(gm.groupLookup))105106err = gm.ApplyConfig(testUnmarshalConfig(t, `107name: configA108scrape_configs:109- job_name: test_job110static_configs:111- targets: [127.0.0.1:12345]112remote_write: []113`))114require.NoError(t, err)115require.Equal(t, 1, len(gm.groups))116require.Equal(t, 1, len(gm.groupLookup))117118// Check the underlying grouped config and make sure it was updated.119expect := testUnmarshalConfig(t, fmt.Sprintf(`120name: %s121scrape_configs:122- job_name: test_job123static_configs:124- targets: [127.0.0.1:12345]125remote_write: []126`, gm.groupLookup["configA"]))127actual := inner.ListConfigs()[gm.groupLookup["configA"]]128require.Equal(t, expect, actual)129})130131t.Run("updating existing config to new group", func(t *testing.T) {132inner := newFakeManager()133gm := NewGroupManager(inner)134err := gm.ApplyConfig(testUnmarshalConfig(t, `135name: configA136scrape_configs: []137remote_write: []138`))139require.NoError(t, err)140require.Equal(t, 1, len(gm.groups))141require.Equal(t, 1, len(gm.groupLookup))142oldGroup := gm.groupLookup["configA"]143144// Reapply the config but give it a setting change that would145// force it into a new group. We should still have only one146// group and only one entry in the group lookup table.147err = gm.ApplyConfig(testUnmarshalConfig(t, `148name: configA149host_filter: true150scrape_configs: []151remote_write: []152`))153require.NoError(t, err)154require.Equal(t, 1, len(gm.groups))155require.Equal(t, 1, len(gm.groupLookup))156newGroup := gm.groupLookup["configA"]157158// Check the underlying grouped config and make sure it was updated.159expect := testUnmarshalConfig(t, fmt.Sprintf(`160name: %s161host_filter: true162scrape_configs: []163remote_write: []164`, gm.groupLookup["configA"]))165actual := inner.ListConfigs()[newGroup]166require.Equal(t, expect, actual)167168// The old underlying ngroup should be gone.169require.NotContains(t, inner.ListConfigs(), oldGroup)170require.Equal(t, 1, len(inner.ListConfigs()))171})172}173174func TestGroupManager_ApplyConfig_RemoteWriteName(t *testing.T) {175inner := newFakeManager()176gm := NewGroupManager(inner)177err := gm.ApplyConfig(testUnmarshalConfig(t, `178name: configA179scrape_configs: []180remote_write:181- name: rw-cfg-a182url: http://localhost:9009/api/prom/push183`))184require.NoError(t, err)185186require.Equal(t, 1, len(gm.groups))187require.Equal(t, 1, len(gm.groupLookup))188189// Check the underlying grouped config and make sure the group_name190// didn't get copied from the remote_name of A.191innerConfigs := inner.ListConfigs()192require.Equal(t, 1, len(innerConfigs))193194cfg := innerConfigs[gm.groupLookup["configA"]]195require.NotEqual(t, "rw-cfg-a", cfg.RemoteWrite[0].Name)196}197198func TestGroupManager_DeleteConfig(t *testing.T) {199t.Run("partial delete", func(t *testing.T) {200inner := newFakeManager()201gm := NewGroupManager(inner)202203// Apply two configs in the same group and then delete one. The group204// should still be active with the one config inside of it.205err := gm.ApplyConfig(testUnmarshalConfig(t, `206name: configA207scrape_configs:208- job_name: test_job209static_configs:210- targets: [127.0.0.1:12345]211remote_write: []212`))213require.NoError(t, err)214215err = gm.ApplyConfig(testUnmarshalConfig(t, `216name: configB217scrape_configs:218- job_name: test_job2219static_configs:220- targets: [127.0.0.1:12345]221remote_write: []222`))223require.NoError(t, err)224225err = gm.DeleteConfig("configA")226require.NoError(t, err)227228expect := testUnmarshalConfig(t, fmt.Sprintf(`229name: %s230scrape_configs:231- job_name: test_job2232static_configs:233- targets: [127.0.0.1:12345]234remote_write: []`, gm.groupLookup["configB"]))235actual := inner.ListConfigs()[gm.groupLookup["configB"]]236require.Equal(t, expect, actual)237require.Equal(t, 1, len(gm.groups))238require.Equal(t, 1, len(gm.groupLookup))239})240241t.Run("full delete", func(t *testing.T) {242inner := newFakeManager()243gm := NewGroupManager(inner)244245// Apply a single config but delete the entire group.246err := gm.ApplyConfig(testUnmarshalConfig(t, `247name: configA248scrape_configs:249- job_name: test_job250static_configs:251- targets: [127.0.0.1:12345]252remote_write: []253`))254require.NoError(t, err)255256err = gm.DeleteConfig("configA")257require.NoError(t, err)258require.Equal(t, 0, len(inner.ListConfigs()))259require.Equal(t, 0, len(inner.ListInstances()))260require.Equal(t, 0, len(gm.groups))261require.Equal(t, 0, len(gm.groupLookup))262})263}264265func newFakeManager() Manager {266instances := make(map[string]ManagedInstance)267configs := make(map[string]Config)268269return &MockManager{270ListInstancesFunc: func() map[string]ManagedInstance {271return instances272},273ListConfigsFunc: func() map[string]Config {274return configs275},276ApplyConfigFunc: func(c Config) error {277instances[c.Name] = &mockInstance{}278configs[c.Name] = c279return nil280},281DeleteConfigFunc: func(name string) error {282delete(instances, name)283delete(configs, name)284return nil285},286StopFunc: func() {},287}288}289290func Test_hashConfig(t *testing.T) {291t.Run("name and scrape configs are ignored", func(t *testing.T) {292configAText := `293name: configA294scrape_configs: []295remote_write: []`296297configBText := `298name: configB299scrape_configs:300- job_name: test_job301static_configs:302- targets: [127.0.0.1:12345]303remote_write: []`304305hashA, hashB := getHashesFromConfigs(t, configAText, configBText)306require.Equal(t, hashA, hashB)307})308309t.Run("remote_writes are unordered", func(t *testing.T) {310configAText := `311name: configA312scrape_configs: []313remote_write:314- url: http://localhost:9009/api/prom/push1315- url: http://localhost:9009/api/prom/push2`316317configBText := `318name: configB319scrape_configs: []320remote_write:321- url: http://localhost:9009/api/prom/push2322- url: http://localhost:9009/api/prom/push1`323324hashA, hashB := getHashesFromConfigs(t, configAText, configBText)325require.Equal(t, hashA, hashB)326})327328t.Run("remote_writes must match", func(t *testing.T) {329configAText := `330name: configA331scrape_configs: []332remote_write:333- url: http://localhost:9009/api/prom/push1334- url: http://localhost:9009/api/prom/push2`335336configBText := `337name: configB338scrape_configs: []339remote_write:340- url: http://localhost:9009/api/prom/push1341- url: http://localhost:9009/api/prom/push1`342343hashA, hashB := getHashesFromConfigs(t, configAText, configBText)344require.NotEqual(t, hashA, hashB)345})346347t.Run("other fields must match", func(t *testing.T) {348configAText := `349name: configA350host_filter: true351scrape_configs: []352remote_write: []`353354configBText := `355name: configB356host_filter: false357scrape_configs: []358remote_write: []`359360hashA, hashB := getHashesFromConfigs(t, configAText, configBText)361require.NotEqual(t, hashA, hashB)362})363}364365func getHashesFromConfigs(t *testing.T, configAText, configBText string) (string, string) {366configA := testUnmarshalConfig(t, configAText)367configB := testUnmarshalConfig(t, configBText)368369hashA, err := hashConfig(configA)370require.NoError(t, err)371372hashB, err := hashConfig(configB)373require.NoError(t, err)374375return hashA, hashB376}377378func Test_groupConfigs(t *testing.T) {379configAText := `380name: configA381scrape_configs:382- job_name: test_job383static_configs:384- targets: [127.0.0.1:12345]385remote_write:386- url: http://localhost:9009/api/prom/push1387- url: http://localhost:9009/api/prom/push2`388389configBText := `390name: configB391scrape_configs:392- job_name: test_job2393static_configs:394- targets: [127.0.0.1:12345]395remote_write:396- url: http://localhost:9009/api/prom/push2397- url: http://localhost:9009/api/prom/push1`398399configA := testUnmarshalConfig(t, configAText)400configB := testUnmarshalConfig(t, configBText)401402groupName, err := hashConfig(configA)403require.NoError(t, err)404405expectText := fmt.Sprintf(`406name: %s407scrape_configs:408- job_name: test_job409static_configs:410- targets: [127.0.0.1:12345]411- job_name: test_job2412static_configs:413- targets: [127.0.0.1:12345]414remote_write:415- url: http://localhost:9009/api/prom/push1416- url: http://localhost:9009/api/prom/push2`, groupName)417418expect, err := UnmarshalConfig(strings.NewReader(expectText))419require.NoError(t, err)420421// Generate expected remote_write names422for _, rwConfig := range expect.RemoteWrite {423hash, err := getHash(rwConfig)424require.NoError(t, err)425rwConfig.Name = groupName[:6] + "-" + hash[:6]426}427428group := groupedConfigs{429"configA": configA,430"configB": configB,431}432actual, err := groupConfigs(groupName, group)433require.NoError(t, err)434require.Equal(t, *expect, actual)435436// Consistency check: groupedConfigs is a map and we want to always have437// groupConfigs return the same thing regardless of how the map438// is iterated over. Run through groupConfigs a bunch of times and439// make sure it always returns the same thing.440for i := 0; i < 100; i++ {441actual, err = groupConfigs(groupName, group)442require.NoError(t, err)443require.Equal(t, *expect, actual)444}445}446447448