python-etcd3-async 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- etcd3/__init__.py +90 -0
- etcd3/async_client.py +1495 -0
- etcd3/async_client_extensions.py +878 -0
- etcd3/async_endpoint.py +140 -0
- etcd3/async_managers.py +498 -0
- etcd3/async_monitor.py +16 -0
- etcd3/async_watch.py +462 -0
- etcd3/client.py +2021 -0
- etcd3/endpoint.py +79 -0
- etcd3/events.py +40 -0
- etcd3/exceptions.py +47 -0
- etcd3/exporters/__init__.py +18 -0
- etcd3/exporters/prometheus.py +365 -0
- etcd3/leases.py +106 -0
- etcd3/locks.py +593 -0
- etcd3/log.py +146 -0
- etcd3/log_formatters.py +228 -0
- etcd3/log_sampling.py +221 -0
- etcd3/members.py +44 -0
- etcd3/observability.py +431 -0
- etcd3/shared.py +486 -0
- etcd3/trace.py +231 -0
- etcd3/trace_attributes.py +434 -0
- etcd3/transactions.py +280 -0
- etcd3/utils.py +158 -0
- etcd3/watch.py +270 -0
- etcdrpc/__init__.py +16 -0
- etcdrpc/auth.proto +49 -0
- etcdrpc/auth_pb2.py +43 -0
- etcdrpc/auth_pb2.pyi +44 -0
- etcdrpc/auth_pb2_grpc.py +24 -0
- etcdrpc/kv.proto +43 -0
- etcdrpc/kv_pb2.py +40 -0
- etcdrpc/kv_pb2.pyi +39 -0
- etcdrpc/kv_pb2_grpc.py +24 -0
- etcdrpc/rpc.proto +924 -0
- etcdrpc/rpc_pb2.py +237 -0
- etcdrpc/rpc_pb2.pyi +811 -0
- etcdrpc/rpc_pb2_grpc.py +1893 -0
- python_etcd3_async-0.1.0.dist-info/METADATA +328 -0
- python_etcd3_async-0.1.0.dist-info/RECORD +42 -0
- python_etcd3_async-0.1.0.dist-info/WHEEL +4 -0
etcd3/async_endpoint.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Async endpoint implementation for etcd3 client.
|
|
2
|
+
|
|
3
|
+
This module provides the AsyncEndpoint class for managing etcd cluster endpoints
|
|
4
|
+
in async operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
|
|
9
|
+
import grpc
|
|
10
|
+
|
|
11
|
+
from etcd3.endpoint import BaseEndpoint
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AsyncEndpointConfig:
|
|
15
|
+
"""Configuration for AsyncEndpoint creation.
|
|
16
|
+
|
|
17
|
+
:param str host: Endpoint host
|
|
18
|
+
:param int port: Endpoint port
|
|
19
|
+
:param bool secure: Use secure channel, default True
|
|
20
|
+
:param creds: Credentials to use for secure channel, required if
|
|
21
|
+
secure=True
|
|
22
|
+
:type creds: grpc.ChannelCredentials, optional
|
|
23
|
+
:param time_retry: Seconds to wait before retrying this endpoint after
|
|
24
|
+
failure, default 300.0
|
|
25
|
+
:type time_retry: int or float
|
|
26
|
+
:param opts: Additional gRPC options
|
|
27
|
+
:type opts: dict, optional
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, host, port, secure=True, creds=None, time_retry=300.0, opts=None
|
|
32
|
+
):
|
|
33
|
+
self.host = host
|
|
34
|
+
self.port = port
|
|
35
|
+
self.secure = secure
|
|
36
|
+
self.creds = creds
|
|
37
|
+
self.time_retry = time_retry
|
|
38
|
+
self.opts = opts
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AsyncEndpoint(BaseEndpoint):
|
|
42
|
+
"""Represents an etcd cluster endpoint for async operations.
|
|
43
|
+
|
|
44
|
+
:param config: AsyncEndpointConfig object with endpoint configuration
|
|
45
|
+
:param str host: Endpoint host (ignored if config is provided)
|
|
46
|
+
:param int port: Endpoint port (ignored if config is provided)
|
|
47
|
+
:param bool secure: Use secure channel, default True (ignored if config is provided)
|
|
48
|
+
:param creds: Credentials to use for secure channel, required if
|
|
49
|
+
secure=True (ignored if config is provided)
|
|
50
|
+
:type creds: grpc.ChannelCredentials, optional
|
|
51
|
+
:param time_retry: Seconds to wait before retrying this endpoint after
|
|
52
|
+
failure, default 300.0 (ignored if config is provided)
|
|
53
|
+
:type time_retry: int or float
|
|
54
|
+
:param opts: Additional gRPC options (ignored if config is provided)
|
|
55
|
+
:type opts: dict, optional
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
config=None,
|
|
61
|
+
host=None,
|
|
62
|
+
port=None,
|
|
63
|
+
secure=True,
|
|
64
|
+
creds=None,
|
|
65
|
+
time_retry=300.0,
|
|
66
|
+
opts=None,
|
|
67
|
+
):
|
|
68
|
+
# Use config object if provided, otherwise use individual parameters
|
|
69
|
+
if config is not None:
|
|
70
|
+
host = config.host
|
|
71
|
+
port = config.port
|
|
72
|
+
secure = config.secure
|
|
73
|
+
creds = config.creds
|
|
74
|
+
time_retry = config.time_retry
|
|
75
|
+
opts = config.opts
|
|
76
|
+
|
|
77
|
+
if host is None or port is None:
|
|
78
|
+
raise ValueError("Host and port must be provided")
|
|
79
|
+
|
|
80
|
+
super().__init__(host, port, secure, creds, time_retry, opts)
|
|
81
|
+
self.channel = self._mkchannel(opts)
|
|
82
|
+
|
|
83
|
+
def is_healthy(self):
|
|
84
|
+
"""Check if the endpoint is healthy.
|
|
85
|
+
|
|
86
|
+
Only READY state is considered healthy. CONNECTING state is excluded
|
|
87
|
+
to ensure the connection is fully established before use.
|
|
88
|
+
|
|
89
|
+
:returns: True if the endpoint is healthy (READY state)
|
|
90
|
+
:rtype: bool
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
state = self.channel.get_state()
|
|
94
|
+
# Only READY is considered healthy, not CONNECTING
|
|
95
|
+
return state == grpc.ChannelConnectivity.READY
|
|
96
|
+
except Exception:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
async def health_check_async(self):
|
|
100
|
+
"""Perform an asynchronous health check using gRPC status check.
|
|
101
|
+
|
|
102
|
+
This method uses the channel's await_ready() for a more robust
|
|
103
|
+
connectivity check.
|
|
104
|
+
|
|
105
|
+
:returns: True if the endpoint is healthy
|
|
106
|
+
:rtype: bool
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
await asyncio.wait_for(self.channel.healthy(), timeout=2.0)
|
|
110
|
+
return True
|
|
111
|
+
except asyncio.TimeoutError:
|
|
112
|
+
return False
|
|
113
|
+
except Exception:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
async def rebuild_channel(self):
|
|
117
|
+
"""Rebuild the gRPC channel.
|
|
118
|
+
|
|
119
|
+
This method closes the existing channel and creates a new one,
|
|
120
|
+
which is useful when the connection has become stale (e.g., after
|
|
121
|
+
etcd server restart).
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
await self.channel.close()
|
|
125
|
+
except Exception:
|
|
126
|
+
pass # Ignore close errors
|
|
127
|
+
|
|
128
|
+
self.channel = self._mkchannel(self.opts)
|
|
129
|
+
|
|
130
|
+
async def close(self):
|
|
131
|
+
"""Close the gRPC channel."""
|
|
132
|
+
try:
|
|
133
|
+
await self.channel.close()
|
|
134
|
+
except Exception:
|
|
135
|
+
pass # Ignore close errors
|
|
136
|
+
|
|
137
|
+
def _mkchannel(self, opts):
|
|
138
|
+
if self.secure:
|
|
139
|
+
return grpc.aio.secure_channel(self.netloc, self.credentials, options=opts)
|
|
140
|
+
return grpc.aio.insecure_channel(self.netloc, options=opts)
|
etcd3/async_managers.py
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""Manager classes for etcd3 async client.
|
|
2
|
+
|
|
3
|
+
This module provides manager classes to handle specific functionality
|
|
4
|
+
for the etcd3 async client, reducing the size of the main client class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
|
|
9
|
+
import grpc
|
|
10
|
+
import etcdrpc
|
|
11
|
+
|
|
12
|
+
from etcd3 import utils
|
|
13
|
+
from etcd3.members import Member
|
|
14
|
+
from etcd3.client import Status, Alarm
|
|
15
|
+
from etcd3.async_watch import AsyncWatcher
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AsyncWatchManager:
|
|
19
|
+
"""Manager for watch-related operations.
|
|
20
|
+
|
|
21
|
+
:param etcd_client: The etcd client instance
|
|
22
|
+
:type etcd_client: AsyncMultiEndpointEtcd3Client
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, etcd_client):
|
|
26
|
+
self.etcd_client = etcd_client
|
|
27
|
+
self._watcher = None
|
|
28
|
+
|
|
29
|
+
def _get_watcher(self):
|
|
30
|
+
"""Get or create a watcher instance."""
|
|
31
|
+
if self._watcher is None:
|
|
32
|
+
watchstub = etcdrpc.WatchStub(self.etcd_client.channel)
|
|
33
|
+
self._watcher = AsyncWatcher(
|
|
34
|
+
watchstub,
|
|
35
|
+
timeout=self.etcd_client.timeout,
|
|
36
|
+
call_credentials=self.etcd_client.call_credentials,
|
|
37
|
+
metadata=self.etcd_client.metadata,
|
|
38
|
+
)
|
|
39
|
+
return self._watcher
|
|
40
|
+
|
|
41
|
+
async def add_watch_callback(self, *args, **kwargs):
|
|
42
|
+
"""
|
|
43
|
+
Watch a key or range of keys and call a callback on every response.
|
|
44
|
+
|
|
45
|
+
If timeout was declared during the client initialization and
|
|
46
|
+
the watch cannot be created during that time the method raises
|
|
47
|
+
a ``WatchTimedOut`` exception.
|
|
48
|
+
|
|
49
|
+
:param key: key to watch
|
|
50
|
+
:param callback: callback function
|
|
51
|
+
|
|
52
|
+
:returns: watch_id. Later it could be used for cancelling watch.
|
|
53
|
+
"""
|
|
54
|
+
watcher = self._get_watcher()
|
|
55
|
+
return await watcher.add_callback(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
async def add_watch_prefix_callback(self, key_prefix, callback, **kwargs):
|
|
58
|
+
"""
|
|
59
|
+
Watch a prefix and call a callback on every response.
|
|
60
|
+
|
|
61
|
+
If timeout was declared during the client initialization and
|
|
62
|
+
the watch cannot be created during that time the method raises
|
|
63
|
+
a ``WatchTimedOut`` exception.
|
|
64
|
+
|
|
65
|
+
:param key_prefix: prefix to watch
|
|
66
|
+
:param callback: callback function
|
|
67
|
+
|
|
68
|
+
:returns: watch_id. Later it could be used for cancelling watch.
|
|
69
|
+
"""
|
|
70
|
+
kwargs["range_end"] = utils.prefix_range_end(utils.to_bytes(key_prefix))
|
|
71
|
+
return await self.add_watch_callback(key_prefix, callback, **kwargs)
|
|
72
|
+
|
|
73
|
+
async def watch_response(self, key, **kwargs):
|
|
74
|
+
"""
|
|
75
|
+
Watch a key.
|
|
76
|
+
|
|
77
|
+
Example usage:
|
|
78
|
+
|
|
79
|
+
.. code-block:: python
|
|
80
|
+
responses_iterator, cancel = await etcd.watch_response('/doot/key')
|
|
81
|
+
async for response in responses_iterator:
|
|
82
|
+
print(response)
|
|
83
|
+
|
|
84
|
+
:param key: key to watch
|
|
85
|
+
|
|
86
|
+
:returns: tuple of ``responses_iterator`` and ``cancel``.
|
|
87
|
+
Use ``responses_iterator`` to get the watch responses,
|
|
88
|
+
each of which contains a header and a list of events.
|
|
89
|
+
Use ``cancel`` to cancel the watch request.
|
|
90
|
+
"""
|
|
91
|
+
watcher = self._get_watcher()
|
|
92
|
+
event_iter = watcher.watch(key, **kwargs)
|
|
93
|
+
|
|
94
|
+
cancel_event = asyncio.Event()
|
|
95
|
+
|
|
96
|
+
async def cancel():
|
|
97
|
+
cancel_event.set()
|
|
98
|
+
await watcher.cancel(0)
|
|
99
|
+
|
|
100
|
+
async def event_iterator():
|
|
101
|
+
try:
|
|
102
|
+
async for event in event_iter:
|
|
103
|
+
if cancel_event.is_set():
|
|
104
|
+
break
|
|
105
|
+
yield event
|
|
106
|
+
except grpc.RpcError as exc:
|
|
107
|
+
if not cancel_event.is_set():
|
|
108
|
+
self.etcd_client._manage_grpc_errors(exc)
|
|
109
|
+
|
|
110
|
+
return event_iterator(), cancel
|
|
111
|
+
|
|
112
|
+
async def watch(self, key, **kwargs):
|
|
113
|
+
"""
|
|
114
|
+
Watch a key.
|
|
115
|
+
|
|
116
|
+
Example usage:
|
|
117
|
+
|
|
118
|
+
.. code-block:: python
|
|
119
|
+
events_iterator, cancel = await etcd.watch('/doot/key')
|
|
120
|
+
async for event in events_iterator:
|
|
121
|
+
print(event)
|
|
122
|
+
|
|
123
|
+
:param key: key to watch
|
|
124
|
+
|
|
125
|
+
:returns: tuple of ``events_iterator`` and ``cancel``.
|
|
126
|
+
Use ``events_iterator`` to get the events of key changes
|
|
127
|
+
and ``cancel`` to cancel the watch request.
|
|
128
|
+
"""
|
|
129
|
+
response_iterator, cancel = await self.watch_response(key, **kwargs)
|
|
130
|
+
return utils.async_response_to_event_iterator(response_iterator), cancel
|
|
131
|
+
|
|
132
|
+
async def watch_prefix_response(self, key_prefix, **kwargs):
|
|
133
|
+
"""
|
|
134
|
+
Watch a range of keys with a prefix.
|
|
135
|
+
|
|
136
|
+
:param key_prefix: prefix to watch
|
|
137
|
+
|
|
138
|
+
:returns: tuple of ``responses_iterator`` and ``cancel``.
|
|
139
|
+
"""
|
|
140
|
+
kwargs["range_end"] = utils.prefix_range_end(utils.to_bytes(key_prefix))
|
|
141
|
+
return await self.watch_response(key_prefix, **kwargs)
|
|
142
|
+
|
|
143
|
+
async def watch_prefix(self, key_prefix, **kwargs):
|
|
144
|
+
"""
|
|
145
|
+
Watch a range of keys with a prefix.
|
|
146
|
+
|
|
147
|
+
:param key_prefix: prefix to watch
|
|
148
|
+
|
|
149
|
+
:returns: tuple of ``events_iterator`` and ``cancel``.
|
|
150
|
+
"""
|
|
151
|
+
kwargs["range_end"] = utils.prefix_range_end(utils.to_bytes(key_prefix))
|
|
152
|
+
return await self.watch(key_prefix, **kwargs)
|
|
153
|
+
|
|
154
|
+
async def watch_once_response(self, key, timeout=None, **kwargs):
|
|
155
|
+
"""
|
|
156
|
+
Watch a key and stop after the first response.
|
|
157
|
+
|
|
158
|
+
If the timeout was specified and response didn't arrive method
|
|
159
|
+
will raise ``WatchTimedOut`` exception.
|
|
160
|
+
|
|
161
|
+
:param key: key to watch
|
|
162
|
+
:param timeout: (optional) timeout in seconds.
|
|
163
|
+
|
|
164
|
+
:returns: ``WatchResponse``
|
|
165
|
+
"""
|
|
166
|
+
watcher = self._get_watcher()
|
|
167
|
+
return await watcher.watch_once(key, timeout=timeout, **kwargs)
|
|
168
|
+
|
|
169
|
+
async def watch_once(self, key, timeout=None, **kwargs):
|
|
170
|
+
"""
|
|
171
|
+
Watch a key and stop after the first event.
|
|
172
|
+
|
|
173
|
+
If the timeout was specified and event didn't arrive method
|
|
174
|
+
will raise ``WatchTimedOut`` exception.
|
|
175
|
+
|
|
176
|
+
:param key: key to watch
|
|
177
|
+
:param timeout: (optional) timeout in seconds.
|
|
178
|
+
|
|
179
|
+
:returns: ``Event``
|
|
180
|
+
"""
|
|
181
|
+
response = await self.watch_once_response(key, timeout=timeout, **kwargs)
|
|
182
|
+
if not response.events:
|
|
183
|
+
raise IndexError("No events in watch response")
|
|
184
|
+
return response.events[0]
|
|
185
|
+
|
|
186
|
+
async def watch_prefix_once_response(self, key_prefix, timeout=None, **kwargs):
|
|
187
|
+
"""
|
|
188
|
+
Watch a range of keys with a prefix and stop after the first response.
|
|
189
|
+
|
|
190
|
+
If the timeout was specified and response didn't arrive method
|
|
191
|
+
will raise ``WatchTimedOut`` exception.
|
|
192
|
+
"""
|
|
193
|
+
kwargs["range_end"] = utils.prefix_range_end(utils.to_bytes(key_prefix))
|
|
194
|
+
return await self.watch_once_response(key_prefix, timeout=timeout, **kwargs)
|
|
195
|
+
|
|
196
|
+
async def watch_prefix_once(self, key_prefix, timeout=None, **kwargs):
|
|
197
|
+
"""
|
|
198
|
+
Watch a range of keys with a prefix and stop after the first event.
|
|
199
|
+
|
|
200
|
+
If the timeout was specified and event didn't arrive method
|
|
201
|
+
will raise ``WatchTimedOut`` exception.
|
|
202
|
+
"""
|
|
203
|
+
kwargs["range_end"] = utils.prefix_range_end(utils.to_bytes(key_prefix))
|
|
204
|
+
return await self.watch_once(key_prefix, timeout=timeout, **kwargs)
|
|
205
|
+
|
|
206
|
+
async def cancel_watch(self, watch_id):
|
|
207
|
+
"""
|
|
208
|
+
Stop watching a key or range of keys.
|
|
209
|
+
|
|
210
|
+
:param watch_id: watch_id returned by ``add_watch_callback`` method
|
|
211
|
+
"""
|
|
212
|
+
watcher = self._get_watcher()
|
|
213
|
+
await watcher.cancel(watch_id)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class AsyncClusterManager:
|
|
217
|
+
"""Manager for cluster-related operations.
|
|
218
|
+
|
|
219
|
+
:param etcd_client: The etcd client instance
|
|
220
|
+
:type etcd_client: AsyncMultiEndpointEtcd3Client
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def __init__(self, etcd_client):
|
|
224
|
+
self.etcd_client = etcd_client
|
|
225
|
+
|
|
226
|
+
async def add_member(self, urls):
|
|
227
|
+
"""
|
|
228
|
+
Add a member into the cluster.
|
|
229
|
+
|
|
230
|
+
:returns: new member
|
|
231
|
+
:rtype: :class:`.Member`
|
|
232
|
+
"""
|
|
233
|
+
member_add_request = etcdrpc.MemberAddRequest(peerURLs=urls)
|
|
234
|
+
|
|
235
|
+
# Create cluster stub directly
|
|
236
|
+
cluster_stub = etcdrpc.ClusterStub(self.etcd_client.channel)
|
|
237
|
+
member_add_response = await cluster_stub.MemberAdd(
|
|
238
|
+
member_add_request,
|
|
239
|
+
timeout=self.etcd_client.timeout,
|
|
240
|
+
credentials=self.etcd_client.call_credentials,
|
|
241
|
+
metadata=self.etcd_client.metadata,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
member = member_add_response.member
|
|
245
|
+
return Member(
|
|
246
|
+
member.ID,
|
|
247
|
+
member.name,
|
|
248
|
+
member.peerURLs,
|
|
249
|
+
member.clientURLs,
|
|
250
|
+
etcd_client=self.etcd_client,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
async def remove_member(self, member_id):
|
|
254
|
+
"""
|
|
255
|
+
Remove an existing member from the cluster.
|
|
256
|
+
|
|
257
|
+
:param member_id: ID of the member to remove
|
|
258
|
+
"""
|
|
259
|
+
member_rm_request = etcdrpc.MemberRemoveRequest(ID=member_id)
|
|
260
|
+
# Create cluster stub directly
|
|
261
|
+
cluster_stub = etcdrpc.ClusterStub(self.etcd_client.channel)
|
|
262
|
+
await cluster_stub.MemberRemove(
|
|
263
|
+
member_rm_request,
|
|
264
|
+
timeout=self.etcd_client.timeout,
|
|
265
|
+
credentials=self.etcd_client.call_credentials,
|
|
266
|
+
metadata=self.etcd_client.metadata,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
async def update_member(self, member_id, peer_urls):
|
|
270
|
+
"""
|
|
271
|
+
Update the configuration of an existing member in the cluster.
|
|
272
|
+
|
|
273
|
+
:param member_id: ID of the member to update
|
|
274
|
+
:param peer_urls: new list of peer urls the member will use to
|
|
275
|
+
communicate with the cluster
|
|
276
|
+
"""
|
|
277
|
+
member_update_request = etcdrpc.MemberUpdateRequest(
|
|
278
|
+
ID=member_id, peerURLs=peer_urls
|
|
279
|
+
)
|
|
280
|
+
# Create cluster stub directly
|
|
281
|
+
cluster_stub = etcdrpc.ClusterStub(self.etcd_client.channel)
|
|
282
|
+
await cluster_stub.MemberUpdate(
|
|
283
|
+
member_update_request,
|
|
284
|
+
timeout=self.etcd_client.timeout,
|
|
285
|
+
credentials=self.etcd_client.call_credentials,
|
|
286
|
+
metadata=self.etcd_client.metadata,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
async def members(self):
|
|
290
|
+
"""
|
|
291
|
+
List of all members associated with the cluster.
|
|
292
|
+
|
|
293
|
+
:type: sequence of :class:`.Member`
|
|
294
|
+
|
|
295
|
+
"""
|
|
296
|
+
member_list_request = etcdrpc.MemberListRequest()
|
|
297
|
+
# Create cluster stub directly
|
|
298
|
+
cluster_stub = etcdrpc.ClusterStub(self.etcd_client.channel)
|
|
299
|
+
member_list_response = await cluster_stub.MemberList(
|
|
300
|
+
member_list_request,
|
|
301
|
+
timeout=self.etcd_client.timeout,
|
|
302
|
+
credentials=self.etcd_client.call_credentials,
|
|
303
|
+
metadata=self.etcd_client.metadata,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
for member in member_list_response.members:
|
|
307
|
+
yield Member(
|
|
308
|
+
member.ID,
|
|
309
|
+
member.name,
|
|
310
|
+
member.peerURLs,
|
|
311
|
+
member.clientURLs,
|
|
312
|
+
etcd_client=self.etcd_client,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class AsyncMaintenanceManager:
|
|
317
|
+
"""Manager for maintenance-related operations.
|
|
318
|
+
|
|
319
|
+
:param etcd_client: The etcd client instance
|
|
320
|
+
:type etcd_client: AsyncMultiEndpointEtcd3Client
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
def __init__(self, etcd_client):
|
|
324
|
+
self.etcd_client = etcd_client
|
|
325
|
+
|
|
326
|
+
async def status(self):
|
|
327
|
+
"""Get the status of the responding member."""
|
|
328
|
+
status_request = etcdrpc.StatusRequest()
|
|
329
|
+
# Create maintenance stub directly
|
|
330
|
+
maintenance_stub = etcdrpc.MaintenanceStub(self.etcd_client.channel)
|
|
331
|
+
status_response = await maintenance_stub.Status(
|
|
332
|
+
status_request,
|
|
333
|
+
timeout=self.etcd_client.timeout,
|
|
334
|
+
credentials=self.etcd_client.call_credentials,
|
|
335
|
+
metadata=self.etcd_client.metadata,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
members_list = []
|
|
339
|
+
async for member in self.etcd_client.cluster_manager.members():
|
|
340
|
+
members_list.append(member)
|
|
341
|
+
|
|
342
|
+
for m in members_list:
|
|
343
|
+
if m.id == status_response.leader:
|
|
344
|
+
leader = m
|
|
345
|
+
break
|
|
346
|
+
else:
|
|
347
|
+
leader = None
|
|
348
|
+
|
|
349
|
+
return Status(
|
|
350
|
+
status_response.version,
|
|
351
|
+
status_response.dbSize,
|
|
352
|
+
leader,
|
|
353
|
+
status_response.raftIndex,
|
|
354
|
+
status_response.raftTerm,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
async def defragment(self):
|
|
358
|
+
"""Defragment a member's backend database to recover storage space."""
|
|
359
|
+
defrag_request = etcdrpc.DefragmentRequest()
|
|
360
|
+
# Create maintenance stub directly
|
|
361
|
+
maintenance_stub = etcdrpc.MaintenanceStub(self.etcd_client.channel)
|
|
362
|
+
await maintenance_stub.Defragment(
|
|
363
|
+
defrag_request,
|
|
364
|
+
timeout=self.etcd_client.timeout,
|
|
365
|
+
credentials=self.etcd_client.call_credentials,
|
|
366
|
+
metadata=self.etcd_client.metadata,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
async def hash(self):
|
|
370
|
+
"""
|
|
371
|
+
Return the hash of the local KV state.
|
|
372
|
+
|
|
373
|
+
:returns: kv state hash
|
|
374
|
+
:rtype: int
|
|
375
|
+
"""
|
|
376
|
+
hash_request = etcdrpc.HashRequest()
|
|
377
|
+
# Create maintenance stub directly
|
|
378
|
+
maintenance_stub = etcdrpc.MaintenanceStub(self.etcd_client.channel)
|
|
379
|
+
hash_response = await maintenance_stub.Hash(
|
|
380
|
+
hash_request,
|
|
381
|
+
timeout=self.etcd_client.timeout,
|
|
382
|
+
credentials=self.etcd_client.call_credentials,
|
|
383
|
+
metadata=self.etcd_client.metadata,
|
|
384
|
+
)
|
|
385
|
+
return hash_response.hash
|
|
386
|
+
|
|
387
|
+
async def create_alarm(self, member_id=0):
|
|
388
|
+
"""Create an alarm.
|
|
389
|
+
|
|
390
|
+
If no member id is given, the alarm is activated for all the
|
|
391
|
+
members of the cluster. Only the `no space` alarm can be raised.
|
|
392
|
+
|
|
393
|
+
:param member_id: The cluster member id to create an alarm to.
|
|
394
|
+
If 0, the alarm is created for all the members
|
|
395
|
+
of the cluster.
|
|
396
|
+
:returns: list of :class:`.Alarm`
|
|
397
|
+
"""
|
|
398
|
+
alarm_request = self._build_alarm_request("activate", member_id, "no space")
|
|
399
|
+
# Create maintenance stub directly
|
|
400
|
+
maintenance_stub = etcdrpc.MaintenanceStub(self.etcd_client.channel)
|
|
401
|
+
alarm_response = await maintenance_stub.Alarm(
|
|
402
|
+
alarm_request,
|
|
403
|
+
timeout=self.etcd_client.timeout,
|
|
404
|
+
credentials=self.etcd_client.call_credentials,
|
|
405
|
+
metadata=self.etcd_client.metadata,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
return [Alarm(alarm.alarm, alarm.memberID) for alarm in alarm_response.alarms]
|
|
409
|
+
|
|
410
|
+
async def list_alarms(self, member_id=0, alarm_type="none"):
|
|
411
|
+
"""List the activated alarms.
|
|
412
|
+
|
|
413
|
+
:param member_id:
|
|
414
|
+
:param alarm_type: The cluster member id to create an alarm to.
|
|
415
|
+
If 0, the alarm is created for all the members
|
|
416
|
+
of the cluster.
|
|
417
|
+
:returns: sequence of :class:`.Alarm`
|
|
418
|
+
"""
|
|
419
|
+
alarm_request = self._build_alarm_request("get", member_id, alarm_type)
|
|
420
|
+
# Create maintenance stub directly
|
|
421
|
+
maintenance_stub = etcdrpc.MaintenanceStub(self.etcd_client.channel)
|
|
422
|
+
alarm_response = await maintenance_stub.Alarm(
|
|
423
|
+
alarm_request,
|
|
424
|
+
timeout=self.etcd_client.timeout,
|
|
425
|
+
credentials=self.etcd_client.call_credentials,
|
|
426
|
+
metadata=self.etcd_client.metadata,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
for alarm in alarm_response.alarms:
|
|
430
|
+
yield Alarm(alarm.alarm, alarm.memberID)
|
|
431
|
+
|
|
432
|
+
def _build_alarm_request(self, alarm_action, member_id, alarm_type):
|
|
433
|
+
"""Build an alarm request.
|
|
434
|
+
|
|
435
|
+
:param alarm_action: The alarm action ('get', 'activate', 'deactivate')
|
|
436
|
+
:param member_id: The member ID
|
|
437
|
+
:param alarm_type: The alarm type
|
|
438
|
+
:return: AlarmRequest instance
|
|
439
|
+
"""
|
|
440
|
+
alarm_request = etcdrpc.AlarmRequest()
|
|
441
|
+
|
|
442
|
+
if alarm_action == "get":
|
|
443
|
+
alarm_request.action = etcdrpc.AlarmRequest.GET
|
|
444
|
+
elif alarm_action == "activate":
|
|
445
|
+
alarm_request.action = etcdrpc.AlarmRequest.ACTIVATE
|
|
446
|
+
elif alarm_action == "deactivate":
|
|
447
|
+
alarm_request.action = etcdrpc.AlarmRequest.DEACTIVATE
|
|
448
|
+
else:
|
|
449
|
+
raise ValueError(f"Unknown alarm action: {alarm_action}")
|
|
450
|
+
|
|
451
|
+
alarm_request.memberID = member_id
|
|
452
|
+
|
|
453
|
+
if alarm_type == "none":
|
|
454
|
+
alarm_request.alarm = etcdrpc.NONE
|
|
455
|
+
elif alarm_type == "no space":
|
|
456
|
+
alarm_request.alarm = etcdrpc.NOSPACE
|
|
457
|
+
else:
|
|
458
|
+
raise ValueError(f"Unknown alarm type: {alarm_type}")
|
|
459
|
+
|
|
460
|
+
return alarm_request
|
|
461
|
+
|
|
462
|
+
async def disarm_alarm(self, member_id=0):
|
|
463
|
+
"""Cancel an alarm.
|
|
464
|
+
|
|
465
|
+
:param member_id: The cluster member id to cancel an alarm.
|
|
466
|
+
If 0, the alarm is canceled for all the members
|
|
467
|
+
of the cluster.
|
|
468
|
+
:returns: List of :class:`.Alarm`
|
|
469
|
+
"""
|
|
470
|
+
alarm_request = self._build_alarm_request("deactivate", member_id, "no space")
|
|
471
|
+
# Create maintenance stub directly
|
|
472
|
+
maintenance_stub = etcdrpc.MaintenanceStub(self.etcd_client.channel)
|
|
473
|
+
alarm_response = await maintenance_stub.Alarm(
|
|
474
|
+
alarm_request,
|
|
475
|
+
timeout=self.etcd_client.timeout,
|
|
476
|
+
credentials=self.etcd_client.call_credentials,
|
|
477
|
+
metadata=self.etcd_client.metadata,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
return [Alarm(alarm.alarm, alarm.memberID) for alarm in alarm_response.alarms]
|
|
481
|
+
|
|
482
|
+
async def snapshot(self, file_obj):
|
|
483
|
+
"""Take a snapshot of the database.
|
|
484
|
+
|
|
485
|
+
:param file_obj: A file-like object to write the database contents in.
|
|
486
|
+
"""
|
|
487
|
+
snapshot_request = etcdrpc.SnapshotRequest()
|
|
488
|
+
# Create maintenance stub directly
|
|
489
|
+
maintenance_stub = etcdrpc.MaintenanceStub(self.etcd_client.channel)
|
|
490
|
+
snapshot_response = maintenance_stub.Snapshot(
|
|
491
|
+
snapshot_request,
|
|
492
|
+
timeout=self.etcd_client.timeout,
|
|
493
|
+
credentials=self.etcd_client.call_credentials,
|
|
494
|
+
metadata=self.etcd_client.metadata,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
async for response in snapshot_response:
|
|
498
|
+
await file_obj.write(response.blob)
|
etcd3/async_monitor.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Monitoring utilities for etcd3 async client.
|
|
2
|
+
|
|
3
|
+
.. deprecated::
|
|
4
|
+
Use etcd3.exporters.prometheus instead for metrics collection.
|
|
5
|
+
|
|
6
|
+
This module provides monitoring functionality for the etcd3 async client,
|
|
7
|
+
including metrics collection and logging utilities.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Re-export from prometheus exporter
|
|
11
|
+
from etcd3.exporters.prometheus import (
|
|
12
|
+
get_metrics_interface as get_monitor,
|
|
13
|
+
monitor_request,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = ["get_monitor", "monitor_request"]
|