Skip to content

Commit 6c79b52

Browse files
PMM-9400 Fix filters for PMM-9312 (#402) (#408)
* PMM-9400 Fix filters for PMM-9312 * Added a comment * Improvement for CR * Updates for CR * Fixes for CR * Changes for CR * Changes for CR
1 parent c1ae24f commit 6c79b52

File tree

3 files changed

+178
-65
lines changed

3 files changed

+178
-65
lines changed

exporter/collstats_collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (d *collstatsCollector) Collect(ch chan<- prometheus.Metric) {
4646
if d.discoveringMode {
4747
namespaces, err := listAllCollections(d.ctx, d.client, d.collections, systemDBs)
4848
if err != nil {
49-
d.logger.Errorf("cannot auto discover databases and collections")
49+
d.logger.Errorf("cannot auto discover databases and collections: %s", err.Error())
5050

5151
return
5252
}

exporter/common.go

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -91,42 +91,93 @@ func makeExcludeFilter(exclude []string) *primitive.E {
9191
return &primitive.E{Key: "$and", Value: filterExpressions}
9292
}
9393

94-
// makeDBsFilter creates a filter to list all databases or only the ones in the specified
95-
// namespaces list. Namespaces have the form of <db>.<collection> and the collection name
96-
// can have a dot. Example: db1.collection.one -> db: db1, collection: collection.one
97-
// db1, db2.col2, db3.col.one will produce [db1, db2, db3].
9894
func makeDBsFilter(filterInNamespaces []string) *primitive.E {
99-
dbs := []string{}
100-
if len(dbs) == 0 {
95+
filterExpressions := []bson.D{}
96+
97+
nss := removeEmptyStrings(filterInNamespaces)
98+
for _, namespace := range nss {
99+
parts := strings.Split(namespace, ".")
100+
filterExpressions = append(filterExpressions,
101+
bson.D{{Key: "name", Value: bson.D{{Key: "$eq", Value: parts[0]}}}},
102+
)
103+
}
104+
105+
if len(filterExpressions) == 0 {
101106
return nil
102107
}
103108

104-
for _, namespace := range filterInNamespaces {
105-
parts := strings.Split(namespace, ".")
106-
dbs = append(dbs, parts[0])
109+
return &primitive.E{Key: "$or", Value: filterExpressions}
110+
}
111+
112+
func removeEmptyStrings(items []string) []string {
113+
cleanList := []string{}
114+
115+
for _, item := range items {
116+
if item == "" {
117+
continue
118+
}
119+
cleanList = append(cleanList, item)
107120
}
108121

109-
return &primitive.E{Key: "name", Value: primitive.E{Key: "$in", Value: dbs}}
122+
return cleanList
123+
}
124+
125+
func unique(slice []string) []string {
126+
keys := make(map[string]bool)
127+
list := []string{}
128+
129+
for _, entry := range slice {
130+
if _, ok := keys[entry]; !ok {
131+
keys[entry] = true
132+
list = append(list, entry)
133+
}
134+
}
135+
136+
return list
110137
}
111138

112139
func listAllCollections(ctx context.Context, client *mongo.Client, filterInNamespaces []string, excludeDBs []string) (map[string][]string, error) {
113140
namespaces := make(map[string][]string)
114141

115-
for _, namespace := range filterInNamespaces {
116-
parts := strings.Split(namespace, ".")
117-
dbname := parts[0]
142+
dbs, err := databases(ctx, client, filterInNamespaces, excludeDBs)
143+
if err != nil {
144+
return nil, errors.Wrap(err, "cannot make the list of databases to list all collections")
145+
}
118146

119-
colls, err := listCollections(ctx, client, dbname, []string{namespace})
120-
if err != nil {
121-
return nil, errors.Wrapf(err, "cannot list the collections for %q", dbname)
122-
}
147+
filterNS := removeEmptyStrings(filterInNamespaces)
148+
149+
// If there are no specified namespaces to search for collections, it means all dbs should be included.
150+
if len(filterNS) == 0 {
151+
filterNS = append(filterNS, dbs...)
152+
}
153+
154+
for _, db := range dbs {
155+
for _, namespace := range filterNS {
156+
parts := strings.Split(namespace, ".")
157+
dbname := strings.TrimSpace(parts[0])
123158

124-
if _, ok := namespaces[dbname]; !ok {
125-
namespaces[dbname] = colls
126-
} else {
127-
namespaces[dbname] = append(namespaces[dbname], colls...)
159+
if dbname == "" || dbname != db {
160+
continue
161+
}
162+
163+
colls, err := listCollections(ctx, client, db, []string{namespace})
164+
if err != nil {
165+
return nil, errors.Wrapf(err, "cannot list the collections for %q", db)
166+
}
167+
168+
if _, ok := namespaces[db]; !ok {
169+
namespaces[db] = []string{}
170+
}
171+
172+
namespaces[db] = append(namespaces[db], colls...)
128173
}
129-
sort.Strings(namespaces[dbname]) // make it testeable.
174+
}
175+
176+
// Make it testable.
177+
for db, colls := range namespaces {
178+
uc := unique(colls)
179+
sort.Strings(uc)
180+
namespaces[db] = uc
130181
}
131182

132183
return namespaces, nil

exporter/common_test.go

Lines changed: 104 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,65 +8,127 @@ import (
88

99
"github.com/stretchr/testify/assert"
1010
"go.mongodb.org/mongo-driver/bson"
11+
"go.mongodb.org/mongo-driver/mongo"
1112

1213
"github.com/percona/mongodb_exporter/internal/tu"
1314
)
1415

15-
func TestListCollections(t *testing.T) {
16-
t.Parallel()
17-
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
18-
defer cancel()
19-
20-
client := tu.DefaultTestClient(ctx, t)
21-
22-
inDBs := []string{"testdb01", "testdb02"}
23-
inColls := []string{"col01", "col02", "colxx", "colyy"}
16+
//nolint:gochecknoglobals
17+
var (
18+
testDBs = []string{"testdb01", "testdb02"}
19+
testColls = []string{"col01", "col02", "colxx", "colyy"}
20+
)
2421

25-
defer func() {
26-
for _, dbname := range inDBs {
27-
client.Database(dbname).Drop(ctx) //nolint:errcheck
28-
}
29-
}()
22+
func setupDB(ctx context.Context, t *testing.T, client *mongo.Client) {
23+
t.Helper()
3024

31-
for _, dbname := range inDBs {
32-
for _, coll := range inColls {
25+
for _, dbname := range testDBs {
26+
for _, coll := range testColls {
3327
for j := 0; j < 10; j++ {
3428
_, err := client.Database(dbname).Collection(coll).InsertOne(ctx, bson.M{"f1": j, "f2": "2"})
3529
assert.NoError(t, err)
3630
}
3731
}
3832
}
33+
}
34+
35+
func cleanupDB(ctx context.Context, client *mongo.Client) {
36+
for _, dbname := range testDBs {
37+
client.Database(dbname).Drop(ctx) //nolint:errcheck
38+
}
39+
}
3940

40-
want := []string{"admin", "config", "local", "testdb01", "testdb02"}
41-
allDBs, err := databases(ctx, client, nil, nil)
42-
assert.NoError(t, err)
43-
assert.Equal(t, want, allDBs)
41+
//nolint:paralleltest
42+
func TestListDatabases(t *testing.T) {
43+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
44+
defer cancel()
4445

45-
want = []string{"col01", "col02", "colxx"}
46-
inNameSpaces := []string{inDBs[0] + ".col0", inDBs[0] + ".colx"}
47-
colls, err := listCollections(ctx, client, inDBs[0], inNameSpaces)
48-
sort.Strings(colls)
46+
client := tu.DefaultTestClient(ctx, t)
4947

50-
assert.NoError(t, err)
51-
assert.Equal(t, want, colls)
48+
setupDB(ctx, t, client)
49+
defer cleanupDB(ctx, client)
5250

53-
// Advanced filtering test
54-
wantNS := map[string][]string{
55-
"testdb01": {"col01", "col02", "colxx", "colyy"},
56-
"testdb02": {"col01", "col02"},
57-
}
51+
t.Run("Empty filter in list", func(t *testing.T) {
52+
want := []string{"testdb01", "testdb02"}
53+
allDBs, err := databases(ctx, client, nil, systemDBs)
54+
assert.NoError(t, err)
55+
assert.Equal(t, want, allDBs)
56+
})
5857

59-
// List all collections in testdb01 (inDBs[0]) but only col01 and col02 from testdb02.
60-
filterInNameSpaces := []string{inDBs[0], inDBs[1] + ".col01", inDBs[1] + ".col02"}
61-
namespaces, err := listAllCollections(ctx, client, filterInNameSpaces, systemDBs)
62-
assert.NoError(t, err)
63-
assert.Equal(t, wantNS, namespaces)
58+
t.Run("One collection in list", func(t *testing.T) {
59+
want := []string{"testdb01"}
60+
allDBs, err := databases(ctx, client, []string{"testdb01.col1"}, systemDBs)
61+
assert.NoError(t, err)
62+
assert.Equal(t, want, allDBs)
63+
})
64+
65+
t.Run("Multiple namespaces in list", func(t *testing.T) {
66+
want := []string{"testdb01", "testdb02"}
67+
allDBs, err := databases(ctx, client, []string{"testdb01", "testdb02.col2", "testdb02.col1"}, systemDBs)
68+
assert.NoError(t, err)
69+
assert.Equal(t, want, allDBs)
70+
})
71+
}
72+
73+
//nolint:paralleltest
74+
func TestListCollections(t *testing.T) {
75+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
76+
defer cancel()
77+
78+
client := tu.DefaultTestClient(ctx, t)
79+
80+
setupDB(ctx, t, client)
81+
defer cleanupDB(ctx, client)
82+
83+
t.Run("Basic test", func(t *testing.T) {
84+
want := []string{"admin", "config", "local", "testdb01", "testdb02"}
85+
allDBs, err := databases(ctx, client, nil, nil)
86+
assert.NoError(t, err)
87+
assert.Equal(t, want, allDBs)
88+
})
89+
90+
t.Run("Filter in databases", func(t *testing.T) {
91+
want := []string{"col01", "col02", "colxx"}
92+
inNameSpaces := []string{testDBs[0] + ".col0", testDBs[0] + ".colx"}
93+
colls, err := listCollections(ctx, client, testDBs[0], inNameSpaces)
94+
sort.Strings(colls)
95+
96+
assert.NoError(t, err)
97+
assert.Equal(t, want, colls)
98+
})
99+
100+
t.Run("With namespaces list", func(t *testing.T) {
101+
// Advanced filtering test
102+
wantNS := map[string][]string{
103+
"testdb01": {"col01", "col02", "colxx", "colyy"},
104+
"testdb02": {"col01", "col02"},
105+
}
106+
// List all collections in testdb01 (inDBs[0]) but only col01 and col02 from testdb02.
107+
filterInNameSpaces := []string{testDBs[0], testDBs[1] + ".col01", testDBs[1] + ".col02"}
108+
namespaces, err := listAllCollections(ctx, client, filterInNameSpaces, systemDBs)
109+
assert.NoError(t, err)
110+
assert.Equal(t, wantNS, namespaces)
111+
})
112+
113+
t.Run("Empty namespaces list", func(t *testing.T) {
114+
wantNS := map[string][]string{
115+
"testdb01": {"col01", "col02", "colxx", "colyy"},
116+
"testdb02": {"col01", "col02", "colxx", "colyy"},
117+
}
118+
namespaces, err := listAllCollections(ctx, client, nil, systemDBs)
119+
assert.NoError(t, err)
120+
assert.Equal(t, wantNS, namespaces)
121+
})
64122

65-
count, err := nonSystemCollectionsCount(ctx, client, nil, nil)
66-
assert.NoError(t, err)
67-
assert.True(t, count == 8)
123+
t.Run("Count basic", func(t *testing.T) {
124+
count, err := nonSystemCollectionsCount(ctx, client, nil, nil)
125+
assert.NoError(t, err)
126+
assert.Equal(t, 8, count)
127+
})
68128

69-
count, err = nonSystemCollectionsCount(ctx, client, nil, []string{inDBs[0] + ".col0", inDBs[0] + ".colx"})
70-
assert.NoError(t, err)
71-
assert.Equal(t, 6, count)
129+
t.Run("Filtered count", func(t *testing.T) {
130+
count, err := nonSystemCollectionsCount(ctx, client, nil, []string{testDBs[0] + ".col0", testDBs[0] + ".colx"})
131+
assert.NoError(t, err)
132+
assert.Equal(t, 6, count)
133+
})
72134
}

0 commit comments

Comments
 (0)