sensu-plugins-mongodb-mrtrotl 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +22 -0
- data/README.md +27 -0
- data/bin/check-mongodb-metric.rb +144 -0
- data/bin/check-mongodb-query-count.rb +267 -0
- data/bin/check-mongodb.py +1644 -0
- data/bin/check-mongodb.rb +5 -0
- data/bin/metrics-mongodb-replication.rb +254 -0
- data/bin/metrics-mongodb.rb +133 -0
- data/lib/bson/__init__.py +1347 -0
- data/lib/bson/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/_helpers.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/binary.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/code.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/codec_options.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/dbref.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/decimal128.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/int64.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/json_util.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/max_key.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/min_key.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/objectid.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/raw_bson.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/regex.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/son.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/timestamp.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/tz_util.cpython-310.pyc +0 -0
- data/lib/bson/_cbson.cpython-310-x86_64-linux-gnu.so +0 -0
- data/lib/bson/_helpers.py +41 -0
- data/lib/bson/binary.py +364 -0
- data/lib/bson/code.py +101 -0
- data/lib/bson/codec_options.py +414 -0
- data/lib/bson/codec_options.pyi +100 -0
- data/lib/bson/dbref.py +133 -0
- data/lib/bson/decimal128.py +314 -0
- data/lib/bson/errors.py +35 -0
- data/lib/bson/int64.py +39 -0
- data/lib/bson/json_util.py +874 -0
- data/lib/bson/max_key.py +55 -0
- data/lib/bson/min_key.py +55 -0
- data/lib/bson/objectid.py +286 -0
- data/lib/bson/py.typed +2 -0
- data/lib/bson/raw_bson.py +175 -0
- data/lib/bson/regex.py +135 -0
- data/lib/bson/son.py +208 -0
- data/lib/bson/timestamp.py +124 -0
- data/lib/bson/tz_util.py +52 -0
- data/lib/gridfs/__init__.py +1015 -0
- data/lib/gridfs/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/gridfs/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/gridfs/__pycache__/grid_file.cpython-310.pyc +0 -0
- data/lib/gridfs/errors.py +33 -0
- data/lib/gridfs/grid_file.py +907 -0
- data/lib/gridfs/py.typed +2 -0
- data/lib/pymongo/__init__.py +185 -0
- data/lib/pymongo/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/_csot.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/aggregation.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/auth.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/auth_aws.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/bulk.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/change_stream.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/client_options.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/client_session.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/collation.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/collection.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/command_cursor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/common.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/compression_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/cursor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/daemon.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/database.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/driver_info.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/encryption.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/encryption_options.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/event_loggers.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/hello.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/helpers.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/max_staleness_selectors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/message.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/mongo_client.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/monitor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/monitoring.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/network.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ocsp_cache.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ocsp_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/operations.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/periodic_executor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/pool.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/pyopenssl_context.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/read_concern.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/read_preferences.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/response.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/results.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/saslprep.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_api.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_description.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_selectors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_type.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/settings.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/socket_checker.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/srv_resolver.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ssl_context.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ssl_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/topology.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/topology_description.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/typings.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/uri_parser.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/write_concern.cpython-310.pyc +0 -0
- data/lib/pymongo/_cmessage.cpython-310-x86_64-linux-gnu.so +0 -0
- data/lib/pymongo/_csot.py +118 -0
- data/lib/pymongo/aggregation.py +229 -0
- data/lib/pymongo/auth.py +549 -0
- data/lib/pymongo/auth_aws.py +94 -0
- data/lib/pymongo/bulk.py +513 -0
- data/lib/pymongo/change_stream.py +457 -0
- data/lib/pymongo/client_options.py +302 -0
- data/lib/pymongo/client_session.py +1112 -0
- data/lib/pymongo/collation.py +224 -0
- data/lib/pymongo/collection.py +3204 -0
- data/lib/pymongo/command_cursor.py +353 -0
- data/lib/pymongo/common.py +984 -0
- data/lib/pymongo/compression_support.py +149 -0
- data/lib/pymongo/cursor.py +1345 -0
- data/lib/pymongo/daemon.py +141 -0
- data/lib/pymongo/database.py +1202 -0
- data/lib/pymongo/driver_info.py +42 -0
- data/lib/pymongo/encryption.py +884 -0
- data/lib/pymongo/encryption_options.py +221 -0
- data/lib/pymongo/errors.py +365 -0
- data/lib/pymongo/event_loggers.py +221 -0
- data/lib/pymongo/hello.py +219 -0
- data/lib/pymongo/helpers.py +259 -0
- data/lib/pymongo/max_staleness_selectors.py +114 -0
- data/lib/pymongo/message.py +1440 -0
- data/lib/pymongo/mongo_client.py +2144 -0
- data/lib/pymongo/monitor.py +440 -0
- data/lib/pymongo/monitoring.py +1801 -0
- data/lib/pymongo/network.py +311 -0
- data/lib/pymongo/ocsp_cache.py +87 -0
- data/lib/pymongo/ocsp_support.py +372 -0
- data/lib/pymongo/operations.py +507 -0
- data/lib/pymongo/periodic_executor.py +183 -0
- data/lib/pymongo/pool.py +1660 -0
- data/lib/pymongo/py.typed +2 -0
- data/lib/pymongo/pyopenssl_context.py +383 -0
- data/lib/pymongo/read_concern.py +75 -0
- data/lib/pymongo/read_preferences.py +609 -0
- data/lib/pymongo/response.py +109 -0
- data/lib/pymongo/results.py +217 -0
- data/lib/pymongo/saslprep.py +113 -0
- data/lib/pymongo/server.py +247 -0
- data/lib/pymongo/server_api.py +170 -0
- data/lib/pymongo/server_description.py +285 -0
- data/lib/pymongo/server_selectors.py +153 -0
- data/lib/pymongo/server_type.py +32 -0
- data/lib/pymongo/settings.py +159 -0
- data/lib/pymongo/socket_checker.py +104 -0
- data/lib/pymongo/srv_resolver.py +126 -0
- data/lib/pymongo/ssl_context.py +39 -0
- data/lib/pymongo/ssl_support.py +99 -0
- data/lib/pymongo/topology.py +890 -0
- data/lib/pymongo/topology_description.py +639 -0
- data/lib/pymongo/typings.py +39 -0
- data/lib/pymongo/uri_parser.py +624 -0
- data/lib/pymongo/write_concern.py +129 -0
- data/lib/pymongo-4.2.0.dist-info/INSTALLER +1 -0
- data/lib/pymongo-4.2.0.dist-info/LICENSE +201 -0
- data/lib/pymongo-4.2.0.dist-info/METADATA +250 -0
- data/lib/pymongo-4.2.0.dist-info/RECORD +167 -0
- data/lib/pymongo-4.2.0.dist-info/REQUESTED +0 -0
- data/lib/pymongo-4.2.0.dist-info/WHEEL +6 -0
- data/lib/pymongo-4.2.0.dist-info/top_level.txt +3 -0
- data/lib/sensu-plugins-mongodb/metrics.rb +391 -0
- data/lib/sensu-plugins-mongodb/version.rb +9 -0
- data/lib/sensu-plugins-mongodb.rb +1 -0
- 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])
|