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  }