1
+ import logging
1
2
import sys
2
3
from abc import ABC
3
4
from asyncio import IncompleteReadError , StreamReader , TimeoutError
4
- from typing import Callable , List , Optional , Protocol , Union
5
+ from typing import Awaitable , Callable , List , Optional , Protocol , Union
6
+
7
+ from redis .maintenance_events import (
8
+ MaintenanceEvent ,
9
+ NodeFailedOverEvent ,
10
+ NodeFailingOverEvent ,
11
+ NodeMigratedEvent ,
12
+ NodeMigratingEvent ,
13
+ NodeMovingEvent ,
14
+ )
5
15
6
16
if sys .version_info .major >= 3 and sys .version_info .minor >= 11 :
7
17
from asyncio import timeout as async_timeout
50
60
"Client sent AUTH, but no password is set" : AuthenticationError ,
51
61
}
52
62
63
+ logger = logging .getLogger (__name__ )
64
+
53
65
54
66
class BaseParser (ABC ):
55
67
EXCEPTION_CLASSES = {
@@ -158,48 +170,195 @@ async def read_response(
158
170
raise NotImplementedError ()
159
171
160
172
161
- _INVALIDATION_MESSAGE = [b"invalidate" , "invalidate" ]
173
+ class MaintenanceNotificationsParser :
174
+ """Protocol defining maintenance push notification parsing functionality"""
175
+
176
+ @staticmethod
177
+ def parse_maintenance_start_msg (response , notification_type ):
178
+ # Expected message format is: <event_type> <seq_number> <time>
179
+ id = response [1 ]
180
+ ttl = response [2 ]
181
+ return notification_type (id , ttl )
182
+
183
+ @staticmethod
184
+ def parse_maintenance_completed_msg (response , notification_type ):
185
+ # Expected message format is: <event_type> <seq_number>
186
+ id = response [1 ]
187
+ return notification_type (id )
188
+
189
+ @staticmethod
190
+ def parse_moving_msg (response ):
191
+ # Expected message format is: MOVING <seq_number> <time> <endpoint>
192
+ id = response [1 ]
193
+ ttl = response [2 ]
194
+ if response [3 ] in [b"null" , "null" ]:
195
+ host , port = None , None
196
+ else :
197
+ value = response [3 ]
198
+ if isinstance (value , bytes ):
199
+ value = value .decode ()
200
+ host , port = value .split (":" )
201
+ port = int (port ) if port is not None else None
202
+
203
+ return NodeMovingEvent (id , host , port , ttl )
204
+
205
+
206
+ _INVALIDATION_MESSAGE = "invalidate"
207
+ _MOVING_MESSAGE = "MOVING"
208
+ _MIGRATING_MESSAGE = "MIGRATING"
209
+ _MIGRATED_MESSAGE = "MIGRATED"
210
+ _FAILING_OVER_MESSAGE = "FAILING_OVER"
211
+ _FAILED_OVER_MESSAGE = "FAILED_OVER"
212
+
213
+ _MAINTENANCE_MESSAGES = (
214
+ _MIGRATING_MESSAGE ,
215
+ _MIGRATED_MESSAGE ,
216
+ _FAILING_OVER_MESSAGE ,
217
+ _FAILED_OVER_MESSAGE ,
218
+ )
219
+
220
+ MSG_TYPE_TO_EVENT_PARSER_MAPPING : dict [str , tuple [type [MaintenanceEvent ], Callable ]] = {
221
+ _MIGRATING_MESSAGE : (
222
+ NodeMigratingEvent ,
223
+ MaintenanceNotificationsParser .parse_maintenance_start_msg ,
224
+ ),
225
+ _MIGRATED_MESSAGE : (
226
+ NodeMigratedEvent ,
227
+ MaintenanceNotificationsParser .parse_maintenance_completed_msg ,
228
+ ),
229
+ _FAILING_OVER_MESSAGE : (
230
+ NodeFailingOverEvent ,
231
+ MaintenanceNotificationsParser .parse_maintenance_start_msg ,
232
+ ),
233
+ _FAILED_OVER_MESSAGE : (
234
+ NodeFailedOverEvent ,
235
+ MaintenanceNotificationsParser .parse_maintenance_completed_msg ,
236
+ ),
237
+ _MOVING_MESSAGE : (
238
+ NodeMovingEvent ,
239
+ MaintenanceNotificationsParser .parse_moving_msg ,
240
+ ),
241
+ }
162
242
163
243
164
244
class PushNotificationsParser (Protocol ):
165
245
"""Protocol defining RESP3-specific parsing functionality"""
166
246
167
247
pubsub_push_handler_func : Callable
168
248
invalidation_push_handler_func : Optional [Callable ] = None
249
+ node_moving_push_handler_func : Optional [Callable ] = None
250
+ maintenance_push_handler_func : Optional [Callable ] = None
169
251
170
252
def handle_pubsub_push_response (self , response ):
171
253
"""Handle pubsub push responses"""
172
254
raise NotImplementedError ()
173
255
174
256
def handle_push_response (self , response , ** kwargs ):
175
- if response [0 ] not in _INVALIDATION_MESSAGE :
257
+ msg_type = response [0 ]
258
+ if isinstance (msg_type , bytes ):
259
+ msg_type = msg_type .decode ()
260
+
261
+ if msg_type not in (
262
+ _INVALIDATION_MESSAGE ,
263
+ * _MAINTENANCE_MESSAGES ,
264
+ _MOVING_MESSAGE ,
265
+ ):
176
266
return self .pubsub_push_handler_func (response )
177
- if self .invalidation_push_handler_func :
178
- return self .invalidation_push_handler_func (response )
267
+
268
+ try :
269
+ if (
270
+ msg_type == _INVALIDATION_MESSAGE
271
+ and self .invalidation_push_handler_func
272
+ ):
273
+ return self .invalidation_push_handler_func (response )
274
+
275
+ if msg_type == _MOVING_MESSAGE and self .node_moving_push_handler_func :
276
+ parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING [msg_type ][1 ]
277
+
278
+ notification = parser_function (response )
279
+ return self .node_moving_push_handler_func (notification )
280
+
281
+ if msg_type in _MAINTENANCE_MESSAGES and self .maintenance_push_handler_func :
282
+ parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING [msg_type ][1 ]
283
+ notification_type = MSG_TYPE_TO_EVENT_PARSER_MAPPING [msg_type ][0 ]
284
+ notification = parser_function (response , notification_type )
285
+
286
+ if notification is not None :
287
+ return self .maintenance_push_handler_func (notification )
288
+ except Exception as e :
289
+ logger .error (
290
+ "Error handling {} message ({}): {}" .format (msg_type , response , e )
291
+ )
292
+
293
+ return None
179
294
180
295
def set_pubsub_push_handler (self , pubsub_push_handler_func ):
181
296
self .pubsub_push_handler_func = pubsub_push_handler_func
182
297
183
298
def set_invalidation_push_handler (self , invalidation_push_handler_func ):
184
299
self .invalidation_push_handler_func = invalidation_push_handler_func
185
300
301
+ def set_node_moving_push_handler (self , node_moving_push_handler_func ):
302
+ self .node_moving_push_handler_func = node_moving_push_handler_func
303
+
304
+ def set_maintenance_push_handler (self , maintenance_push_handler_func ):
305
+ self .maintenance_push_handler_func = maintenance_push_handler_func
306
+
186
307
187
308
class AsyncPushNotificationsParser (Protocol ):
188
309
"""Protocol defining async RESP3-specific parsing functionality"""
189
310
190
311
pubsub_push_handler_func : Callable
191
312
invalidation_push_handler_func : Optional [Callable ] = None
313
+ node_moving_push_handler_func : Optional [Callable [..., Awaitable [None ]]] = None
314
+ maintenance_push_handler_func : Optional [Callable [..., Awaitable [None ]]] = None
192
315
193
316
async def handle_pubsub_push_response (self , response ):
194
317
"""Handle pubsub push responses asynchronously"""
195
318
raise NotImplementedError ()
196
319
197
320
async def handle_push_response (self , response , ** kwargs ):
198
321
"""Handle push responses asynchronously"""
199
- if response [0 ] not in _INVALIDATION_MESSAGE :
322
+
323
+ msg_type = response [0 ]
324
+ if isinstance (msg_type , bytes ):
325
+ msg_type = msg_type .decode ()
326
+
327
+ if msg_type not in (
328
+ _INVALIDATION_MESSAGE ,
329
+ * _MAINTENANCE_MESSAGES ,
330
+ _MOVING_MESSAGE ,
331
+ ):
200
332
return await self .pubsub_push_handler_func (response )
201
- if self .invalidation_push_handler_func :
202
- return await self .invalidation_push_handler_func (response )
333
+
334
+ try :
335
+ if (
336
+ msg_type == _INVALIDATION_MESSAGE
337
+ and self .invalidation_push_handler_func
338
+ ):
339
+ return await self .invalidation_push_handler_func (response )
340
+
341
+ if isinstance (msg_type , bytes ):
342
+ msg_type = msg_type .decode ()
343
+
344
+ if msg_type == _MOVING_MESSAGE and self .node_moving_push_handler_func :
345
+ parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING [msg_type ][1 ]
346
+ notification = parser_function (response )
347
+ return await self .node_moving_push_handler_func (notification )
348
+
349
+ if msg_type in _MAINTENANCE_MESSAGES and self .maintenance_push_handler_func :
350
+ parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING [msg_type ][1 ]
351
+ notification_type = MSG_TYPE_TO_EVENT_PARSER_MAPPING [msg_type ][0 ]
352
+ notification = parser_function (response , notification_type )
353
+
354
+ if notification is not None :
355
+ return await self .maintenance_push_handler_func (notification )
356
+ except Exception as e :
357
+ logger .error (
358
+ "Error handling {} message ({}): {}" .format (msg_type , response , e )
359
+ )
360
+
361
+ return None
203
362
204
363
def set_pubsub_push_handler (self , pubsub_push_handler_func ):
205
364
"""Set the pubsub push handler function"""
@@ -209,6 +368,12 @@ def set_invalidation_push_handler(self, invalidation_push_handler_func):
209
368
"""Set the invalidation push handler function"""
210
369
self .invalidation_push_handler_func = invalidation_push_handler_func
211
370
371
+ def set_node_moving_push_handler (self , node_moving_push_handler_func ):
372
+ self .node_moving_push_handler_func = node_moving_push_handler_func
373
+
374
+ def set_maintenance_push_handler (self , maintenance_push_handler_func ):
375
+ self .maintenance_push_handler_func = maintenance_push_handler_func
376
+
212
377
213
378
class _AsyncRESPBase (AsyncBaseParser ):
214
379
"""Base class for async resp parsing"""
0 commit comments