@@ -2,75 +2,119 @@ package mongo
22
33import (
44 "context"
5+ "errors"
56 "fmt"
7+ "slices"
68
7- "go.mongodb.org/mongo-driver/v2/bson"
89 "go.mongodb.org/mongo-driver/v2/mongo"
910)
1011
1112const (
1213 MinSupportedVersion = "5.1.0"
1314 MinOplogRetentionHours = 24
15+
16+ ReplicaSet = "ReplicaSet"
17+ ShardedCluster = "ShardedCluster"
1418)
1519
16- type BuildInfo struct {
17- Version string `bson:"version"`
18- }
20+ var RequiredRoles = [... ]string {"readAnyDatabase" , "clusterMonitor" }
1921
20- type ReplSetGetStatus struct {
21- Set string `bson:"set"`
22- MyState int `bson:"myState"`
23- }
22+ func ValidateServerCompatibility (ctx context.Context , client * mongo.Client ) error {
23+ buildInfo , err := GetBuildInfo (ctx , client )
24+ if err != nil {
25+ return err
26+ }
2427
25- type OplogTruncation struct {
26- OplogMinRetentionHours float64 `bson:"oplogMinRetentionHours"`
27- }
28+ if cmp , err := CompareServerVersions (buildInfo .Version , MinSupportedVersion ); err != nil {
29+ return err
30+ } else if cmp < 0 {
31+ return fmt .Errorf ("require minimum mongo version %s" , MinSupportedVersion )
32+ }
2833
29- type StorageEngine struct {
30- Name string `bson:"name"`
31- }
34+ validateStorageEngine := func (instanceCtx context.Context , instanceClient * mongo.Client ) error {
35+ ss , err := GetServerStatus (instanceCtx , instanceClient )
36+ if err != nil {
37+ return err
38+ }
3239
33- type ServerStatus struct {
34- StorageEngine StorageEngine `bson:"storageEngine"`
35- OplogTruncation OplogTruncation `bson:"oplogTruncation"`
40+ if ss .StorageEngine .Name != "wiredTiger" {
41+ return errors .New ("only wiredTiger storage engine is supported" )
42+ }
43+ return nil
44+ }
45+
46+ topologyType , err := GetTopologyType (ctx , client )
47+ if err != nil {
48+ return err
49+ }
50+
51+ if topologyType == ReplicaSet {
52+ return validateStorageEngine (ctx , client )
53+ } else {
54+ // TODO: run validation on shard
55+ return nil
56+ }
3657}
3758
38- func GetBuildInfo (ctx context.Context , client * mongo.Client ) ( * BuildInfo , error ) {
39- singleResult := client . Database ( "admin" ). RunCommand ( ctx , bson. D {bson. E { Key : "buildInfo" , Value : 1 }} )
40- if singleResult . Err () != nil {
41- return nil , fmt . Errorf ( "failed to run 'buildInfo' command: %w" , singleResult . Err ())
59+ func ValidateUserRoles (ctx context.Context , client * mongo.Client ) error {
60+ connectionStatus , err := GetConnectionStatus ( ctx , client )
61+ if err != nil {
62+ return err
4263 }
43- var info BuildInfo
44- if err := singleResult .Decode (& info ); err != nil {
45- return nil , fmt .Errorf ("failed to decode BuildInfo: %w" , err )
64+
65+ for _ , requiredRole := range RequiredRoles {
66+ if ! slices .ContainsFunc (connectionStatus .AuthInfo .AuthenticatedUserRoles , func (r Role ) bool {
67+ return r .Role == requiredRole
68+ }) {
69+ return fmt .Errorf ("missing required role: %s" , requiredRole )
70+ }
4671 }
47- return & info , nil
72+
73+ return nil
4874}
4975
50- func GetReplSetGetStatus (ctx context.Context , client * mongo.Client ) (* ReplSetGetStatus , error ) {
51- singleResult := client .Database ("admin" ).RunCommand (ctx , bson.D {
52- bson.E {Key : "replSetGetStatus" , Value : 1 },
53- })
54- if singleResult .Err () != nil {
55- return nil , fmt .Errorf ("failed to run 'replSetGetStatus' command: %w" , singleResult .Err ())
76+ func ValidateOplogRetention (ctx context.Context , client * mongo.Client ) error {
77+ validateOplogRetention := func (instanceCtx context.Context , instanceClient * mongo.Client ) error {
78+ ss , err := GetServerStatus (instanceCtx , instanceClient )
79+ if err != nil {
80+ return err
81+ }
82+ if ss .OplogTruncation .OplogMinRetentionHours == 0 ||
83+ ss .OplogTruncation .OplogMinRetentionHours < MinOplogRetentionHours {
84+ return fmt .Errorf ("oplog retention must be set to >= 24 hours, but got %f" ,
85+ ss .OplogTruncation .OplogMinRetentionHours )
86+ }
87+ return nil
5688 }
57- var status ReplSetGetStatus
58- if err := singleResult .Decode (& status ); err != nil {
59- return nil , fmt .Errorf ("failed to decode ReplSetGetStatus: %w" , err )
89+
90+ topology , err := GetTopologyType (ctx , client )
91+ if err != nil {
92+ return err
93+ }
94+ if topology == ReplicaSet {
95+ return validateOplogRetention (ctx , client )
96+ } else {
97+ // TODO: run validation on shard
98+ return nil
6099 }
61- return & status , nil
62100}
63101
64- func GetServerStatus (ctx context.Context , client * mongo.Client ) (* ServerStatus , error ) {
65- singleResult := client .Database ("admin" ).RunCommand (ctx , bson.D {
66- bson.E {Key : "serverStatus" , Value : 1 },
67- })
68- if singleResult .Err () != nil {
69- return nil , fmt .Errorf ("failed to run 'serverStatus' command: %w" , singleResult .Err ())
102+ func GetTopologyType (ctx context.Context , client * mongo.Client ) (string , error ) {
103+ hello , err := GetHelloResponse (ctx , client )
104+ if err != nil {
105+ return "" , err
106+ }
107+
108+ // Only replica set has 'hosts' field
109+ // https://www.mongodb.com/docs/manual/reference/command/hello/#mongodb-data-hello.hosts
110+ if len (hello .Hosts ) > 0 {
111+ return ReplicaSet , nil
70112 }
71- var status ServerStatus
72- if err := singleResult .Decode (& status ); err != nil {
73- return nil , fmt .Errorf ("failed to decode ServerStatus: %w" , err )
113+
114+ // Only sharded cluster has 'msg' field, and equals to 'isdbgrid'
115+ // https://www.mongodb.com/docs/manual/reference/command/hello/#mongodb-data-hello.msg
116+ if hello .Msg == "isdbgrid" {
117+ return ShardedCluster , nil
74118 }
75- return & status , nil
119+ return "" , errors . New ( "topology type must be ReplicaSet or ShardedCluster" )
76120}
0 commit comments