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,221 @@
|
|
1
|
+
# Copyright 2020-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
|
+
|
16
|
+
"""Example event logger classes.
|
17
|
+
|
18
|
+
.. versionadded:: 3.11
|
19
|
+
|
20
|
+
These loggers can be registered using :func:`register` or
|
21
|
+
:class:`~pymongo.mongo_client.MongoClient`.
|
22
|
+
|
23
|
+
``monitoring.register(CommandLogger())``
|
24
|
+
|
25
|
+
or
|
26
|
+
|
27
|
+
``MongoClient(event_listeners=[CommandLogger()])``
|
28
|
+
"""
|
29
|
+
import logging
|
30
|
+
|
31
|
+
from pymongo import monitoring
|
32
|
+
|
33
|
+
|
34
|
+
class CommandLogger(monitoring.CommandListener):
|
35
|
+
"""A simple listener that logs command events.
|
36
|
+
|
37
|
+
Listens for :class:`~pymongo.monitoring.CommandStartedEvent`,
|
38
|
+
:class:`~pymongo.monitoring.CommandSucceededEvent` and
|
39
|
+
:class:`~pymongo.monitoring.CommandFailedEvent` events and
|
40
|
+
logs them at the `INFO` severity level using :mod:`logging`.
|
41
|
+
.. versionadded:: 3.11
|
42
|
+
"""
|
43
|
+
|
44
|
+
def started(self, event: monitoring.CommandStartedEvent) -> None:
|
45
|
+
logging.info(
|
46
|
+
f"Command {event.command_name} with request id "
|
47
|
+
f"{event.request_id} started on server "
|
48
|
+
f"{event.connection_id}"
|
49
|
+
)
|
50
|
+
|
51
|
+
def succeeded(self, event: monitoring.CommandSucceededEvent) -> None:
|
52
|
+
logging.info(
|
53
|
+
f"Command {event.command_name} with request id "
|
54
|
+
f"{event.request_id} on server {event.connection_id} "
|
55
|
+
f"succeeded in {event.duration_micros} "
|
56
|
+
"microseconds"
|
57
|
+
)
|
58
|
+
|
59
|
+
def failed(self, event: monitoring.CommandFailedEvent) -> None:
|
60
|
+
logging.info(
|
61
|
+
f"Command {event.command_name} with request id "
|
62
|
+
f"{event.request_id} on server {event.connection_id} "
|
63
|
+
f"failed in {event.duration_micros} "
|
64
|
+
"microseconds"
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
class ServerLogger(monitoring.ServerListener):
|
69
|
+
"""A simple listener that logs server discovery events.
|
70
|
+
|
71
|
+
Listens for :class:`~pymongo.monitoring.ServerOpeningEvent`,
|
72
|
+
:class:`~pymongo.monitoring.ServerDescriptionChangedEvent`,
|
73
|
+
and :class:`~pymongo.monitoring.ServerClosedEvent`
|
74
|
+
events and logs them at the `INFO` severity level using :mod:`logging`.
|
75
|
+
|
76
|
+
.. versionadded:: 3.11
|
77
|
+
"""
|
78
|
+
|
79
|
+
def opened(self, event: monitoring.ServerOpeningEvent) -> None:
|
80
|
+
logging.info(f"Server {event.server_address} added to topology {event.topology_id}")
|
81
|
+
|
82
|
+
def description_changed(self, event: monitoring.ServerDescriptionChangedEvent) -> None:
|
83
|
+
previous_server_type = event.previous_description.server_type
|
84
|
+
new_server_type = event.new_description.server_type
|
85
|
+
if new_server_type != previous_server_type:
|
86
|
+
# server_type_name was added in PyMongo 3.4
|
87
|
+
logging.info(
|
88
|
+
f"Server {event.server_address} changed type from "
|
89
|
+
f"{event.previous_description.server_type_name} to "
|
90
|
+
f"{event.new_description.server_type_name}"
|
91
|
+
)
|
92
|
+
|
93
|
+
def closed(self, event: monitoring.ServerClosedEvent) -> None:
|
94
|
+
logging.warning(f"Server {event.server_address} removed from topology {event.topology_id}")
|
95
|
+
|
96
|
+
|
97
|
+
class HeartbeatLogger(monitoring.ServerHeartbeatListener):
|
98
|
+
"""A simple listener that logs server heartbeat events.
|
99
|
+
|
100
|
+
Listens for :class:`~pymongo.monitoring.ServerHeartbeatStartedEvent`,
|
101
|
+
:class:`~pymongo.monitoring.ServerHeartbeatSucceededEvent`,
|
102
|
+
and :class:`~pymongo.monitoring.ServerHeartbeatFailedEvent`
|
103
|
+
events and logs them at the `INFO` severity level using :mod:`logging`.
|
104
|
+
|
105
|
+
.. versionadded:: 3.11
|
106
|
+
"""
|
107
|
+
|
108
|
+
def started(self, event: monitoring.ServerHeartbeatStartedEvent) -> None:
|
109
|
+
logging.info(f"Heartbeat sent to server {event.connection_id}")
|
110
|
+
|
111
|
+
def succeeded(self, event: monitoring.ServerHeartbeatSucceededEvent) -> None:
|
112
|
+
# The reply.document attribute was added in PyMongo 3.4.
|
113
|
+
logging.info(
|
114
|
+
f"Heartbeat to server {event.connection_id} "
|
115
|
+
"succeeded with reply "
|
116
|
+
f"{event.reply.document}"
|
117
|
+
)
|
118
|
+
|
119
|
+
def failed(self, event: monitoring.ServerHeartbeatFailedEvent) -> None:
|
120
|
+
logging.warning(
|
121
|
+
f"Heartbeat to server {event.connection_id} failed with error {event.reply}"
|
122
|
+
)
|
123
|
+
|
124
|
+
|
125
|
+
class TopologyLogger(monitoring.TopologyListener):
|
126
|
+
"""A simple listener that logs server topology events.
|
127
|
+
|
128
|
+
Listens for :class:`~pymongo.monitoring.TopologyOpenedEvent`,
|
129
|
+
:class:`~pymongo.monitoring.TopologyDescriptionChangedEvent`,
|
130
|
+
and :class:`~pymongo.monitoring.TopologyClosedEvent`
|
131
|
+
events and logs them at the `INFO` severity level using :mod:`logging`.
|
132
|
+
|
133
|
+
.. versionadded:: 3.11
|
134
|
+
"""
|
135
|
+
|
136
|
+
def opened(self, event: monitoring.TopologyOpenedEvent) -> None:
|
137
|
+
logging.info(f"Topology with id {event.topology_id} opened")
|
138
|
+
|
139
|
+
def description_changed(self, event: monitoring.TopologyDescriptionChangedEvent) -> None:
|
140
|
+
logging.info(f"Topology description updated for topology id {event.topology_id}")
|
141
|
+
previous_topology_type = event.previous_description.topology_type
|
142
|
+
new_topology_type = event.new_description.topology_type
|
143
|
+
if new_topology_type != previous_topology_type:
|
144
|
+
# topology_type_name was added in PyMongo 3.4
|
145
|
+
logging.info(
|
146
|
+
f"Topology {event.topology_id} changed type from "
|
147
|
+
f"{event.previous_description.topology_type_name} to "
|
148
|
+
f"{event.new_description.topology_type_name}"
|
149
|
+
)
|
150
|
+
# The has_writable_server and has_readable_server methods
|
151
|
+
# were added in PyMongo 3.4.
|
152
|
+
if not event.new_description.has_writable_server():
|
153
|
+
logging.warning("No writable servers available.")
|
154
|
+
if not event.new_description.has_readable_server():
|
155
|
+
logging.warning("No readable servers available.")
|
156
|
+
|
157
|
+
def closed(self, event: monitoring.TopologyClosedEvent) -> None:
|
158
|
+
logging.info(f"Topology with id {event.topology_id} closed")
|
159
|
+
|
160
|
+
|
161
|
+
class ConnectionPoolLogger(monitoring.ConnectionPoolListener):
|
162
|
+
"""A simple listener that logs server connection pool events.
|
163
|
+
|
164
|
+
Listens for :class:`~pymongo.monitoring.PoolCreatedEvent`,
|
165
|
+
:class:`~pymongo.monitoring.PoolClearedEvent`,
|
166
|
+
:class:`~pymongo.monitoring.PoolClosedEvent`,
|
167
|
+
:~pymongo.monitoring.class:`ConnectionCreatedEvent`,
|
168
|
+
:class:`~pymongo.monitoring.ConnectionReadyEvent`,
|
169
|
+
:class:`~pymongo.monitoring.ConnectionClosedEvent`,
|
170
|
+
:class:`~pymongo.monitoring.ConnectionCheckOutStartedEvent`,
|
171
|
+
:class:`~pymongo.monitoring.ConnectionCheckOutFailedEvent`,
|
172
|
+
:class:`~pymongo.monitoring.ConnectionCheckedOutEvent`,
|
173
|
+
and :class:`~pymongo.monitoring.ConnectionCheckedInEvent`
|
174
|
+
events and logs them at the `INFO` severity level using :mod:`logging`.
|
175
|
+
|
176
|
+
.. versionadded:: 3.11
|
177
|
+
"""
|
178
|
+
|
179
|
+
def pool_created(self, event: monitoring.PoolCreatedEvent) -> None:
|
180
|
+
logging.info(f"[pool {event.address}] pool created")
|
181
|
+
|
182
|
+
def pool_ready(self, event):
|
183
|
+
logging.info(f"[pool {event.address}] pool ready")
|
184
|
+
|
185
|
+
def pool_cleared(self, event: monitoring.PoolClearedEvent) -> None:
|
186
|
+
logging.info(f"[pool {event.address}] pool cleared")
|
187
|
+
|
188
|
+
def pool_closed(self, event: monitoring.PoolClosedEvent) -> None:
|
189
|
+
logging.info(f"[pool {event.address}] pool closed")
|
190
|
+
|
191
|
+
def connection_created(self, event: monitoring.ConnectionCreatedEvent) -> None:
|
192
|
+
logging.info(f"[pool {event.address}][conn #{event.connection_id}] connection created")
|
193
|
+
|
194
|
+
def connection_ready(self, event: monitoring.ConnectionReadyEvent) -> None:
|
195
|
+
logging.info(
|
196
|
+
f"[pool {event.address}][conn #{event.connection_id}] connection setup succeeded"
|
197
|
+
)
|
198
|
+
|
199
|
+
def connection_closed(self, event: monitoring.ConnectionClosedEvent) -> None:
|
200
|
+
logging.info(
|
201
|
+
f"[pool {event.address}][conn #{event.connection_id}] "
|
202
|
+
f'connection closed, reason: "{event.reason}"'
|
203
|
+
)
|
204
|
+
|
205
|
+
def connection_check_out_started(
|
206
|
+
self, event: monitoring.ConnectionCheckOutStartedEvent
|
207
|
+
) -> None:
|
208
|
+
logging.info(f"[pool {event.address}] connection check out started")
|
209
|
+
|
210
|
+
def connection_check_out_failed(self, event: monitoring.ConnectionCheckOutFailedEvent) -> None:
|
211
|
+
logging.info(f"[pool {event.address}] connection check out failed, reason: {event.reason}")
|
212
|
+
|
213
|
+
def connection_checked_out(self, event: monitoring.ConnectionCheckedOutEvent) -> None:
|
214
|
+
logging.info(
|
215
|
+
f"[pool {event.address}][conn #{event.connection_id}] connection checked out of pool"
|
216
|
+
)
|
217
|
+
|
218
|
+
def connection_checked_in(self, event: monitoring.ConnectionCheckedInEvent) -> None:
|
219
|
+
logging.info(
|
220
|
+
f"[pool {event.address}][conn #{event.connection_id}] connection checked into pool"
|
221
|
+
)
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# Copyright 2021-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
|
+
"""Helpers for the 'hello' and legacy hello commands."""
|
16
|
+
|
17
|
+
import copy
|
18
|
+
import datetime
|
19
|
+
import itertools
|
20
|
+
from typing import Any, Generic, List, Mapping, Optional, Set, Tuple
|
21
|
+
|
22
|
+
from bson.objectid import ObjectId
|
23
|
+
from pymongo import common
|
24
|
+
from pymongo.server_type import SERVER_TYPE
|
25
|
+
from pymongo.typings import _DocumentType
|
26
|
+
|
27
|
+
|
28
|
+
class HelloCompat:
|
29
|
+
CMD = "hello"
|
30
|
+
LEGACY_CMD = "ismaster"
|
31
|
+
PRIMARY = "isWritablePrimary"
|
32
|
+
LEGACY_PRIMARY = "ismaster"
|
33
|
+
LEGACY_ERROR = "not master"
|
34
|
+
|
35
|
+
|
36
|
+
def _get_server_type(doc):
|
37
|
+
"""Determine the server type from a hello response."""
|
38
|
+
if not doc.get("ok"):
|
39
|
+
return SERVER_TYPE.Unknown
|
40
|
+
|
41
|
+
if doc.get("serviceId"):
|
42
|
+
return SERVER_TYPE.LoadBalancer
|
43
|
+
elif doc.get("isreplicaset"):
|
44
|
+
return SERVER_TYPE.RSGhost
|
45
|
+
elif doc.get("setName"):
|
46
|
+
if doc.get("hidden"):
|
47
|
+
return SERVER_TYPE.RSOther
|
48
|
+
elif doc.get(HelloCompat.PRIMARY):
|
49
|
+
return SERVER_TYPE.RSPrimary
|
50
|
+
elif doc.get(HelloCompat.LEGACY_PRIMARY):
|
51
|
+
return SERVER_TYPE.RSPrimary
|
52
|
+
elif doc.get("secondary"):
|
53
|
+
return SERVER_TYPE.RSSecondary
|
54
|
+
elif doc.get("arbiterOnly"):
|
55
|
+
return SERVER_TYPE.RSArbiter
|
56
|
+
else:
|
57
|
+
return SERVER_TYPE.RSOther
|
58
|
+
elif doc.get("msg") == "isdbgrid":
|
59
|
+
return SERVER_TYPE.Mongos
|
60
|
+
else:
|
61
|
+
return SERVER_TYPE.Standalone
|
62
|
+
|
63
|
+
|
64
|
+
class Hello(Generic[_DocumentType]):
|
65
|
+
"""Parse a hello response from the server.
|
66
|
+
|
67
|
+
.. versionadded:: 3.12
|
68
|
+
"""
|
69
|
+
|
70
|
+
__slots__ = ("_doc", "_server_type", "_is_writable", "_is_readable", "_awaitable")
|
71
|
+
|
72
|
+
def __init__(self, doc: _DocumentType, awaitable: bool = False) -> None:
|
73
|
+
self._server_type = _get_server_type(doc)
|
74
|
+
self._doc: _DocumentType = doc
|
75
|
+
self._is_writable = self._server_type in (
|
76
|
+
SERVER_TYPE.RSPrimary,
|
77
|
+
SERVER_TYPE.Standalone,
|
78
|
+
SERVER_TYPE.Mongos,
|
79
|
+
SERVER_TYPE.LoadBalancer,
|
80
|
+
)
|
81
|
+
|
82
|
+
self._is_readable = self.server_type == SERVER_TYPE.RSSecondary or self._is_writable
|
83
|
+
self._awaitable = awaitable
|
84
|
+
|
85
|
+
@property
|
86
|
+
def document(self) -> _DocumentType:
|
87
|
+
"""The complete hello command response document.
|
88
|
+
|
89
|
+
.. versionadded:: 3.4
|
90
|
+
"""
|
91
|
+
return copy.copy(self._doc)
|
92
|
+
|
93
|
+
@property
|
94
|
+
def server_type(self) -> int:
|
95
|
+
return self._server_type
|
96
|
+
|
97
|
+
@property
|
98
|
+
def all_hosts(self) -> Set[Tuple[str, int]]:
|
99
|
+
"""List of hosts, passives, and arbiters known to this server."""
|
100
|
+
return set(
|
101
|
+
map(
|
102
|
+
common.clean_node,
|
103
|
+
itertools.chain(
|
104
|
+
self._doc.get("hosts", []),
|
105
|
+
self._doc.get("passives", []),
|
106
|
+
self._doc.get("arbiters", []),
|
107
|
+
),
|
108
|
+
)
|
109
|
+
)
|
110
|
+
|
111
|
+
@property
|
112
|
+
def tags(self) -> Mapping[str, Any]:
|
113
|
+
"""Replica set member tags or empty dict."""
|
114
|
+
return self._doc.get("tags", {})
|
115
|
+
|
116
|
+
@property
|
117
|
+
def primary(self) -> Optional[Tuple[str, int]]:
|
118
|
+
"""This server's opinion about who the primary is, or None."""
|
119
|
+
if self._doc.get("primary"):
|
120
|
+
return common.partition_node(self._doc["primary"])
|
121
|
+
else:
|
122
|
+
return None
|
123
|
+
|
124
|
+
@property
|
125
|
+
def replica_set_name(self) -> Optional[str]:
|
126
|
+
"""Replica set name or None."""
|
127
|
+
return self._doc.get("setName")
|
128
|
+
|
129
|
+
@property
|
130
|
+
def max_bson_size(self) -> int:
|
131
|
+
return self._doc.get("maxBsonObjectSize", common.MAX_BSON_SIZE)
|
132
|
+
|
133
|
+
@property
|
134
|
+
def max_message_size(self) -> int:
|
135
|
+
return self._doc.get("maxMessageSizeBytes", 2 * self.max_bson_size)
|
136
|
+
|
137
|
+
@property
|
138
|
+
def max_write_batch_size(self) -> int:
|
139
|
+
return self._doc.get("maxWriteBatchSize", common.MAX_WRITE_BATCH_SIZE)
|
140
|
+
|
141
|
+
@property
|
142
|
+
def min_wire_version(self) -> int:
|
143
|
+
return self._doc.get("minWireVersion", common.MIN_WIRE_VERSION)
|
144
|
+
|
145
|
+
@property
|
146
|
+
def max_wire_version(self) -> int:
|
147
|
+
return self._doc.get("maxWireVersion", common.MAX_WIRE_VERSION)
|
148
|
+
|
149
|
+
@property
|
150
|
+
def set_version(self) -> Optional[int]:
|
151
|
+
return self._doc.get("setVersion")
|
152
|
+
|
153
|
+
@property
|
154
|
+
def election_id(self) -> Optional[ObjectId]:
|
155
|
+
return self._doc.get("electionId")
|
156
|
+
|
157
|
+
@property
|
158
|
+
def cluster_time(self) -> Optional[Mapping[str, Any]]:
|
159
|
+
return self._doc.get("$clusterTime")
|
160
|
+
|
161
|
+
@property
|
162
|
+
def logical_session_timeout_minutes(self) -> Optional[int]:
|
163
|
+
return self._doc.get("logicalSessionTimeoutMinutes")
|
164
|
+
|
165
|
+
@property
|
166
|
+
def is_writable(self) -> bool:
|
167
|
+
return self._is_writable
|
168
|
+
|
169
|
+
@property
|
170
|
+
def is_readable(self) -> bool:
|
171
|
+
return self._is_readable
|
172
|
+
|
173
|
+
@property
|
174
|
+
def me(self) -> Optional[Tuple[str, int]]:
|
175
|
+
me = self._doc.get("me")
|
176
|
+
if me:
|
177
|
+
return common.clean_node(me)
|
178
|
+
return None
|
179
|
+
|
180
|
+
@property
|
181
|
+
def last_write_date(self) -> Optional[datetime.datetime]:
|
182
|
+
return self._doc.get("lastWrite", {}).get("lastWriteDate")
|
183
|
+
|
184
|
+
@property
|
185
|
+
def compressors(self) -> Optional[List[str]]:
|
186
|
+
return self._doc.get("compression")
|
187
|
+
|
188
|
+
@property
|
189
|
+
def sasl_supported_mechs(self) -> List[str]:
|
190
|
+
"""Supported authentication mechanisms for the current user.
|
191
|
+
|
192
|
+
For example::
|
193
|
+
|
194
|
+
>>> hello.sasl_supported_mechs
|
195
|
+
["SCRAM-SHA-1", "SCRAM-SHA-256"]
|
196
|
+
|
197
|
+
"""
|
198
|
+
return self._doc.get("saslSupportedMechs", [])
|
199
|
+
|
200
|
+
@property
|
201
|
+
def speculative_authenticate(self) -> Optional[Mapping[str, Any]]:
|
202
|
+
"""The speculativeAuthenticate field."""
|
203
|
+
return self._doc.get("speculativeAuthenticate")
|
204
|
+
|
205
|
+
@property
|
206
|
+
def topology_version(self) -> Optional[Mapping[str, Any]]:
|
207
|
+
return self._doc.get("topologyVersion")
|
208
|
+
|
209
|
+
@property
|
210
|
+
def awaitable(self) -> bool:
|
211
|
+
return self._awaitable
|
212
|
+
|
213
|
+
@property
|
214
|
+
def service_id(self) -> Optional[ObjectId]:
|
215
|
+
return self._doc.get("serviceId")
|
216
|
+
|
217
|
+
@property
|
218
|
+
def hello_ok(self) -> bool:
|
219
|
+
return self._doc.get("helloOk", False)
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# Copyright 2009-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
|
+
"""Bits and pieces used by the driver that don't really fit elsewhere."""
|
16
|
+
|
17
|
+
import sys
|
18
|
+
import traceback
|
19
|
+
from collections import abc
|
20
|
+
from typing import Any, List, NoReturn
|
21
|
+
|
22
|
+
from bson.son import SON
|
23
|
+
from pymongo import ASCENDING
|
24
|
+
from pymongo.errors import (
|
25
|
+
CursorNotFound,
|
26
|
+
DuplicateKeyError,
|
27
|
+
ExecutionTimeout,
|
28
|
+
NotPrimaryError,
|
29
|
+
OperationFailure,
|
30
|
+
WriteConcernError,
|
31
|
+
WriteError,
|
32
|
+
WTimeoutError,
|
33
|
+
_wtimeout_error,
|
34
|
+
)
|
35
|
+
from pymongo.hello import HelloCompat
|
36
|
+
|
37
|
+
# From the SDAM spec, the "node is shutting down" codes.
|
38
|
+
_SHUTDOWN_CODES = frozenset(
|
39
|
+
[
|
40
|
+
11600, # InterruptedAtShutdown
|
41
|
+
91, # ShutdownInProgress
|
42
|
+
]
|
43
|
+
)
|
44
|
+
# From the SDAM spec, the "not primary" error codes are combined with the
|
45
|
+
# "node is recovering" error codes (of which the "node is shutting down"
|
46
|
+
# errors are a subset).
|
47
|
+
_NOT_PRIMARY_CODES = (
|
48
|
+
frozenset(
|
49
|
+
[
|
50
|
+
10058, # LegacyNotPrimary <=3.2 "not primary" error code
|
51
|
+
10107, # NotWritablePrimary
|
52
|
+
13435, # NotPrimaryNoSecondaryOk
|
53
|
+
11602, # InterruptedDueToReplStateChange
|
54
|
+
13436, # NotPrimaryOrSecondary
|
55
|
+
189, # PrimarySteppedDown
|
56
|
+
]
|
57
|
+
)
|
58
|
+
| _SHUTDOWN_CODES
|
59
|
+
)
|
60
|
+
# From the retryable writes spec.
|
61
|
+
_RETRYABLE_ERROR_CODES = _NOT_PRIMARY_CODES | frozenset(
|
62
|
+
[
|
63
|
+
7, # HostNotFound
|
64
|
+
6, # HostUnreachable
|
65
|
+
89, # NetworkTimeout
|
66
|
+
9001, # SocketException
|
67
|
+
262, # ExceededTimeLimit
|
68
|
+
]
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
def _gen_index_name(keys):
|
73
|
+
"""Generate an index name from the set of fields it is over."""
|
74
|
+
return "_".join(["%s_%s" % item for item in keys])
|
75
|
+
|
76
|
+
|
77
|
+
def _index_list(key_or_list, direction=None):
|
78
|
+
"""Helper to generate a list of (key, direction) pairs.
|
79
|
+
|
80
|
+
Takes such a list, or a single key, or a single key and direction.
|
81
|
+
"""
|
82
|
+
if direction is not None:
|
83
|
+
return [(key_or_list, direction)]
|
84
|
+
else:
|
85
|
+
if isinstance(key_or_list, str):
|
86
|
+
return [(key_or_list, ASCENDING)]
|
87
|
+
if isinstance(key_or_list, abc.ItemsView):
|
88
|
+
return list(key_or_list)
|
89
|
+
elif not isinstance(key_or_list, (list, tuple)):
|
90
|
+
raise TypeError("if no direction is specified, key_or_list must be an instance of list")
|
91
|
+
return key_or_list
|
92
|
+
|
93
|
+
|
94
|
+
def _index_document(index_list):
|
95
|
+
"""Helper to generate an index specifying document.
|
96
|
+
|
97
|
+
Takes a list of (key, direction) pairs.
|
98
|
+
"""
|
99
|
+
if isinstance(index_list, abc.Mapping):
|
100
|
+
raise TypeError(
|
101
|
+
"passing a dict to sort/create_index/hint is not "
|
102
|
+
"allowed - use a list of tuples instead. did you "
|
103
|
+
"mean %r?" % list(index_list.items())
|
104
|
+
)
|
105
|
+
elif not isinstance(index_list, (list, tuple)):
|
106
|
+
raise TypeError("must use a list of (key, direction) pairs, not: " + repr(index_list))
|
107
|
+
if not len(index_list):
|
108
|
+
raise ValueError("key_or_list must not be the empty list")
|
109
|
+
|
110
|
+
index: SON[str, Any] = SON()
|
111
|
+
for (key, value) in index_list:
|
112
|
+
if not isinstance(key, str):
|
113
|
+
raise TypeError("first item in each key pair must be an instance of str")
|
114
|
+
if not isinstance(value, (str, int, abc.Mapping)):
|
115
|
+
raise TypeError(
|
116
|
+
"second item in each key pair must be 1, -1, "
|
117
|
+
"'2d', or another valid MongoDB index specifier."
|
118
|
+
)
|
119
|
+
index[key] = value
|
120
|
+
return index
|
121
|
+
|
122
|
+
|
123
|
+
def _check_command_response(
|
124
|
+
response, max_wire_version, allowable_errors=None, parse_write_concern_error=False
|
125
|
+
):
|
126
|
+
"""Check the response to a command for errors."""
|
127
|
+
if "ok" not in response:
|
128
|
+
# Server didn't recognize our message as a command.
|
129
|
+
raise OperationFailure(
|
130
|
+
response.get("$err"), response.get("code"), response, max_wire_version
|
131
|
+
)
|
132
|
+
|
133
|
+
if parse_write_concern_error and "writeConcernError" in response:
|
134
|
+
_error = response["writeConcernError"]
|
135
|
+
_labels = response.get("errorLabels")
|
136
|
+
if _labels:
|
137
|
+
_error.update({"errorLabels": _labels})
|
138
|
+
_raise_write_concern_error(_error)
|
139
|
+
|
140
|
+
if response["ok"]:
|
141
|
+
return
|
142
|
+
|
143
|
+
details = response
|
144
|
+
# Mongos returns the error details in a 'raw' object
|
145
|
+
# for some errors.
|
146
|
+
if "raw" in response:
|
147
|
+
for shard in response["raw"].values():
|
148
|
+
# Grab the first non-empty raw error from a shard.
|
149
|
+
if shard.get("errmsg") and not shard.get("ok"):
|
150
|
+
details = shard
|
151
|
+
break
|
152
|
+
|
153
|
+
errmsg = details["errmsg"]
|
154
|
+
code = details.get("code")
|
155
|
+
|
156
|
+
# For allowable errors, only check for error messages when the code is not
|
157
|
+
# included.
|
158
|
+
if allowable_errors:
|
159
|
+
if code is not None:
|
160
|
+
if code in allowable_errors:
|
161
|
+
return
|
162
|
+
elif errmsg in allowable_errors:
|
163
|
+
return
|
164
|
+
|
165
|
+
# Server is "not primary" or "recovering"
|
166
|
+
if code is not None:
|
167
|
+
if code in _NOT_PRIMARY_CODES:
|
168
|
+
raise NotPrimaryError(errmsg, response)
|
169
|
+
elif HelloCompat.LEGACY_ERROR in errmsg or "node is recovering" in errmsg:
|
170
|
+
raise NotPrimaryError(errmsg, response)
|
171
|
+
|
172
|
+
# Other errors
|
173
|
+
# findAndModify with upsert can raise duplicate key error
|
174
|
+
if code in (11000, 11001, 12582):
|
175
|
+
raise DuplicateKeyError(errmsg, code, response, max_wire_version)
|
176
|
+
elif code == 50:
|
177
|
+
raise ExecutionTimeout(errmsg, code, response, max_wire_version)
|
178
|
+
elif code == 43:
|
179
|
+
raise CursorNotFound(errmsg, code, response, max_wire_version)
|
180
|
+
|
181
|
+
raise OperationFailure(errmsg, code, response, max_wire_version)
|
182
|
+
|
183
|
+
|
184
|
+
def _raise_last_write_error(write_errors: List[Any]) -> NoReturn:
|
185
|
+
# If the last batch had multiple errors only report
|
186
|
+
# the last error to emulate continue_on_error.
|
187
|
+
error = write_errors[-1]
|
188
|
+
if error.get("code") == 11000:
|
189
|
+
raise DuplicateKeyError(error.get("errmsg"), 11000, error)
|
190
|
+
raise WriteError(error.get("errmsg"), error.get("code"), error)
|
191
|
+
|
192
|
+
|
193
|
+
def _raise_write_concern_error(error: Any) -> NoReturn:
|
194
|
+
if _wtimeout_error(error):
|
195
|
+
# Make sure we raise WTimeoutError
|
196
|
+
raise WTimeoutError(error.get("errmsg"), error.get("code"), error)
|
197
|
+
raise WriteConcernError(error.get("errmsg"), error.get("code"), error)
|
198
|
+
|
199
|
+
|
200
|
+
def _get_wce_doc(result):
|
201
|
+
"""Return the writeConcernError or None."""
|
202
|
+
wce = result.get("writeConcernError")
|
203
|
+
if wce:
|
204
|
+
# The server reports errorLabels at the top level but it's more
|
205
|
+
# convenient to attach it to the writeConcernError doc itself.
|
206
|
+
error_labels = result.get("errorLabels")
|
207
|
+
if error_labels:
|
208
|
+
wce["errorLabels"] = error_labels
|
209
|
+
return wce
|
210
|
+
|
211
|
+
|
212
|
+
def _check_write_command_response(result):
|
213
|
+
"""Backward compatibility helper for write command error handling."""
|
214
|
+
# Prefer write errors over write concern errors
|
215
|
+
write_errors = result.get("writeErrors")
|
216
|
+
if write_errors:
|
217
|
+
_raise_last_write_error(write_errors)
|
218
|
+
|
219
|
+
wce = _get_wce_doc(result)
|
220
|
+
if wce:
|
221
|
+
_raise_write_concern_error(wce)
|
222
|
+
|
223
|
+
|
224
|
+
def _fields_list_to_dict(fields, option_name):
|
225
|
+
"""Takes a sequence of field names and returns a matching dictionary.
|
226
|
+
|
227
|
+
["a", "b"] becomes {"a": 1, "b": 1}
|
228
|
+
|
229
|
+
and
|
230
|
+
|
231
|
+
["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1}
|
232
|
+
"""
|
233
|
+
if isinstance(fields, abc.Mapping):
|
234
|
+
return fields
|
235
|
+
|
236
|
+
if isinstance(fields, (abc.Sequence, abc.Set)):
|
237
|
+
if not all(isinstance(field, str) for field in fields):
|
238
|
+
raise TypeError(
|
239
|
+
"%s must be a list of key names, each an instance of str" % (option_name,)
|
240
|
+
)
|
241
|
+
return dict.fromkeys(fields, 1)
|
242
|
+
|
243
|
+
raise TypeError("%s must be a mapping or list of key names" % (option_name,))
|
244
|
+
|
245
|
+
|
246
|
+
def _handle_exception():
|
247
|
+
"""Print exceptions raised by subscribers to stderr."""
|
248
|
+
# Heavily influenced by logging.Handler.handleError.
|
249
|
+
|
250
|
+
# See note here:
|
251
|
+
# https://docs.python.org/3.4/library/sys.html#sys.__stderr__
|
252
|
+
if sys.stderr:
|
253
|
+
einfo = sys.exc_info()
|
254
|
+
try:
|
255
|
+
traceback.print_exception(einfo[0], einfo[1], einfo[2], None, sys.stderr)
|
256
|
+
except IOError:
|
257
|
+
pass
|
258
|
+
finally:
|
259
|
+
del einfo
|