master
go 300 lines 7.08 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package mongo
4
5 import (
6 "context"
7 "fmt"
8 "time"
9
10 "go.mongodb.org/mongo-driver/bson"
11 "go.mongodb.org/mongo-driver/mongo"
12 "go.mongodb.org/mongo-driver/mongo/options"
13 )
14
15 const (
16 mongos = "mongos"
17 )
18
19 type mongoConn interface {
20 serverStatus() (*documentServerStatus, error)
21 listDatabaseNames() ([]string, error)
22 dbStats(name string) (*documentDBStats, error)
23 isReplicaSet() bool
24 isMongos() bool
25 replSetGetStatus() (*documentReplSetStatus, error)
26 shardNodes() (*documentShardNodesResult, error)
27 shardDatabasesPartitioning() (*documentPartitionedResult, error)
28 shardCollectionsPartitioning() (*documentPartitionedResult, error)
29 shardChunks() (map[string]int64, error)
30 initClient(uri string, timeout time.Duration) error
31 close() error
32 }
33
34 type mongoClient struct {
35 client *mongo.Client
36 timeout time.Duration
37 replicaSetFlag *bool
38 mongosFlag *bool
39 }
40
41 func (c *mongoClient) serverStatus() (*documentServerStatus, error) {
42 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
43 defer cancel()
44
45 cmd := bson.D{
46 {Key: "serverStatus", Value: 1},
47 {Key: "repl", Value: 1},
48 {Key: "metrics",
49 Value: bson.D{
50 {Key: "document", Value: true},
51 {Key: "cursor", Value: true},
52 {Key: "queryExecutor", Value: true},
53 {Key: "apiVersions", Value: false},
54 {Key: "aggStageCounters", Value: false},
55 {Key: "commands", Value: false},
56 {Key: "dotsAndDollarsFields", Value: false},
57 {Key: "getLastError", Value: false},
58 {Key: "mongos", Value: false},
59 {Key: "operation", Value: false},
60 {Key: "operatorCounters", Value: false},
61 {Key: "query", Value: false},
62 {Key: "record", Value: false},
63 {Key: "repl", Value: false},
64 {Key: "storage", Value: false},
65 {Key: "ttl", Value: false},
66 },
67 },
68 }
69 var status *documentServerStatus
70
71 err := c.client.Database("admin").RunCommand(ctx, cmd).Decode(&status)
72 if err != nil {
73 return nil, err
74 }
75
76 isReplSet := status.Repl != nil
77 c.replicaSetFlag = &isReplSet
78
79 isMongos := status.Process == mongos
80 c.mongosFlag = &isMongos
81
82 return status, err
83 }
84
85 func (c *mongoClient) listDatabaseNames() ([]string, error) {
86 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
87 defer cancel()
88
89 return c.client.ListDatabaseNames(ctx, bson.M{})
90 }
91
92 func (c *mongoClient) dbStats(name string) (*documentDBStats, error) {
93 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
94 defer cancel()
95
96 cmd := bson.M{"dbStats": 1}
97 var stats documentDBStats
98
99 if err := c.client.Database(name).RunCommand(ctx, cmd).Decode(&stats); err != nil {
100 return nil, err
101 }
102
103 return &stats, nil
104 }
105
106 func (c *mongoClient) isReplicaSet() bool {
107 if c.replicaSetFlag != nil {
108 return *c.replicaSetFlag
109 }
110
111 status, err := c.serverStatus()
112 if err != nil {
113 return false
114 }
115
116 return status.Repl != nil
117 }
118
119 func (c *mongoClient) isMongos() bool {
120 if c.mongosFlag != nil {
121 return *c.mongosFlag
122 }
123
124 status, err := c.serverStatus()
125 if err != nil {
126 return false
127 }
128
129 return status.Process == mongos
130 }
131
132 func (c *mongoClient) replSetGetStatus() (*documentReplSetStatus, error) {
133 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
134 defer cancel()
135
136 var status *documentReplSetStatus
137 cmd := bson.M{"replSetGetStatus": 1}
138
139 err := c.client.Database("admin").RunCommand(ctx, cmd).Decode(&status)
140 if err != nil {
141 return nil, err
142 }
143
144 return status, err
145 }
146
147 func (c *mongoClient) shardNodes() (*documentShardNodesResult, error) {
148 collection := "shards"
149 groupStage := bson.D{{Key: "$sortByCount", Value: "$state"}}
150
151 nodesByState, err := c.shardCollectAggregation(collection, []bson.D{groupStage})
152 if err != nil {
153 return nil, err
154 }
155
156 return &documentShardNodesResult{nodesByState.True, nodesByState.False}, nil
157 }
158
159 func (c *mongoClient) shardDatabasesPartitioning() (*documentPartitionedResult, error) {
160 collection := "databases"
161 groupStage := bson.D{{Key: "$sortByCount", Value: "$partitioned"}}
162
163 partitioning, err := c.shardCollectAggregation(collection, []bson.D{groupStage})
164 if err != nil {
165 return nil, err
166 }
167
168 return &documentPartitionedResult{partitioning.True, partitioning.False}, nil
169 }
170
171 func (c *mongoClient) shardCollectionsPartitioning() (*documentPartitionedResult, error) {
172 collection := "collections"
173 matchStage := bson.D{{Key: "$match", Value: bson.D{{Key: "dropped", Value: false}}}}
174 countStage := bson.D{{Key: "$sortByCount", Value: bson.D{{Key: "$eq", Value: bson.A{"$distributionMode", "sharded"}}}}}
175
176 partitioning, err := c.shardCollectAggregation(collection, []bson.D{matchStage, countStage})
177 if err != nil {
178 return nil, err
179 }
180
181 return &documentPartitionedResult{partitioning.True, partitioning.False}, nil
182 }
183
184 func (c *mongoClient) shardCollectAggregation(collection string, aggr []bson.D) (*documentAggrResult, error) {
185 rows, err := c.dbAggregate(collection, aggr)
186 if err != nil {
187 return nil, err
188 }
189
190 result := &documentAggrResult{}
191
192 for _, row := range rows {
193 if row.Bool {
194 result.True = row.Count
195 } else {
196 result.False = row.Count
197 }
198 }
199
200 return result, err
201 }
202
203 func (c *mongoClient) shardChunks() (map[string]int64, error) {
204 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
205 defer cancel()
206
207 col := c.client.Database("config").Collection("chunks")
208
209 cursor, err := col.Aggregate(ctx, mongo.Pipeline{bson.D{{Key: "$sortByCount", Value: "$shard"}}})
210 if err != nil {
211 return nil, err
212 }
213
214 var shards []bson.M
215 if err = cursor.All(ctx, &shards); err != nil {
216 return nil, err
217 }
218
219 defer func() { _ = cursor.Close(ctx) }()
220
221 result := map[string]int64{}
222
223 for _, row := range shards {
224 k, ok := row["_id"].(string)
225 if !ok {
226 return nil, fmt.Errorf("shard name is not a string: %v", row["_id"])
227 }
228 v, ok := row["count"].(int32)
229 if !ok {
230 return nil, fmt.Errorf("shard chunk count is not a int32: %v", row["count"])
231 }
232 result[k] = int64(v)
233 }
234
235 return result, err
236 }
237
238 func (c *mongoClient) initClient(uri string, timeout time.Duration) error {
239 if c.client != nil {
240 return nil
241 }
242
243 c.timeout = timeout
244
245 ctxConn, cancelConn := context.WithTimeout(context.Background(), c.timeout)
246 defer cancelConn()
247
248 client, err := mongo.Connect(ctxConn, options.Client().ApplyURI(uri))
249 if err != nil {
250 return err
251 }
252
253 ctxPing, cancelPing := context.WithTimeout(context.Background(), c.timeout)
254 defer cancelPing()
255
256 if err := client.Ping(ctxPing, nil); err != nil {
257 _ = client.Disconnect(ctxConn)
258 return err
259 }
260
261 c.client = client
262
263 return nil
264 }
265
266 func (c *mongoClient) close() error {
267 if c.client == nil {
268 return nil
269 }
270
271 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
272 defer cancel()
273
274 if err := c.client.Disconnect(ctx); err != nil {
275 return err
276 }
277
278 c.client = nil
279
280 return nil
281 }
282
283 func (c *mongoClient) dbAggregate(collection string, aggr []bson.D) ([]documentAggrResults, error) {
284 ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
285 defer cancel()
286
287 cursor, err := c.client.Database("config").Collection(collection).Aggregate(ctx, aggr)
288 if err != nil {
289 return nil, err
290 }
291
292 defer func() { _ = cursor.Close(ctx) }()
293
294 var rows []documentAggrResults
295 if err := cursor.All(ctx, &rows); err != nil {
296 return nil, err
297 }
298
299 return rows, nil
300 }