github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/mongo/description/server.go (about) 1 // Copyright (C) MongoDB, Inc. 2017-present. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may 4 // not use this file except in compliance with the License. You may obtain 5 // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 7 package description 8 9 import ( 10 "errors" 11 "fmt" 12 "time" 13 14 "go.mongodb.org/mongo-driver/bson" 15 "go.mongodb.org/mongo-driver/bson/primitive" 16 "go.mongodb.org/mongo-driver/internal" 17 "go.mongodb.org/mongo-driver/mongo/address" 18 "go.mongodb.org/mongo-driver/tag" 19 ) 20 21 // SelectedServer augments the Server type by also including the TopologyKind of the topology that includes the server. 22 // This type should be used to track the state of a server that was selected to perform an operation. 23 type SelectedServer struct { 24 Server 25 Kind TopologyKind 26 } 27 28 // Server contains information about a node in a cluster. This is created from hello command responses. If the value 29 // of the Kind field is LoadBalancer, only the Addr and Kind fields will be set. All other fields will be set to the 30 // zero value of the field's type. 31 type Server struct { 32 Addr address.Address 33 34 Arbiters []string 35 AverageRTT time.Duration 36 AverageRTTSet bool 37 Compression []string // compression methods returned by server 38 CanonicalAddr address.Address 39 ElectionID primitive.ObjectID 40 HeartbeatInterval time.Duration 41 HelloOK bool 42 Hosts []string 43 IsCryptd bool 44 LastError error 45 LastUpdateTime time.Time 46 LastWriteTime time.Time 47 MaxBatchCount uint32 48 MaxDocumentSize uint32 49 MaxMessageSize uint32 50 Members []address.Address 51 Passives []string 52 Passive bool 53 Primary address.Address 54 ReadOnly bool 55 ServiceID *primitive.ObjectID // Only set for servers that are deployed behind a load balancer. 56 SessionTimeoutMinutes uint32 57 SetName string 58 SetVersion uint32 59 Tags tag.Set 60 TopologyVersion *TopologyVersion 61 Kind ServerKind 62 WireVersion *VersionRange 63 } 64 65 // NewServer creates a new server description from the given hello command response. 66 func NewServer(addr address.Address, response bson.Raw) Server { 67 desc := Server{Addr: addr, CanonicalAddr: addr, LastUpdateTime: time.Now().UTC()} 68 elements, err := response.Elements() 69 if err != nil { 70 desc.LastError = err 71 return desc 72 } 73 var ok bool 74 var isReplicaSet, isWritablePrimary, hidden, secondary, arbiterOnly bool 75 var msg string 76 var versionRange VersionRange 77 for _, element := range elements { 78 switch element.Key() { 79 case "arbiters": 80 var err error 81 desc.Arbiters, err = internal.StringSliceFromRawElement(element) 82 if err != nil { 83 desc.LastError = err 84 return desc 85 } 86 case "arbiterOnly": 87 arbiterOnly, ok = element.Value().BooleanOK() 88 if !ok { 89 desc.LastError = fmt.Errorf("expected 'arbiterOnly' to be a boolean but it's a BSON %s", element.Value().Type) 90 return desc 91 } 92 case "compression": 93 var err error 94 desc.Compression, err = internal.StringSliceFromRawElement(element) 95 if err != nil { 96 desc.LastError = err 97 return desc 98 } 99 case "electionId": 100 desc.ElectionID, ok = element.Value().ObjectIDOK() 101 if !ok { 102 desc.LastError = fmt.Errorf("expected 'electionId' to be a objectID but it's a BSON %s", element.Value().Type) 103 return desc 104 } 105 case "iscryptd": 106 desc.IsCryptd, ok = element.Value().BooleanOK() 107 if !ok { 108 desc.LastError = fmt.Errorf("expected 'iscryptd' to be a boolean but it's a BSON %s", element.Value().Type) 109 return desc 110 } 111 case "helloOk": 112 desc.HelloOK, ok = element.Value().BooleanOK() 113 if !ok { 114 desc.LastError = fmt.Errorf("expected 'helloOk' to be a boolean but it's a BSON %s", element.Value().Type) 115 return desc 116 } 117 case "hidden": 118 hidden, ok = element.Value().BooleanOK() 119 if !ok { 120 desc.LastError = fmt.Errorf("expected 'hidden' to be a boolean but it's a BSON %s", element.Value().Type) 121 return desc 122 } 123 case "hosts": 124 var err error 125 desc.Hosts, err = internal.StringSliceFromRawElement(element) 126 if err != nil { 127 desc.LastError = err 128 return desc 129 } 130 case "isWritablePrimary": 131 isWritablePrimary, ok = element.Value().BooleanOK() 132 if !ok { 133 desc.LastError = fmt.Errorf("expected 'isWritablePrimary' to be a boolean but it's a BSON %s", element.Value().Type) 134 return desc 135 } 136 case internal.LegacyHelloLowercase: 137 isWritablePrimary, ok = element.Value().BooleanOK() 138 if !ok { 139 desc.LastError = fmt.Errorf("expected legacy hello to be a boolean but it's a BSON %s", element.Value().Type) 140 return desc 141 } 142 case "isreplicaset": 143 isReplicaSet, ok = element.Value().BooleanOK() 144 if !ok { 145 desc.LastError = fmt.Errorf("expected 'isreplicaset' to be a boolean but it's a BSON %s", element.Value().Type) 146 return desc 147 } 148 case "lastWrite": 149 lastWrite, ok := element.Value().DocumentOK() 150 if !ok { 151 desc.LastError = fmt.Errorf("expected 'lastWrite' to be a document but it's a BSON %s", element.Value().Type) 152 return desc 153 } 154 dateTime, err := lastWrite.LookupErr("lastWriteDate") 155 if err == nil { 156 dt, ok := dateTime.DateTimeOK() 157 if !ok { 158 desc.LastError = fmt.Errorf("expected 'lastWriteDate' to be a datetime but it's a BSON %s", dateTime.Type) 159 return desc 160 } 161 desc.LastWriteTime = time.Unix(dt/1000, dt%1000*1000000).UTC() 162 } 163 case "logicalSessionTimeoutMinutes": 164 i64, ok := element.Value().AsInt64OK() 165 if !ok { 166 desc.LastError = fmt.Errorf("expected 'logicalSessionTimeoutMinutes' to be an integer but it's a BSON %s", element.Value().Type) 167 return desc 168 } 169 desc.SessionTimeoutMinutes = uint32(i64) 170 case "maxBsonObjectSize": 171 i64, ok := element.Value().AsInt64OK() 172 if !ok { 173 desc.LastError = fmt.Errorf("expected 'maxBsonObjectSize' to be an integer but it's a BSON %s", element.Value().Type) 174 return desc 175 } 176 desc.MaxDocumentSize = uint32(i64) 177 case "maxMessageSizeBytes": 178 i64, ok := element.Value().AsInt64OK() 179 if !ok { 180 desc.LastError = fmt.Errorf("expected 'maxMessageSizeBytes' to be an integer but it's a BSON %s", element.Value().Type) 181 return desc 182 } 183 desc.MaxMessageSize = uint32(i64) 184 case "maxWriteBatchSize": 185 i64, ok := element.Value().AsInt64OK() 186 if !ok { 187 desc.LastError = fmt.Errorf("expected 'maxWriteBatchSize' to be an integer but it's a BSON %s", element.Value().Type) 188 return desc 189 } 190 desc.MaxBatchCount = uint32(i64) 191 case "me": 192 me, ok := element.Value().StringValueOK() 193 if !ok { 194 desc.LastError = fmt.Errorf("expected 'me' to be a string but it's a BSON %s", element.Value().Type) 195 return desc 196 } 197 desc.CanonicalAddr = address.Address(me).Canonicalize() 198 case "maxWireVersion": 199 versionRange.Max, ok = element.Value().AsInt32OK() 200 if !ok { 201 desc.LastError = fmt.Errorf("expected 'maxWireVersion' to be an integer but it's a BSON %s", element.Value().Type) 202 return desc 203 } 204 case "minWireVersion": 205 versionRange.Min, ok = element.Value().AsInt32OK() 206 if !ok { 207 desc.LastError = fmt.Errorf("expected 'minWireVersion' to be an integer but it's a BSON %s", element.Value().Type) 208 return desc 209 } 210 case "msg": 211 msg, ok = element.Value().StringValueOK() 212 if !ok { 213 desc.LastError = fmt.Errorf("expected 'msg' to be a string but it's a BSON %s", element.Value().Type) 214 return desc 215 } 216 case "ok": 217 okay, ok := element.Value().AsInt32OK() 218 if !ok { 219 desc.LastError = fmt.Errorf("expected 'ok' to be a boolean but it's a BSON %s", element.Value().Type) 220 return desc 221 } 222 if okay != 1 { 223 desc.LastError = errors.New("not ok") 224 return desc 225 } 226 case "passives": 227 var err error 228 desc.Passives, err = internal.StringSliceFromRawElement(element) 229 if err != nil { 230 desc.LastError = err 231 return desc 232 } 233 case "passive": 234 desc.Passive, ok = element.Value().BooleanOK() 235 if !ok { 236 desc.LastError = fmt.Errorf("expected 'passive' to be a boolean but it's a BSON %s", element.Value().Type) 237 return desc 238 } 239 case "primary": 240 primary, ok := element.Value().StringValueOK() 241 if !ok { 242 desc.LastError = fmt.Errorf("expected 'primary' to be a string but it's a BSON %s", element.Value().Type) 243 return desc 244 } 245 desc.Primary = address.Address(primary) 246 case "readOnly": 247 desc.ReadOnly, ok = element.Value().BooleanOK() 248 if !ok { 249 desc.LastError = fmt.Errorf("expected 'readOnly' to be a boolean but it's a BSON %s", element.Value().Type) 250 return desc 251 } 252 case "secondary": 253 secondary, ok = element.Value().BooleanOK() 254 if !ok { 255 desc.LastError = fmt.Errorf("expected 'secondary' to be a boolean but it's a BSON %s", element.Value().Type) 256 return desc 257 } 258 case "serviceId": 259 oid, ok := element.Value().ObjectIDOK() 260 if !ok { 261 desc.LastError = fmt.Errorf("expected 'serviceId' to be an ObjectId but it's a BSON %s", element.Value().Type) 262 } 263 desc.ServiceID = &oid 264 case "setName": 265 desc.SetName, ok = element.Value().StringValueOK() 266 if !ok { 267 desc.LastError = fmt.Errorf("expected 'setName' to be a string but it's a BSON %s", element.Value().Type) 268 return desc 269 } 270 case "setVersion": 271 i64, ok := element.Value().AsInt64OK() 272 if !ok { 273 desc.LastError = fmt.Errorf("expected 'setVersion' to be an integer but it's a BSON %s", element.Value().Type) 274 return desc 275 } 276 desc.SetVersion = uint32(i64) 277 case "tags": 278 m, err := decodeStringMap(element, "tags") 279 if err != nil { 280 desc.LastError = err 281 return desc 282 } 283 desc.Tags = tag.NewTagSetFromMap(m) 284 case "topologyVersion": 285 doc, ok := element.Value().DocumentOK() 286 if !ok { 287 desc.LastError = fmt.Errorf("expected 'topologyVersion' to be a document but it's a BSON %s", element.Value().Type) 288 return desc 289 } 290 291 desc.TopologyVersion, err = NewTopologyVersion(doc) 292 if err != nil { 293 desc.LastError = err 294 return desc 295 } 296 } 297 } 298 299 for _, host := range desc.Hosts { 300 desc.Members = append(desc.Members, address.Address(host).Canonicalize()) 301 } 302 303 for _, passive := range desc.Passives { 304 desc.Members = append(desc.Members, address.Address(passive).Canonicalize()) 305 } 306 307 for _, arbiter := range desc.Arbiters { 308 desc.Members = append(desc.Members, address.Address(arbiter).Canonicalize()) 309 } 310 311 desc.Kind = Standalone 312 313 if isReplicaSet { 314 desc.Kind = RSGhost 315 } else if desc.SetName != "" { 316 if isWritablePrimary { 317 desc.Kind = RSPrimary 318 } else if hidden { 319 desc.Kind = RSMember 320 } else if secondary { 321 desc.Kind = RSSecondary 322 } else if arbiterOnly { 323 desc.Kind = RSArbiter 324 } else { 325 desc.Kind = RSMember 326 } 327 } else if msg == "isdbgrid" { 328 desc.Kind = Mongos 329 } 330 331 desc.WireVersion = &versionRange 332 333 return desc 334 } 335 336 // NewDefaultServer creates a new unknown server description with the given address. 337 func NewDefaultServer(addr address.Address) Server { 338 return NewServerFromError(addr, nil, nil) 339 } 340 341 // NewServerFromError creates a new unknown server description with the given parameters. 342 func NewServerFromError(addr address.Address, err error, tv *TopologyVersion) Server { 343 return Server{ 344 Addr: addr, 345 LastError: err, 346 Kind: Unknown, 347 TopologyVersion: tv, 348 } 349 } 350 351 // SetAverageRTT sets the average round trip time for this server description. 352 func (s Server) SetAverageRTT(rtt time.Duration) Server { 353 s.AverageRTT = rtt 354 s.AverageRTTSet = true 355 return s 356 } 357 358 // DataBearing returns true if the server is a data bearing server. 359 func (s Server) DataBearing() bool { 360 return s.Kind == RSPrimary || 361 s.Kind == RSSecondary || 362 s.Kind == Mongos || 363 s.Kind == Standalone 364 } 365 366 // LoadBalanced returns true if the server is a load balancer or is behind a load balancer. 367 func (s Server) LoadBalanced() bool { 368 return s.Kind == LoadBalancer || s.ServiceID != nil 369 } 370 371 // String implements the Stringer interface 372 func (s Server) String() string { 373 str := fmt.Sprintf("Addr: %s, Type: %s", 374 s.Addr, s.Kind) 375 if len(s.Tags) != 0 { 376 str += fmt.Sprintf(", Tag sets: %s", s.Tags) 377 } 378 379 if s.AverageRTTSet { 380 str += fmt.Sprintf(", Average RTT: %d", s.AverageRTT) 381 } 382 383 if s.LastError != nil { 384 str += fmt.Sprintf(", Last error: %s", s.LastError) 385 } 386 return str 387 } 388 389 func decodeStringMap(element bson.RawElement, name string) (map[string]string, error) { 390 doc, ok := element.Value().DocumentOK() 391 if !ok { 392 return nil, fmt.Errorf("expected '%s' to be a document but it's a BSON %s", name, element.Value().Type) 393 } 394 elements, err := doc.Elements() 395 if err != nil { 396 return nil, err 397 } 398 m := make(map[string]string) 399 for _, element := range elements { 400 key := element.Key() 401 value, ok := element.Value().StringValueOK() 402 if !ok { 403 return nil, fmt.Errorf("expected '%s' to be a document of strings, but found a BSON %s", name, element.Value().Type) 404 } 405 m[key] = value 406 } 407 return m, nil 408 } 409 410 // Equal compares two server descriptions and returns true if they are equal 411 func (s Server) Equal(other Server) bool { 412 if s.CanonicalAddr.String() != other.CanonicalAddr.String() { 413 return false 414 } 415 416 if !sliceStringEqual(s.Arbiters, other.Arbiters) { 417 return false 418 } 419 420 if !sliceStringEqual(s.Hosts, other.Hosts) { 421 return false 422 } 423 424 if !sliceStringEqual(s.Passives, other.Passives) { 425 return false 426 } 427 428 if s.Primary != other.Primary { 429 return false 430 } 431 432 if s.SetName != other.SetName { 433 return false 434 } 435 436 if s.Kind != other.Kind { 437 return false 438 } 439 440 if s.LastError != nil || other.LastError != nil { 441 if s.LastError == nil || other.LastError == nil { 442 return false 443 } 444 if s.LastError.Error() != other.LastError.Error() { 445 return false 446 } 447 } 448 449 if !s.WireVersion.Equals(other.WireVersion) { 450 return false 451 } 452 453 if len(s.Tags) != len(other.Tags) || !s.Tags.ContainsAll(other.Tags) { 454 return false 455 } 456 457 if s.SetVersion != other.SetVersion { 458 return false 459 } 460 461 if s.ElectionID != other.ElectionID { 462 return false 463 } 464 465 if s.SessionTimeoutMinutes != other.SessionTimeoutMinutes { 466 return false 467 } 468 469 // If TopologyVersion is nil for both servers, CompareToIncoming will return -1 because it assumes that the 470 // incoming response is newer. We want the descriptions to be considered equal in this case, though, so an 471 // explicit check is required. 472 if s.TopologyVersion == nil && other.TopologyVersion == nil { 473 return true 474 } 475 return s.TopologyVersion.CompareToIncoming(other.TopologyVersion) == 0 476 } 477 478 func sliceStringEqual(a []string, b []string) bool { 479 if len(a) != len(b) { 480 return false 481 } 482 for i, v := range a { 483 if v != b[i] { 484 return false 485 } 486 } 487 return true 488 }