sensu-plugins-mongodb-mrtrotl 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +22 -0
  4. data/README.md +27 -0
  5. data/bin/check-mongodb-metric.rb +144 -0
  6. data/bin/check-mongodb-query-count.rb +267 -0
  7. data/bin/check-mongodb.py +1644 -0
  8. data/bin/check-mongodb.rb +5 -0
  9. data/bin/metrics-mongodb-replication.rb +254 -0
  10. data/bin/metrics-mongodb.rb +133 -0
  11. data/lib/bson/__init__.py +1347 -0
  12. data/lib/bson/__pycache__/__init__.cpython-310.pyc +0 -0
  13. data/lib/bson/__pycache__/_helpers.cpython-310.pyc +0 -0
  14. data/lib/bson/__pycache__/binary.cpython-310.pyc +0 -0
  15. data/lib/bson/__pycache__/code.cpython-310.pyc +0 -0
  16. data/lib/bson/__pycache__/codec_options.cpython-310.pyc +0 -0
  17. data/lib/bson/__pycache__/dbref.cpython-310.pyc +0 -0
  18. data/lib/bson/__pycache__/decimal128.cpython-310.pyc +0 -0
  19. data/lib/bson/__pycache__/errors.cpython-310.pyc +0 -0
  20. data/lib/bson/__pycache__/int64.cpython-310.pyc +0 -0
  21. data/lib/bson/__pycache__/json_util.cpython-310.pyc +0 -0
  22. data/lib/bson/__pycache__/max_key.cpython-310.pyc +0 -0
  23. data/lib/bson/__pycache__/min_key.cpython-310.pyc +0 -0
  24. data/lib/bson/__pycache__/objectid.cpython-310.pyc +0 -0
  25. data/lib/bson/__pycache__/raw_bson.cpython-310.pyc +0 -0
  26. data/lib/bson/__pycache__/regex.cpython-310.pyc +0 -0
  27. data/lib/bson/__pycache__/son.cpython-310.pyc +0 -0
  28. data/lib/bson/__pycache__/timestamp.cpython-310.pyc +0 -0
  29. data/lib/bson/__pycache__/tz_util.cpython-310.pyc +0 -0
  30. data/lib/bson/_cbson.cpython-310-x86_64-linux-gnu.so +0 -0
  31. data/lib/bson/_helpers.py +41 -0
  32. data/lib/bson/binary.py +364 -0
  33. data/lib/bson/code.py +101 -0
  34. data/lib/bson/codec_options.py +414 -0
  35. data/lib/bson/codec_options.pyi +100 -0
  36. data/lib/bson/dbref.py +133 -0
  37. data/lib/bson/decimal128.py +314 -0
  38. data/lib/bson/errors.py +35 -0
  39. data/lib/bson/int64.py +39 -0
  40. data/lib/bson/json_util.py +874 -0
  41. data/lib/bson/max_key.py +55 -0
  42. data/lib/bson/min_key.py +55 -0
  43. data/lib/bson/objectid.py +286 -0
  44. data/lib/bson/py.typed +2 -0
  45. data/lib/bson/raw_bson.py +175 -0
  46. data/lib/bson/regex.py +135 -0
  47. data/lib/bson/son.py +208 -0
  48. data/lib/bson/timestamp.py +124 -0
  49. data/lib/bson/tz_util.py +52 -0
  50. data/lib/gridfs/__init__.py +1015 -0
  51. data/lib/gridfs/__pycache__/__init__.cpython-310.pyc +0 -0
  52. data/lib/gridfs/__pycache__/errors.cpython-310.pyc +0 -0
  53. data/lib/gridfs/__pycache__/grid_file.cpython-310.pyc +0 -0
  54. data/lib/gridfs/errors.py +33 -0
  55. data/lib/gridfs/grid_file.py +907 -0
  56. data/lib/gridfs/py.typed +2 -0
  57. data/lib/pymongo/__init__.py +185 -0
  58. data/lib/pymongo/__pycache__/__init__.cpython-310.pyc +0 -0
  59. data/lib/pymongo/__pycache__/_csot.cpython-310.pyc +0 -0
  60. data/lib/pymongo/__pycache__/aggregation.cpython-310.pyc +0 -0
  61. data/lib/pymongo/__pycache__/auth.cpython-310.pyc +0 -0
  62. data/lib/pymongo/__pycache__/auth_aws.cpython-310.pyc +0 -0
  63. data/lib/pymongo/__pycache__/bulk.cpython-310.pyc +0 -0
  64. data/lib/pymongo/__pycache__/change_stream.cpython-310.pyc +0 -0
  65. data/lib/pymongo/__pycache__/client_options.cpython-310.pyc +0 -0
  66. data/lib/pymongo/__pycache__/client_session.cpython-310.pyc +0 -0
  67. data/lib/pymongo/__pycache__/collation.cpython-310.pyc +0 -0
  68. data/lib/pymongo/__pycache__/collection.cpython-310.pyc +0 -0
  69. data/lib/pymongo/__pycache__/command_cursor.cpython-310.pyc +0 -0
  70. data/lib/pymongo/__pycache__/common.cpython-310.pyc +0 -0
  71. data/lib/pymongo/__pycache__/compression_support.cpython-310.pyc +0 -0
  72. data/lib/pymongo/__pycache__/cursor.cpython-310.pyc +0 -0
  73. data/lib/pymongo/__pycache__/daemon.cpython-310.pyc +0 -0
  74. data/lib/pymongo/__pycache__/database.cpython-310.pyc +0 -0
  75. data/lib/pymongo/__pycache__/driver_info.cpython-310.pyc +0 -0
  76. data/lib/pymongo/__pycache__/encryption.cpython-310.pyc +0 -0
  77. data/lib/pymongo/__pycache__/encryption_options.cpython-310.pyc +0 -0
  78. data/lib/pymongo/__pycache__/errors.cpython-310.pyc +0 -0
  79. data/lib/pymongo/__pycache__/event_loggers.cpython-310.pyc +0 -0
  80. data/lib/pymongo/__pycache__/hello.cpython-310.pyc +0 -0
  81. data/lib/pymongo/__pycache__/helpers.cpython-310.pyc +0 -0
  82. data/lib/pymongo/__pycache__/max_staleness_selectors.cpython-310.pyc +0 -0
  83. data/lib/pymongo/__pycache__/message.cpython-310.pyc +0 -0
  84. data/lib/pymongo/__pycache__/mongo_client.cpython-310.pyc +0 -0
  85. data/lib/pymongo/__pycache__/monitor.cpython-310.pyc +0 -0
  86. data/lib/pymongo/__pycache__/monitoring.cpython-310.pyc +0 -0
  87. data/lib/pymongo/__pycache__/network.cpython-310.pyc +0 -0
  88. data/lib/pymongo/__pycache__/ocsp_cache.cpython-310.pyc +0 -0
  89. data/lib/pymongo/__pycache__/ocsp_support.cpython-310.pyc +0 -0
  90. data/lib/pymongo/__pycache__/operations.cpython-310.pyc +0 -0
  91. data/lib/pymongo/__pycache__/periodic_executor.cpython-310.pyc +0 -0
  92. data/lib/pymongo/__pycache__/pool.cpython-310.pyc +0 -0
  93. data/lib/pymongo/__pycache__/pyopenssl_context.cpython-310.pyc +0 -0
  94. data/lib/pymongo/__pycache__/read_concern.cpython-310.pyc +0 -0
  95. data/lib/pymongo/__pycache__/read_preferences.cpython-310.pyc +0 -0
  96. data/lib/pymongo/__pycache__/response.cpython-310.pyc +0 -0
  97. data/lib/pymongo/__pycache__/results.cpython-310.pyc +0 -0
  98. data/lib/pymongo/__pycache__/saslprep.cpython-310.pyc +0 -0
  99. data/lib/pymongo/__pycache__/server.cpython-310.pyc +0 -0
  100. data/lib/pymongo/__pycache__/server_api.cpython-310.pyc +0 -0
  101. data/lib/pymongo/__pycache__/server_description.cpython-310.pyc +0 -0
  102. data/lib/pymongo/__pycache__/server_selectors.cpython-310.pyc +0 -0
  103. data/lib/pymongo/__pycache__/server_type.cpython-310.pyc +0 -0
  104. data/lib/pymongo/__pycache__/settings.cpython-310.pyc +0 -0
  105. data/lib/pymongo/__pycache__/socket_checker.cpython-310.pyc +0 -0
  106. data/lib/pymongo/__pycache__/srv_resolver.cpython-310.pyc +0 -0
  107. data/lib/pymongo/__pycache__/ssl_context.cpython-310.pyc +0 -0
  108. data/lib/pymongo/__pycache__/ssl_support.cpython-310.pyc +0 -0
  109. data/lib/pymongo/__pycache__/topology.cpython-310.pyc +0 -0
  110. data/lib/pymongo/__pycache__/topology_description.cpython-310.pyc +0 -0
  111. data/lib/pymongo/__pycache__/typings.cpython-310.pyc +0 -0
  112. data/lib/pymongo/__pycache__/uri_parser.cpython-310.pyc +0 -0
  113. data/lib/pymongo/__pycache__/write_concern.cpython-310.pyc +0 -0
  114. data/lib/pymongo/_cmessage.cpython-310-x86_64-linux-gnu.so +0 -0
  115. data/lib/pymongo/_csot.py +118 -0
  116. data/lib/pymongo/aggregation.py +229 -0
  117. data/lib/pymongo/auth.py +549 -0
  118. data/lib/pymongo/auth_aws.py +94 -0
  119. data/lib/pymongo/bulk.py +513 -0
  120. data/lib/pymongo/change_stream.py +457 -0
  121. data/lib/pymongo/client_options.py +302 -0
  122. data/lib/pymongo/client_session.py +1112 -0
  123. data/lib/pymongo/collation.py +224 -0
  124. data/lib/pymongo/collection.py +3204 -0
  125. data/lib/pymongo/command_cursor.py +353 -0
  126. data/lib/pymongo/common.py +984 -0
  127. data/lib/pymongo/compression_support.py +149 -0
  128. data/lib/pymongo/cursor.py +1345 -0
  129. data/lib/pymongo/daemon.py +141 -0
  130. data/lib/pymongo/database.py +1202 -0
  131. data/lib/pymongo/driver_info.py +42 -0
  132. data/lib/pymongo/encryption.py +884 -0
  133. data/lib/pymongo/encryption_options.py +221 -0
  134. data/lib/pymongo/errors.py +365 -0
  135. data/lib/pymongo/event_loggers.py +221 -0
  136. data/lib/pymongo/hello.py +219 -0
  137. data/lib/pymongo/helpers.py +259 -0
  138. data/lib/pymongo/max_staleness_selectors.py +114 -0
  139. data/lib/pymongo/message.py +1440 -0
  140. data/lib/pymongo/mongo_client.py +2144 -0
  141. data/lib/pymongo/monitor.py +440 -0
  142. data/lib/pymongo/monitoring.py +1801 -0
  143. data/lib/pymongo/network.py +311 -0
  144. data/lib/pymongo/ocsp_cache.py +87 -0
  145. data/lib/pymongo/ocsp_support.py +372 -0
  146. data/lib/pymongo/operations.py +507 -0
  147. data/lib/pymongo/periodic_executor.py +183 -0
  148. data/lib/pymongo/pool.py +1660 -0
  149. data/lib/pymongo/py.typed +2 -0
  150. data/lib/pymongo/pyopenssl_context.py +383 -0
  151. data/lib/pymongo/read_concern.py +75 -0
  152. data/lib/pymongo/read_preferences.py +609 -0
  153. data/lib/pymongo/response.py +109 -0
  154. data/lib/pymongo/results.py +217 -0
  155. data/lib/pymongo/saslprep.py +113 -0
  156. data/lib/pymongo/server.py +247 -0
  157. data/lib/pymongo/server_api.py +170 -0
  158. data/lib/pymongo/server_description.py +285 -0
  159. data/lib/pymongo/server_selectors.py +153 -0
  160. data/lib/pymongo/server_type.py +32 -0
  161. data/lib/pymongo/settings.py +159 -0
  162. data/lib/pymongo/socket_checker.py +104 -0
  163. data/lib/pymongo/srv_resolver.py +126 -0
  164. data/lib/pymongo/ssl_context.py +39 -0
  165. data/lib/pymongo/ssl_support.py +99 -0
  166. data/lib/pymongo/topology.py +890 -0
  167. data/lib/pymongo/topology_description.py +639 -0
  168. data/lib/pymongo/typings.py +39 -0
  169. data/lib/pymongo/uri_parser.py +624 -0
  170. data/lib/pymongo/write_concern.py +129 -0
  171. data/lib/pymongo-4.2.0.dist-info/INSTALLER +1 -0
  172. data/lib/pymongo-4.2.0.dist-info/LICENSE +201 -0
  173. data/lib/pymongo-4.2.0.dist-info/METADATA +250 -0
  174. data/lib/pymongo-4.2.0.dist-info/RECORD +167 -0
  175. data/lib/pymongo-4.2.0.dist-info/REQUESTED +0 -0
  176. data/lib/pymongo-4.2.0.dist-info/WHEEL +6 -0
  177. data/lib/pymongo-4.2.0.dist-info/top_level.txt +3 -0
  178. data/lib/sensu-plugins-mongodb/metrics.rb +391 -0
  179. data/lib/sensu-plugins-mongodb/version.rb +9 -0
  180. data/lib/sensu-plugins-mongodb.rb +1 -0
  181. metadata +407 -0
