@@ -2,75 +2,125 @@ package mongo
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67
7- "go.mongodb.org/mongo-driver/v2/bson"
88 "go.mongodb.org/mongo-driver/v2/mongo"
99)
1010
1111const (
1212 MinSupportedVersion = "5.1.0"
1313 MinOplogRetentionHours = 24
14+
15+ ReplicaSet = "ReplicaSet"
16+ ShardedCluster = "ShardedCluster"
1417)
1518
16- type BuildInfo struct {
17- Version string `bson:"version"`
18- }
19+ func ValidateServerCompatibility (ctx context.Context , client * mongo.Client ) error {
20+ buildInfo , err := GetBuildInfo (ctx , client )
21+ if err != nil {
22+ return err
23+ }
1924
20- type ReplSetGetStatus struct {
21- Set string `bson:"set"`
22- MyState int `bson:"myState"`
23- }
25+ if cmp , err := CompareServerVersions (buildInfo .Version , MinSupportedVersion ); err != nil {
26+ return err
27+ } else if cmp < 0 {
28+ return fmt .Errorf ("require minimum mongo version %s" , MinSupportedVersion )
29+ }
2430
25- type OplogTruncation struct {
26- OplogMinRetentionHours float64 `bson:"oplogMinRetentionHours"`
27- }
31+ validateStorageEngine := func (instanceCtx context.Context , instanceClient * mongo.Client ) error {
32+ ss , err := GetServerStatus (instanceCtx , instanceClient )
33+ if err != nil {
34+ return err
35+ }
2836
29- type StorageEngine struct {
30- Name string `bson:"name"`
31- }
37+ if ss .StorageEngine .Name != "wiredTiger" {
38+ return errors .New ("only wiredTiger storage engine is supported" )
39+ }
40+ return nil
41+ }
3242
33- type ServerStatus struct {
34- StorageEngine StorageEngine `bson:"storageEngine"`
35- OplogTruncation OplogTruncation `bson:"oplogTruncation"`
43+ topologyType , err := GetTopologyType (ctx , client )
44+ if err != nil {
45+ return err
46+ }
47+
48+ if topologyType == ReplicaSet {
49+ return validateStorageEngine (ctx , client )
50+ } else {
51+ // TODO: run validation on shard
52+ return nil
53+ }
3654}
3755
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 ())
56+ func ValidateUserRoles (ctx context.Context , client * mongo.Client ) error {
57+ RequiredRoles := []string {"readAnyDatabase" , "clusterMonitor" }
58+
59+ connectionStatus , err := GetConnectionStatus (ctx , client )
60+ if err != nil {
61+ return err
4262 }
43- var info BuildInfo
44- if err := singleResult .Decode (& info ); err != nil {
45- return nil , fmt .Errorf ("failed to decode BuildInfo: %w" , err )
63+
64+ hasRole := func (roles []Role , targetRole string ) bool {
65+ for _ , role := range roles {
66+ if role .Role == targetRole {
67+ return true
68+ }
69+ }
70+ return false
4671 }
47- return & info , nil
72+
73+ for _ , requiredRole := range RequiredRoles {
74+ if ! hasRole (connectionStatus .AuthInfo .AuthenticatedUserRoles , requiredRole ) {
75+ return fmt .Errorf ("missing required role: %s" , requiredRole )
76+ }
77+ }
78+
79+ return nil
4880}
4981
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 ())
82+ func ValidateOplogRetention (ctx context.Context , client * mongo.Client ) error {
83+ validateOplogRetention := func (instanceCtx context.Context , instanceClient * mongo.Client ) error {
84+ ss , err := GetServerStatus (instanceCtx , instanceClient )
85+ if err != nil {
86+ return err
87+ }
88+ if ss .OplogTruncation .OplogMinRetentionHours == 0 ||
89+ ss .OplogTruncation .OplogMinRetentionHours < MinOplogRetentionHours {
90+ return fmt .Errorf ("oplog retention must be set to >= 24 hours, but got %f" ,
91+ ss .OplogTruncation .OplogMinRetentionHours )
92+ }
93+ return nil
5694 }
57- var status ReplSetGetStatus
58- if err := singleResult .Decode (& status ); err != nil {
59- return nil , fmt .Errorf ("failed to decode ReplSetGetStatus: %w" , err )
95+
96+ topology , err := GetTopologyType (ctx , client )
97+ if err != nil {
98+ return err
99+ }
100+ if topology == ReplicaSet {
101+ return validateOplogRetention (ctx , client )
102+ } else {
103+ // TODO: run validation on shard
104+ return nil
60105 }
61- return & status , nil
62106}
63107
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 ())
108+ func GetTopologyType (ctx context.Context , client * mongo.Client ) (string , error ) {
109+ hello , err := GetHelloResponse (ctx , client )
110+ if err != nil {
111+ return "" , err
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 replica set has 'hosts' field
115+ // https://www.mongodb.com/docs/manual/reference/command/hello/#mongodb-data-hello.hosts
116+ if len (hello .Hosts ) > 0 {
117+ return ReplicaSet , nil
118+ }
119+
120+ // Only sharded cluster has 'msg' field, and equals to 'isdbgrid'
121+ // https://www.mongodb.com/docs/manual/reference/command/hello/#mongodb-data-hello.msg
122+ if hello .Msg == "isdbgrid" {
123+ return ShardedCluster , nil
74124 }
75- return & status , nil
125+ return "" , errors . New ( "topology type must be ReplicaSet or ShardedCluster" )
76126}
0 commit comments