@@ -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