@@ -0,0 +1,639 @@
1
+ # Copyright 2014-present MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
4
+ # may not use this file except in compliance with the License. You
5
+ # may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+ # implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+ """Represent a deployment of MongoDB servers."""
16
+
17
+ from random import sample
18
+ from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
19
+
20
+ from bson.objectid import ObjectId
21
+ from pymongo import common
22
+ from pymongo.errors import ConfigurationError
23
+ from pymongo.read_preferences import ReadPreference, _AggWritePref, _ServerMode
24
+ from pymongo.server_description import ServerDescription
25
+ from pymongo.server_selectors import Selection
26
+ from pymongo.server_type import SERVER_TYPE
27
+ from pymongo.typings import _Address
28
+
29
+
30
+ # Enumeration for various kinds of MongoDB cluster topologies.
31
+ class _TopologyType(NamedTuple):
32
+ Single: int
33
+ ReplicaSetNoPrimary: int
34
+ ReplicaSetWithPrimary: int
35
+ Sharded: int
36
+ Unknown: int
37
+ LoadBalanced: int
38
+
39
+
40
+ TOPOLOGY_TYPE = _TopologyType(*range(6))
41
+
42
+ # Topologies compatible with SRV record polling.
43
+ SRV_POLLING_TOPOLOGIES: Tuple[int, int] = (TOPOLOGY_TYPE.Unknown, TOPOLOGY_TYPE.Sharded)
44
+
45
+
46
+ _ServerSelector = Callable[[List[ServerDescription]], List[ServerDescription]]
47
+
48
+
49
+ class TopologyDescription(object):
50
+ def __init__(
51
+ self,
52
+ topology_type: int,
53
+ server_descriptions: Dict[_Address, ServerDescription],
54
+ replica_set_name: Optional[str],
55
+ max_set_version: Optional[int],
56
+ max_election_id: Optional[ObjectId],
57
+ topology_settings: Any,
58
+ ) -> None:
59
+ """Representation of a deployment of MongoDB servers.
60
+
61
+ :Parameters:
62
+ - `topology_type`: initial type
63
+ - `server_descriptions`: dict of (address, ServerDescription) for
64
+ all seeds
65
+ - `replica_set_name`: replica set name or None
66
+ - `max_set_version`: greatest setVersion seen from a primary, or None
67
+ - `max_election_id`: greatest electionId seen from a primary, or None
68
+ - `topology_settings`: a TopologySettings
69
+ """
70
+ self._topology_type = topology_type
71
+ self._replica_set_name = replica_set_name
72
+ self._server_descriptions = server_descriptions
73
+ self._max_set_version = max_set_version
74
+ self._max_election_id = max_election_id
75
+
76
+ # The heartbeat_frequency is used in staleness estimates.
77
+ self._topology_settings = topology_settings
78
+
79
+ # Is PyMongo compatible with all servers' wire protocols?
80
+ self._incompatible_err = None
81
+ if self._topology_type != TOPOLOGY_TYPE.LoadBalanced:
82
+ self._init_incompatible_err()
83
+
84
+ # Server Discovery And Monitoring Spec: Whenever a client updates the
85
+ # TopologyDescription from an hello response, it MUST set
86
+ # TopologyDescription.logicalSessionTimeoutMinutes to the smallest
87
+ # logicalSessionTimeoutMinutes value among ServerDescriptions of all
88
+ # data-bearing server types. If any have a null
89
+ # logicalSessionTimeoutMinutes, then
90
+ # TopologyDescription.logicalSessionTimeoutMinutes MUST be set to null.
91
+ readable_servers = self.readable_servers
92
+ if not readable_servers:
93
+ self._ls_timeout_minutes = None
94
+ elif any(s.logical_session_timeout_minutes is None for s in readable_servers):
95
+ self._ls_timeout_minutes = None
96
+ else:
97
+ self._ls_timeout_minutes = min( # type: ignore[type-var]
98
+ s.logical_session_timeout_minutes for s in readable_servers
99
+ )
100
+
101
+ def _init_incompatible_err(self):
102
+ """Internal compatibility check for non-load balanced topologies."""
103
+ for s in self._server_descriptions.values():
104
+ if not s.is_server_type_known:
105
+ continue
106
+
107
+ # s.min/max_wire_version is the server's wire protocol.
108
+ # MIN/MAX_SUPPORTED_WIRE_VERSION is what PyMongo supports.
109
+ server_too_new = (
110
+ # Server too new.
111
+ s.min_wire_version is not None
112
+ and s.min_wire_version > common.MAX_SUPPORTED_WIRE_VERSION
113
+ )
114
+
115
+ server_too_old = (
116
+ # Server too old.
117
+ s.max_wire_version is not None
118
+ and s.max_wire_version < common.MIN_SUPPORTED_WIRE_VERSION
119
+ )
120
+
121
+ if server_too_new:
122
+ self._incompatible_err = (
123
+ "Server at %s:%d requires wire version %d, but this " # type: ignore
124
+ "version of PyMongo only supports up to %d."
125
+ % (
126
+ s.address[0],
127
+ s.address[1] or 0,
128
+ s.min_wire_version,
129
+ common.MAX_SUPPORTED_WIRE_VERSION,
130
+ )
131
+ )
132
+
133
+ elif server_too_old:
134
+ self._incompatible_err = (
135
+ "Server at %s:%d reports wire version %d, but this " # type: ignore
136
+ "version of PyMongo requires at least %d (MongoDB %s)."
137
+ % (
138
+ s.address[0],
139
+ s.address[1] or 0,
140
+ s.max_wire_version,
141
+ common.MIN_SUPPORTED_WIRE_VERSION,
142
+ common.MIN_SUPPORTED_SERVER_VERSION,
143
+ )
144
+ )
145
+
146
+ break
147
+
148
+ def check_compatible(self) -> None:
149
+ """Raise ConfigurationError if any server is incompatible.
150
+
151
+ A server is incompatible if its wire protocol version range does not
152
+ overlap with PyMongo's.
153
+ """
154
+ if self._incompatible_err:
155
+ raise ConfigurationError(self._incompatible_err)
156
+
157
+ def has_server(self, address: _Address) -> bool:
158
+ return address in self._server_descriptions
159
+
160
+ def reset_server(self, address: _Address) -> "TopologyDescription":
161
+ """A copy of this description, with one server marked Unknown."""
162
+ unknown_sd = self._server_descriptions[address].to_unknown()
163
+ return updated_topology_description(self, unknown_sd)
164
+
165
+ def reset(self) -> "TopologyDescription":
166
+ """A copy of this description, with all servers marked Unknown."""
167
+ if self._topology_type == TOPOLOGY_TYPE.ReplicaSetWithPrimary:
168
+ topology_type = TOPOLOGY_TYPE.ReplicaSetNoPrimary
169
+ else:
170
+ topology_type = self._topology_type
171
+
172
+ # The default ServerDescription's type is Unknown.
173
+ sds = dict((address, ServerDescription(address)) for address in self._server_descriptions)
174
+
175
+ return TopologyDescription(
176
+ topology_type,
177
+ sds,
178
+ self._replica_set_name,
179
+ self._max_set_version,
180
+ self._max_election_id,
181
+ self._topology_settings,
182
+ )
183
+
184
+ def server_descriptions(self) -> Dict[_Address, ServerDescription]:
185
+ """Dict of (address,
186
+ :class:`~pymongo.server_description.ServerDescription`)."""
187
+ return self._server_descriptions.copy()
188
+
189
+ @property
190
+ def topology_type(self) -> int:
191
+ """The type of this topology."""
192
+ return self._topology_type
193
+
194
+ @property
195
+ def topology_type_name(self) -> str:
196
+ """The topology type as a human readable string.
197
+
198
+ .. versionadded:: 3.4
199
+ """
200
+ return TOPOLOGY_TYPE._fields[self._topology_type]
201
+
202
+ @property
203
+ def replica_set_name(self) -> Optional[str]:
204
+ """The replica set name."""
205
+ return self._replica_set_name
206
+
207
+ @property
208
+ def max_set_version(self) -> Optional[int]:
209
+ """Greatest setVersion seen from a primary, or None."""
210
+ return self._max_set_version
211
+
212
+ @property
213
+ def max_election_id(self) -> Optional[ObjectId]:
214
+ """Greatest electionId seen from a primary, or None."""
215
+ return self._max_election_id
216
+
217
+ @property
218
+ def logical_session_timeout_minutes(self) -> Optional[int]:
219
+ """Minimum logical session timeout, or None."""
220
+ return self._ls_timeout_minutes
221
+
222
+ @property
223
+ def known_servers(self) -> List[ServerDescription]:
224
+ """List of Servers of types besides Unknown."""
225
+ return [s for s in self._server_descriptions.values() if s.is_server_type_known]
226
+
227
+ @property
228
+ def has_known_servers(self) -> bool:
229
+ """Whether there are any Servers of types besides Unknown."""
230
+ return any(s for s in self._server_descriptions.values() if s.is_server_type_known)
231
+
232
+ @property
233
+ def readable_servers(self) -> List[ServerDescription]:
234
+ """List of readable Servers."""
235
+ return [s for s in self._server_descriptions.values() if s.is_readable]
236
+
237
+ @property
238
+ def common_wire_version(self) -> Optional[int]:
239
+ """Minimum of all servers' max wire versions, or None."""
240
+ servers = self.known_servers
241
+ if servers:
242
+ return min(s.max_wire_version for s in self.known_servers)
243
+
244
+ return None
245
+
246
+ @property
247
+ def heartbeat_frequency(self) -> int:
248
+ return self._topology_settings.heartbeat_frequency
249
+
250
+ @property
251
+ def srv_max_hosts(self) -> int:
252
+ return self._topology_settings._srv_max_hosts
253
+
254
+ def _apply_local_threshold(self, selection):
255
+ if not selection:
256
+ return []
257
+ # Round trip time in seconds.
258
+ fastest = min(s.round_trip_time for s in selection.server_descriptions)
259
+ threshold = self._topology_settings.local_threshold_ms / 1000.0
260
+ return [
261
+ s for s in selection.server_descriptions if (s.round_trip_time - fastest) <= threshold
262
+ ]
263
+
264
+ def apply_selector(
265
+ self,
266
+ selector: Any,
267
+ address: Optional[_Address] = None,
268
+ custom_selector: Optional[_ServerSelector] = None,
269
+ ) -> List[ServerDescription]:
270
+ """List of servers matching the provided selector(s).
271
+
272
+ :Parameters:
273
+ - `selector`: a callable that takes a Selection as input and returns
274
+ a Selection as output. For example, an instance of a read
275
+ preference from :mod:`~pymongo.read_preferences`.
276
+ - `address` (optional): A server address to select.
277
+ - `custom_selector` (optional): A callable that augments server
278
+ selection rules. Accepts a list of
279
+ :class:`~pymongo.server_description.ServerDescription` objects and
280
+ return a list of server descriptions that should be considered
281
+ suitable for the desired operation.
282
+
283
+ .. versionadded:: 3.4
284
+ """
285
+ if getattr(selector, "min_wire_version", 0):
286
+ common_wv = self.common_wire_version
287
+ if common_wv and common_wv < selector.min_wire_version:
288
+ raise ConfigurationError(
289
+ "%s requires min wire version %d, but topology's min"
290
+ " wire version is %d" % (selector, selector.min_wire_version, common_wv)
291
+ )
292
+
293
+ if isinstance(selector, _AggWritePref):
294
+ selector.selection_hook(self)
295
+
296
+ if self.topology_type == TOPOLOGY_TYPE.Unknown:
297
+ return []
298
+ elif self.topology_type in (TOPOLOGY_TYPE.Single, TOPOLOGY_TYPE.LoadBalanced):
299
+ # Ignore selectors for standalone and load balancer mode.
300
+ return self.known_servers
301
+ if address:
302
+ # Ignore selectors when explicit address is requested.
303
+ description = self.server_descriptions().get(address)
304
+ return [description] if description else []
305
+
306
+ selection = Selection.from_topology_description(self)
307
+ # Ignore read preference for sharded clusters.
308
+ if self.topology_type != TOPOLOGY_TYPE.Sharded:
309
+ selection = selector(selection)
310
+
311
+ # Apply custom selector followed by localThresholdMS.
312
+ if custom_selector is not None and selection:
313
+ selection = selection.with_server_descriptions(
314
+ custom_selector(selection.server_descriptions)
315
+ )
316
+ return self._apply_local_threshold(selection)
317
+
318
+ def has_readable_server(self, read_preference: _ServerMode = ReadPreference.PRIMARY) -> bool:
319
+ """Does this topology have any readable servers available matching the
320
+ given read preference?
321
+
322
+ :Parameters:
323
+ - `read_preference`: an instance of a read preference from
324
+ :mod:`~pymongo.read_preferences`. Defaults to
325
+ :attr:`~pymongo.read_preferences.ReadPreference.PRIMARY`.
326
+
327
+ .. note:: When connected directly to a single server this method
328
+ always returns ``True``.
329
+
330
+ .. versionadded:: 3.4
331
+ """
332
+ common.validate_read_preference("read_preference", read_preference)
333
+ return any(self.apply_selector(read_preference))
334
+
335
+ def has_writable_server(self) -> bool:
336
+ """Does this topology have a writable server available?
337
+
338
+ .. note:: When connected directly to a single server this method
339
+ always returns ``True``.
340
+
341
+ .. versionadded:: 3.4
342
+ """
343
+ return self.has_readable_server(ReadPreference.PRIMARY)
344
+
345
+ def __repr__(self):
346
+ # Sort the servers by address.
347
+ servers = sorted(self._server_descriptions.values(), key=lambda sd: sd.address)
348
+ return "<%s id: %s, topology_type: %s, servers: %r>" % (
349
+ self.__class__.__name__,
350
+ self._topology_settings._topology_id,
351
+ self.topology_type_name,
352
+ servers,
353
+ )
354
+
355
+
356
+ # If topology type is Unknown and we receive a hello response, what should
357
+ # the new topology type be?
358
+ _SERVER_TYPE_TO_TOPOLOGY_TYPE = {
359
+ SERVER_TYPE.Mongos: TOPOLOGY_TYPE.Sharded,
360
+ SERVER_TYPE.RSPrimary: TOPOLOGY_TYPE.ReplicaSetWithPrimary,
361
+ SERVER_TYPE.RSSecondary: TOPOLOGY_TYPE.ReplicaSetNoPrimary,
362
+ SERVER_TYPE.RSArbiter: TOPOLOGY_TYPE.ReplicaSetNoPrimary,
363
+ SERVER_TYPE.RSOther: TOPOLOGY_TYPE.ReplicaSetNoPrimary,
364
+ # Note: SERVER_TYPE.LoadBalancer and Unknown are intentionally left out.
365
+ }
366
+
367
+
368
+ def updated_topology_description(
369
+ topology_description: TopologyDescription, server_description: ServerDescription
370
+ ) -> "TopologyDescription":
371
+ """Return an updated copy of a TopologyDescription.
372
+
373
+ :Parameters:
374
+ - `topology_description`: the current TopologyDescription
375
+ - `server_description`: a new ServerDescription that resulted from
376
+ a hello call
377
+
378
+ Called after attempting (successfully or not) to call hello on the
379
+ server at server_description.address. Does not modify topology_description.
380
+ """
381
+ address = server_description.address
382
+
383
+ # These values will be updated, if necessary, to form the new
384
+ # TopologyDescription.
385
+ topology_type = topology_description.topology_type
386
+ set_name = topology_description.replica_set_name
387
+ max_set_version = topology_description.max_set_version
388
+ max_election_id = topology_description.max_election_id
389
+ server_type = server_description.server_type
390
+
391
+ # Don't mutate the original dict of server descriptions; copy it.
392
+ sds = topology_description.server_descriptions()
393
+
394
+ # Replace this server's description with the new one.
395
+ sds[address] = server_description
396
+
397
+ if topology_type == TOPOLOGY_TYPE.Single:
398
+ # Set server type to Unknown if replica set name does not match.
399
+ if set_name is not None and set_name != server_description.replica_set_name:
400
+ error = ConfigurationError(
401
+ "client is configured to connect to a replica set named "
402
+ "'%s' but this node belongs to a set named '%s'"
403
+ % (set_name, server_description.replica_set_name)
404
+ )
405
+ sds[address] = server_description.to_unknown(error=error)
406
+ # Single type never changes.
407
+ return TopologyDescription(
408
+ TOPOLOGY_TYPE.Single,
409
+ sds,
410
+ set_name,
411
+ max_set_version,
412
+ max_election_id,
413
+ topology_description._topology_settings,
414
+ )
415
+
416
+ if topology_type == TOPOLOGY_TYPE.Unknown:
417
+ if server_type in (SERVER_TYPE.Standalone, SERVER_TYPE.LoadBalancer):
418
+ if len(topology_description._topology_settings.seeds) == 1:
419
+ topology_type = TOPOLOGY_TYPE.Single
420
+ else:
421
+ # Remove standalone from Topology when given multiple seeds.
422
+ sds.pop(address)
423
+ elif server_type not in (SERVER_TYPE.Unknown, SERVER_TYPE.RSGhost):
424
+ topology_type = _SERVER_TYPE_TO_TOPOLOGY_TYPE[server_type]
425
+
426
+ if topology_type == TOPOLOGY_TYPE.Sharded:
427
+ if server_type not in (SERVER_TYPE.Mongos, SERVER_TYPE.Unknown):
428
+ sds.pop(address)
429
+
430
+ elif topology_type == TOPOLOGY_TYPE.ReplicaSetNoPrimary:
431
+ if server_type in (SERVER_TYPE.Standalone, SERVER_TYPE.Mongos):
432
+ sds.pop(address)
433
+
434
+ elif server_type == SERVER_TYPE.RSPrimary:
435
+ (topology_type, set_name, max_set_version, max_election_id) = _update_rs_from_primary(
436
+ sds, set_name, server_description, max_set_version, max_election_id
437
+ )
438
+
439
+ elif server_type in (SERVER_TYPE.RSSecondary, SERVER_TYPE.RSArbiter, SERVER_TYPE.RSOther):
440
+ topology_type, set_name = _update_rs_no_primary_from_member(
441
+ sds, set_name, server_description
442
+ )
443
+
444
+ elif topology_type == TOPOLOGY_TYPE.ReplicaSetWithPrimary:
445
+ if server_type in (SERVER_TYPE.Standalone, SERVER_TYPE.Mongos):
446
+ sds.pop(address)
447
+ topology_type = _check_has_primary(sds)
448
+
449
+ elif server_type == SERVER_TYPE.RSPrimary:
450
+ (topology_type, set_name, max_set_version, max_election_id) = _update_rs_from_primary(
451
+ sds, set_name, server_description, max_set_version, max_election_id
452
+ )
453
+
454
+ elif server_type in (SERVER_TYPE.RSSecondary, SERVER_TYPE.RSArbiter, SERVER_TYPE.RSOther):
455
+ topology_type = _update_rs_with_primary_from_member(sds, set_name, server_description)
456
+
457
+ else:
458
+ # Server type is Unknown or RSGhost: did we just lose the primary?
459
+ topology_type = _check_has_primary(sds)
460
+
461
+ # Return updated copy.
462
+ return TopologyDescription(
463
+ topology_type,
464
+ sds,
465
+ set_name,
466
+ max_set_version,
467
+ max_election_id,
468
+ topology_description._topology_settings,
469
+ )
470
+
471
+
472
+ def _updated_topology_description_srv_polling(topology_description, seedlist):
473
+ """Return an updated copy of a TopologyDescription.
474
+
475
+ :Parameters:
476
+ - `topology_description`: the current TopologyDescription
477
+ - `seedlist`: a list of new seeds new ServerDescription that resulted from
478
+ a hello call
479
+ """
480
+ assert topology_description.topology_type in SRV_POLLING_TOPOLOGIES
481
+ # Create a copy of the server descriptions.
482
+ sds = topology_description.server_descriptions()
483
+
484
+ # If seeds haven't changed, don't do anything.
485
+ if set(sds.keys()) == set(seedlist):
486
+ return topology_description
487
+
488
+ # Remove SDs corresponding to servers no longer part of the SRV record.
489
+ for address in list(sds.keys()):
490
+ if address not in seedlist:
491
+ sds.pop(address)
492
+
493
+ if topology_description.srv_max_hosts != 0:
494
+ new_hosts = set(seedlist) - set(sds.keys())
495
+ n_to_add = topology_description.srv_max_hosts - len(sds)
496
+ if n_to_add > 0:
497
+ seedlist = sample(new_hosts, min(n_to_add, len(new_hosts)))
498
+ else:
499
+ seedlist = []
500
+ # Add SDs corresponding to servers recently added to the SRV record.
501
+ for address in seedlist:
502
+ if address not in sds:
503
+ sds[address] = ServerDescription(address)
504
+ return TopologyDescription(
505
+ topology_description.topology_type,
506
+ sds,
507
+ topology_description.replica_set_name,
508
+ topology_description.max_set_version,
509
+ topology_description.max_election_id,
510
+ topology_description._topology_settings,
511
+ )
512
+
513
+
514
+ def _update_rs_from_primary(
515
+ sds, replica_set_name, server_description, max_set_version, max_election_id
516
+ ):
517
+ """Update topology description from a primary's hello response.
518
+
519
+ Pass in a dict of ServerDescriptions, current replica set name, the
520
+ ServerDescription we are processing, and the TopologyDescription's
521
+ max_set_version and max_election_id if any.
522
+
523
+ Returns (new topology type, new replica_set_name, new max_set_version,
524
+ new max_election_id).
525
+ """
526
+ if replica_set_name is None:
527
+ replica_set_name = server_description.replica_set_name
528
+
529
+ elif replica_set_name != server_description.replica_set_name:
530
+ # We found a primary but it doesn't have the replica_set_name
531
+ # provided by the user.
532
+ sds.pop(server_description.address)
533
+ return (_check_has_primary(sds), replica_set_name, max_set_version, max_election_id)
534
+
535
+ max_election_tuple = max_set_version, max_election_id
536
+ if None not in server_description.election_tuple:
537
+ if (
538
+ None not in max_election_tuple
539
+ and max_election_tuple > server_description.election_tuple
540
+ ):
541
+
542
+ # Stale primary, set to type Unknown.
543
+ sds[server_description.address] = server_description.to_unknown()
544
+ return (_check_has_primary(sds), replica_set_name, max_set_version, max_election_id)
545
+
546
+ max_election_id = server_description.election_id
547
+
548
+ if server_description.set_version is not None and (
549
+ max_set_version is None or server_description.set_version > max_set_version
550
+ ):
551
+
552
+ max_set_version = server_description.set_version
553
+
554
+ # We've heard from the primary. Is it the same primary as before?
555
+ for server in sds.values():
556
+ if (
557
+ server.server_type is SERVER_TYPE.RSPrimary
558
+ and server.address != server_description.address
559
+ ):
560
+
561
+ # Reset old primary's type to Unknown.
562
+ sds[server.address] = server.to_unknown()
563
+
564
+ # There can be only one prior primary.
565
+ break
566
+
567
+ # Discover new hosts from this primary's response.
568
+ for new_address in server_description.all_hosts:
569
+ if new_address not in sds:
570
+ sds[new_address] = ServerDescription(new_address)
571
+
572
+ # Remove hosts not in the response.
573
+ for addr in set(sds) - server_description.all_hosts:
574
+ sds.pop(addr)
575
+
576
+ # If the host list differs from the seed list, we may not have a primary
577
+ # after all.
578
+ return (_check_has_primary(sds), replica_set_name, max_set_version, max_election_id)
579
+
580
+
581
+ def _update_rs_with_primary_from_member(sds, replica_set_name, server_description):
582
+ """RS with known primary. Process a response from a non-primary.
583
+
584
+ Pass in a dict of ServerDescriptions, current replica set name, and the
585
+ ServerDescription we are processing.
586
+
587
+ Returns new topology type.
588
+ """
589
+ assert replica_set_name is not None
590
+
591
+ if replica_set_name != server_description.replica_set_name:
592
+ sds.pop(server_description.address)
593
+ elif server_description.me and server_description.address != server_description.me:
594
+ sds.pop(server_description.address)
595
+
596
+ # Had this member been the primary?
597
+ return _check_has_primary(sds)
598
+
599
+
600
+ def _update_rs_no_primary_from_member(sds, replica_set_name, server_description):
601
+ """RS without known primary. Update from a non-primary's response.
602
+
603
+ Pass in a dict of ServerDescriptions, current replica set name, and the
604
+ ServerDescription we are processing.
605
+
606
+ Returns (new topology type, new replica_set_name).
607
+ """
608
+ topology_type = TOPOLOGY_TYPE.ReplicaSetNoPrimary
609
+ if replica_set_name is None:
610
+ replica_set_name = server_description.replica_set_name
611
+
612
+ elif replica_set_name != server_description.replica_set_name:
613
+ sds.pop(server_description.address)
614
+ return topology_type, replica_set_name
615
+
616
+ # This isn't the primary's response, so don't remove any servers
617
+ # it doesn't report. Only add new servers.
618
+ for address in server_description.all_hosts:
619
+ if address not in sds:
620
+ sds[address] = ServerDescription(address)
621
+
622
+ if server_description.me and server_description.address != server_description.me:
623
+ sds.pop(server_description.address)
624
+
625
+ return topology_type, replica_set_name
626
+
627
+
628
+ def _check_has_primary(sds):
629
+ """Current topology type is ReplicaSetWithPrimary. Is primary still known?
630
+
631
+ Pass in a dict of ServerDescriptions.
632
+
633
+ Returns new topology type.
634
+ """
635
+ for s in sds.values():
636
+ if s.server_type == SERVER_TYPE.RSPrimary:
637
+ return TOPOLOGY_TYPE.ReplicaSetWithPrimary
638
+ else:
639
+ return TOPOLOGY_TYPE.ReplicaSetNoPrimary
@@ -0,0 +1,39 @@
1
+ # Copyright 2022-Present MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Type aliases used by PyMongo"""
16
+ from typing import (
17
+ TYPE_CHECKING,
18
+ Any,
19
+ Mapping,
20
+ MutableMapping,
21
+ Optional,
22
+ Sequence,
23
+ Tuple,
24
+ TypeVar,
25
+ Union,
26
+ )
27
+
28
+ if TYPE_CHECKING:
29
+ from bson.raw_bson import RawBSONDocument
30
+ from pymongo.collation import Collation
31
+
32
+
33
+ # Common Shared Types.
34
+ _Address = Tuple[str, Optional[int]]
35
+ _CollationIn = Union[Mapping[str, Any], "Collation"]
36
+ _DocumentIn = Union[MutableMapping[str, Any], "RawBSONDocument"]
37
+ _Pipeline = Sequence[Mapping[str, Any]]
38
+ _DocumentOut = _DocumentIn
39
+ _DocumentType = TypeVar("_DocumentType", bound=Mapping[str, Any])