hat-event 0.9.27__cp310.cp311.cp312.cp313-abi3-win_amd64.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.
- hat/event/__init__.py +1 -0
- hat/event/adminer/__init__.py +18 -0
- hat/event/adminer/client.py +124 -0
- hat/event/adminer/common.py +27 -0
- hat/event/adminer/server.py +111 -0
- hat/event/backends/__init__.py +0 -0
- hat/event/backends/dummy.py +49 -0
- hat/event/backends/lmdb/__init__.py +9 -0
- hat/event/backends/lmdb/backend.py +319 -0
- hat/event/backends/lmdb/common.py +277 -0
- hat/event/backends/lmdb/conditions.py +102 -0
- hat/event/backends/lmdb/convert/__init__.py +0 -0
- hat/event/backends/lmdb/convert/__main__.py +8 -0
- hat/event/backends/lmdb/convert/convert_v06_to_v07.py +213 -0
- hat/event/backends/lmdb/convert/convert_v07_to_v09.py +175 -0
- hat/event/backends/lmdb/convert/main.py +88 -0
- hat/event/backends/lmdb/convert/v06.py +216 -0
- hat/event/backends/lmdb/convert/v07.py +508 -0
- hat/event/backends/lmdb/convert/v09.py +50 -0
- hat/event/backends/lmdb/convert/version.py +63 -0
- hat/event/backends/lmdb/environment.py +100 -0
- hat/event/backends/lmdb/latestdb.py +116 -0
- hat/event/backends/lmdb/manager/__init__.py +0 -0
- hat/event/backends/lmdb/manager/__main__.py +8 -0
- hat/event/backends/lmdb/manager/common.py +45 -0
- hat/event/backends/lmdb/manager/copy.py +92 -0
- hat/event/backends/lmdb/manager/main.py +34 -0
- hat/event/backends/lmdb/manager/query.py +215 -0
- hat/event/backends/lmdb/refdb.py +234 -0
- hat/event/backends/lmdb/systemdb.py +102 -0
- hat/event/backends/lmdb/timeseriesdb.py +486 -0
- hat/event/backends/memory.py +178 -0
- hat/event/common/__init__.py +144 -0
- hat/event/common/backend.py +91 -0
- hat/event/common/collection/__init__.py +8 -0
- hat/event/common/collection/common.py +28 -0
- hat/event/common/collection/list.py +19 -0
- hat/event/common/collection/tree.py +62 -0
- hat/event/common/common.py +176 -0
- hat/event/common/encoder.py +305 -0
- hat/event/common/json_schema_repo.json +1 -0
- hat/event/common/matches.py +44 -0
- hat/event/common/module.py +142 -0
- hat/event/common/sbs_repo.json +1 -0
- hat/event/common/subscription/__init__.py +22 -0
- hat/event/common/subscription/_csubscription.abi3.pyd +0 -0
- hat/event/common/subscription/common.py +145 -0
- hat/event/common/subscription/csubscription.py +47 -0
- hat/event/common/subscription/pysubscription.py +97 -0
- hat/event/component.py +284 -0
- hat/event/eventer/__init__.py +28 -0
- hat/event/eventer/client.py +260 -0
- hat/event/eventer/common.py +27 -0
- hat/event/eventer/server.py +286 -0
- hat/event/manager/__init__.py +0 -0
- hat/event/manager/__main__.py +8 -0
- hat/event/manager/common.py +48 -0
- hat/event/manager/main.py +387 -0
- hat/event/server/__init__.py +0 -0
- hat/event/server/__main__.py +8 -0
- hat/event/server/adminer_server.py +43 -0
- hat/event/server/engine.py +216 -0
- hat/event/server/engine_runner.py +127 -0
- hat/event/server/eventer_client.py +205 -0
- hat/event/server/eventer_client_runner.py +152 -0
- hat/event/server/eventer_server.py +119 -0
- hat/event/server/main.py +84 -0
- hat/event/server/main_runner.py +212 -0
- hat_event-0.9.27.dist-info/LICENSE +202 -0
- hat_event-0.9.27.dist-info/METADATA +108 -0
- hat_event-0.9.27.dist-info/RECORD +73 -0
- hat_event-0.9.27.dist-info/WHEEL +7 -0
- hat_event-0.9.27.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
import abc
|
|
3
|
+
import itertools
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from hat.event.common.common import EventTypeSegment, EventType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Subscription(abc.ABC):
|
|
10
|
+
"""Subscription defined by query event types"""
|
|
11
|
+
|
|
12
|
+
@abc.abstractmethod
|
|
13
|
+
def __init__(self, query_types: Iterable[EventType]):
|
|
14
|
+
"""Create subscription instance"""
|
|
15
|
+
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
def get_query_types(self) -> Iterable[EventType]:
|
|
18
|
+
"""Calculate sanitized query event types"""
|
|
19
|
+
|
|
20
|
+
@abc.abstractmethod
|
|
21
|
+
def matches(self, event_type: EventType) -> bool:
|
|
22
|
+
"""Does `event_type` match subscription"""
|
|
23
|
+
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
def union(self, *others: 'Subscription') -> 'Subscription':
|
|
26
|
+
"""Create new subscription including event types from this and
|
|
27
|
+
other subscriptions."""
|
|
28
|
+
|
|
29
|
+
@abc.abstractmethod
|
|
30
|
+
def intersection(self, *others: 'Subscription') -> 'Subscription':
|
|
31
|
+
"""Create new subscription containing event types in common with
|
|
32
|
+
other subscriptions."""
|
|
33
|
+
|
|
34
|
+
@abc.abstractmethod
|
|
35
|
+
def isdisjoint(self, other: 'Subscription') -> bool:
|
|
36
|
+
"""Return ``True`` if this subscription has no event types in common
|
|
37
|
+
with other subscription."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Node(typing.NamedTuple):
|
|
41
|
+
"""Subscription tree node"""
|
|
42
|
+
is_leaf: bool
|
|
43
|
+
children: typing.Dict[EventTypeSegment, 'Node']
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def node_from_query_types(query_types: Iterable[EventType]) -> Node:
|
|
47
|
+
node = Node(False, {})
|
|
48
|
+
|
|
49
|
+
for query_type in query_types:
|
|
50
|
+
node = _add_query_type(node, query_type)
|
|
51
|
+
|
|
52
|
+
return node
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def node_to_query_types(node: Node) -> Iterable[EventType]:
|
|
56
|
+
is_leaf, children = node
|
|
57
|
+
|
|
58
|
+
if is_leaf and '*' not in children:
|
|
59
|
+
yield ()
|
|
60
|
+
|
|
61
|
+
for head, child in children.items():
|
|
62
|
+
for rest in node_to_query_types(child):
|
|
63
|
+
yield (head, *rest)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def union(first: Node, second: Node) -> Node:
|
|
67
|
+
is_leaf = first.is_leaf or second.is_leaf
|
|
68
|
+
|
|
69
|
+
if '*' in first.children or '*' in second.children:
|
|
70
|
+
return Node(is_leaf, {'*': (True, {})})
|
|
71
|
+
|
|
72
|
+
children = {}
|
|
73
|
+
|
|
74
|
+
for name, child in itertools.chain(first.children.items(),
|
|
75
|
+
second.children.items()):
|
|
76
|
+
prev_child = children.get(name)
|
|
77
|
+
children[name] = union(prev_child, child) if prev_child else child
|
|
78
|
+
|
|
79
|
+
return Node(is_leaf, children)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def intersection(first: Node, second: Node) -> Node:
|
|
83
|
+
is_leaf = first.is_leaf and second.is_leaf
|
|
84
|
+
|
|
85
|
+
if '*' in first.children:
|
|
86
|
+
children = second.children
|
|
87
|
+
|
|
88
|
+
elif '*' in second.children:
|
|
89
|
+
children = first.children
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
children = {}
|
|
93
|
+
|
|
94
|
+
for name, child, other_children in itertools.chain(
|
|
95
|
+
((name, child, second.children)
|
|
96
|
+
for name, child in first.children.items()),
|
|
97
|
+
((name, child, first.children)
|
|
98
|
+
for name, child in second.children.items())):
|
|
99
|
+
|
|
100
|
+
if name == '?':
|
|
101
|
+
candidates = other_children
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
candidates = {}
|
|
105
|
+
other_child = other_children.get(name)
|
|
106
|
+
if other_child:
|
|
107
|
+
candidates[name] = other_child
|
|
108
|
+
other_child = other_children.get('?')
|
|
109
|
+
if other_child:
|
|
110
|
+
candidates['?'] = other_child
|
|
111
|
+
|
|
112
|
+
for other_name, other_child in candidates.items():
|
|
113
|
+
result_name = name if name != '?' else other_name
|
|
114
|
+
result_children = intersection(child, other_child)
|
|
115
|
+
prev_result_children = children.get(result_name)
|
|
116
|
+
children[result_name] = (
|
|
117
|
+
union(prev_result_children, result_children)
|
|
118
|
+
if prev_result_children else result_children)
|
|
119
|
+
|
|
120
|
+
return Node(is_leaf, children)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _add_query_type(node, query_type):
|
|
124
|
+
is_leaf, children = node
|
|
125
|
+
|
|
126
|
+
if '*' in children:
|
|
127
|
+
return node
|
|
128
|
+
|
|
129
|
+
if not query_type:
|
|
130
|
+
return Node(True, children)
|
|
131
|
+
|
|
132
|
+
head, rest = query_type[0], query_type[1:]
|
|
133
|
+
|
|
134
|
+
if head == '*':
|
|
135
|
+
if rest:
|
|
136
|
+
raise ValueError('invalid query event type')
|
|
137
|
+
children.clear()
|
|
138
|
+
children['*'] = Node(True, {})
|
|
139
|
+
|
|
140
|
+
else:
|
|
141
|
+
child = children.get(head) or Node(False, {})
|
|
142
|
+
child = _add_query_type(child, rest)
|
|
143
|
+
children[head] = child
|
|
144
|
+
|
|
145
|
+
return node
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from hat.event.common.subscription import common
|
|
2
|
+
|
|
3
|
+
from hat.event.common.subscription import _csubscription
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CSubscription(common.Subscription):
|
|
7
|
+
"""C implementation of Subscription"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, query_types):
|
|
10
|
+
self._subscription = _csubscription.Subscription(query_types)
|
|
11
|
+
|
|
12
|
+
def get_query_types(self):
|
|
13
|
+
return self._subscription.get_query_types()
|
|
14
|
+
|
|
15
|
+
def matches(self, event_type):
|
|
16
|
+
return self._subscription.matches(event_type)
|
|
17
|
+
|
|
18
|
+
def union(self, *others):
|
|
19
|
+
node = common.node_from_query_types(
|
|
20
|
+
self._subscription.get_query_types())
|
|
21
|
+
|
|
22
|
+
for other in others:
|
|
23
|
+
other_node = common.node_from_query_types(other.get_query_types())
|
|
24
|
+
node = common.union(node, other_node)
|
|
25
|
+
|
|
26
|
+
return CSubscription(common.node_to_query_types(node))
|
|
27
|
+
|
|
28
|
+
def intersection(self, *others):
|
|
29
|
+
node = common.node_from_query_types(
|
|
30
|
+
self._subscription.get_query_types())
|
|
31
|
+
|
|
32
|
+
for other in others:
|
|
33
|
+
other_node = common.node_from_query_types(other.get_query_types())
|
|
34
|
+
node = common.intersection(node, other_node)
|
|
35
|
+
|
|
36
|
+
return CSubscription(common.node_to_query_types(node))
|
|
37
|
+
|
|
38
|
+
def isdisjoint(self, other):
|
|
39
|
+
other_subscription = _get_other_subscription(other)
|
|
40
|
+
return self._subscription.isdisjoint(other_subscription)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_other_subscription(other):
|
|
44
|
+
if isinstance(other, CSubscription):
|
|
45
|
+
return other._subscription
|
|
46
|
+
|
|
47
|
+
return CSubscription(other.get_query_types())._subscription
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from hat.event.common.subscription import common
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PySubscription(common.Subscription):
|
|
5
|
+
"""Python implementation of Subscription"""
|
|
6
|
+
|
|
7
|
+
def __init__(self, query_types):
|
|
8
|
+
self._root = common.node_from_query_types(query_types)
|
|
9
|
+
|
|
10
|
+
def get_query_types(self):
|
|
11
|
+
return common.node_to_query_types(self._root)
|
|
12
|
+
|
|
13
|
+
def matches(self, event_type):
|
|
14
|
+
return _matches(self._root, event_type, 0)
|
|
15
|
+
|
|
16
|
+
def union(self, *others):
|
|
17
|
+
node = self._root
|
|
18
|
+
|
|
19
|
+
for other in others:
|
|
20
|
+
other_node = _get_other_node(other)
|
|
21
|
+
node = common.union(node, other_node)
|
|
22
|
+
|
|
23
|
+
result = PySubscription([])
|
|
24
|
+
result._root = node
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
def intersection(self, *others):
|
|
28
|
+
node = self._root
|
|
29
|
+
|
|
30
|
+
for other in others:
|
|
31
|
+
other_node = _get_other_node(other)
|
|
32
|
+
node = common.intersection(node, other_node)
|
|
33
|
+
|
|
34
|
+
result = PySubscription([])
|
|
35
|
+
result._root = node
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
def isdisjoint(self, other):
|
|
39
|
+
other_node = _get_other_node(other)
|
|
40
|
+
return _isdisjoint(self._root, other_node)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_other_node(other):
|
|
44
|
+
if isinstance(other, PySubscription):
|
|
45
|
+
return other._root
|
|
46
|
+
|
|
47
|
+
return common.node_from_query_types(other.get_query_types())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _matches(node, event_type, event_type_index):
|
|
51
|
+
is_leaf, children = node
|
|
52
|
+
|
|
53
|
+
if '*' in children:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
if event_type_index >= len(event_type):
|
|
57
|
+
return is_leaf
|
|
58
|
+
|
|
59
|
+
child = children.get(event_type[event_type_index])
|
|
60
|
+
if child and _matches(child, event_type, event_type_index + 1):
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
child = children.get('?')
|
|
64
|
+
if child and _matches(child, event_type, event_type_index + 1):
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _isdisjoint(first, second):
|
|
71
|
+
if first.is_leaf and second.is_leaf:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
if (('*' in first.children and (second.children or second.is_leaf)) or
|
|
75
|
+
('*' in second.children and (first.children or first.is_leaf))):
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
if '?' in first.children:
|
|
79
|
+
for child in second.children.values():
|
|
80
|
+
if not _isdisjoint(first.children['?'], child):
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
if '?' in second.children:
|
|
84
|
+
for name, child in first.children.items():
|
|
85
|
+
if name == '?':
|
|
86
|
+
continue
|
|
87
|
+
if not _isdisjoint(second.children['?'], child):
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
names = set(first.children.keys()).intersection(second.children.keys())
|
|
91
|
+
for name in names:
|
|
92
|
+
if name == '?':
|
|
93
|
+
continue
|
|
94
|
+
if not _isdisjoint(first.children[name], second.children[name]):
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
return True
|
hat/event/component.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Eventer Component"""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
import asyncio
|
|
5
|
+
import contextlib
|
|
6
|
+
import logging
|
|
7
|
+
import types
|
|
8
|
+
import typing
|
|
9
|
+
|
|
10
|
+
from hat import aio
|
|
11
|
+
from hat import json
|
|
12
|
+
from hat import util
|
|
13
|
+
from hat.drivers import tcp
|
|
14
|
+
import hat.monitor.component
|
|
15
|
+
|
|
16
|
+
from hat.event import common
|
|
17
|
+
import hat.event.eventer.client
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
mlog: logging.Logger = logging.getLogger(__name__)
|
|
21
|
+
"""Module logger"""
|
|
22
|
+
|
|
23
|
+
State: typing.TypeAlias = hat.monitor.component.State
|
|
24
|
+
"""Component state"""
|
|
25
|
+
|
|
26
|
+
Runner: typing.TypeAlias = aio.Resource
|
|
27
|
+
"""Component runner"""
|
|
28
|
+
|
|
29
|
+
RunnerCb: typing.TypeAlias = aio.AsyncCallable[
|
|
30
|
+
['Component', 'ServerData', hat.event.eventer.client.Client],
|
|
31
|
+
Runner]
|
|
32
|
+
"""Runner callback"""
|
|
33
|
+
|
|
34
|
+
StateCb: typing.TypeAlias = aio.AsyncCallable[['Component', State], None]
|
|
35
|
+
"""State callback"""
|
|
36
|
+
|
|
37
|
+
CloseReqCb: typing.TypeAlias = aio.AsyncCallable[['Component'], None]
|
|
38
|
+
"""Close request callback"""
|
|
39
|
+
|
|
40
|
+
StatusCb: typing.TypeAlias = aio.AsyncCallable[
|
|
41
|
+
['Component', hat.event.eventer.client.Client, common.Status],
|
|
42
|
+
None]
|
|
43
|
+
"""Status callback"""
|
|
44
|
+
|
|
45
|
+
EventsCb: typing.TypeAlias = aio.AsyncCallable[
|
|
46
|
+
['Component', hat.event.eventer.client.Client, Collection[common.Event]],
|
|
47
|
+
None]
|
|
48
|
+
"""Events callback"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ServerData(typing.NamedTuple):
|
|
52
|
+
"""Server data"""
|
|
53
|
+
server_id: common.ServerId
|
|
54
|
+
addr: tcp.Address
|
|
55
|
+
server_token: str | None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def connect(addr: tcp.Address,
|
|
59
|
+
name: str,
|
|
60
|
+
group: str,
|
|
61
|
+
server_group: str,
|
|
62
|
+
client_name: str,
|
|
63
|
+
runner_cb: RunnerCb,
|
|
64
|
+
*,
|
|
65
|
+
state_cb: StateCb | None = None,
|
|
66
|
+
close_req_cb: CloseReqCb | None = None,
|
|
67
|
+
status_cb: StatusCb | None = None,
|
|
68
|
+
events_cb: EventsCb | None = None,
|
|
69
|
+
server_data_queue_size: int = 1024,
|
|
70
|
+
reconnect_delay: float = 0.5,
|
|
71
|
+
observer_kwargs: dict[str, typing.Any] = {},
|
|
72
|
+
eventer_kwargs: dict[str, typing.Any] = {}
|
|
73
|
+
) -> 'Component':
|
|
74
|
+
"""Connect to local monitor server and create component
|
|
75
|
+
|
|
76
|
+
High-level interface for communication with Event Server, based on
|
|
77
|
+
information obtained from Monitor Server.
|
|
78
|
+
|
|
79
|
+
Component instance tries to establish active connection with
|
|
80
|
+
Event Server within monitor component group `server_group`. Once this
|
|
81
|
+
connection is established, `runner_cb` is called with currently active
|
|
82
|
+
eventer client instance. Result of calling `runner_cb` should be runner
|
|
83
|
+
representing user defined components activity associated with connection
|
|
84
|
+
to active Event Server. Once connection to Event Server is closed or new
|
|
85
|
+
active Event Server is detected, associated runner is closed. If new
|
|
86
|
+
connection to Event Server is successfully established,
|
|
87
|
+
`component_cb` will be called again to create new runner associated with
|
|
88
|
+
new instance of eventer client.
|
|
89
|
+
|
|
90
|
+
If runner is closed while connection to Event Server is open, component
|
|
91
|
+
is closed.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
component = Component()
|
|
95
|
+
component._server_group = server_group
|
|
96
|
+
component._client_name = client_name
|
|
97
|
+
component._runner_cb = runner_cb
|
|
98
|
+
component._state_cb = state_cb
|
|
99
|
+
component._close_req_cb = close_req_cb
|
|
100
|
+
component._status_cb = status_cb
|
|
101
|
+
component._events_cb = events_cb
|
|
102
|
+
component._server_data_queue_size = server_data_queue_size
|
|
103
|
+
component._reconnect_delay = reconnect_delay
|
|
104
|
+
component._eventer_kwargs = eventer_kwargs
|
|
105
|
+
component._server_data = None
|
|
106
|
+
component._server_data_queue = None
|
|
107
|
+
|
|
108
|
+
component._monitor_component = await hat.monitor.component.connect(
|
|
109
|
+
addr=addr,
|
|
110
|
+
name=name,
|
|
111
|
+
group=group,
|
|
112
|
+
runner_cb=component._create_monitor_runner,
|
|
113
|
+
**{**observer_kwargs,
|
|
114
|
+
'state_cb': component._on_state,
|
|
115
|
+
'close_req_cb': component._on_close_req})
|
|
116
|
+
|
|
117
|
+
return component
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class Component(aio.Resource):
|
|
121
|
+
"""Eventer Component"""
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def async_group(self):
|
|
125
|
+
return self._monitor_component.async_group
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def state(self) -> State:
|
|
129
|
+
"""State"""
|
|
130
|
+
return self._monitor_component.state
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def ready(self) -> bool:
|
|
134
|
+
"""Ready"""
|
|
135
|
+
return self._monitor_component.ready
|
|
136
|
+
|
|
137
|
+
async def set_ready(self, ready: bool):
|
|
138
|
+
"""Set ready"""
|
|
139
|
+
await self._monitor_component.set_ready(ready)
|
|
140
|
+
|
|
141
|
+
async def _on_state(self, monitor_component, state):
|
|
142
|
+
if self._server_data_queue is not None:
|
|
143
|
+
data = self._get_active_server_data(state)
|
|
144
|
+
|
|
145
|
+
if data != self._server_data:
|
|
146
|
+
self._server_data = data
|
|
147
|
+
|
|
148
|
+
with contextlib.suppress(aio.QueueClosedError):
|
|
149
|
+
await self._server_data_queue.put(data)
|
|
150
|
+
|
|
151
|
+
if self._state_cb:
|
|
152
|
+
await aio.call(self._state_cb, self, state)
|
|
153
|
+
|
|
154
|
+
async def _on_close_req(self, monitor_component):
|
|
155
|
+
if self._close_req_cb:
|
|
156
|
+
await aio.call(self._close_req_cb, self)
|
|
157
|
+
|
|
158
|
+
async def _on_status(self, eventer_client, status):
|
|
159
|
+
if self._status_cb:
|
|
160
|
+
await aio.call(self._status_cb, self, eventer_client, status)
|
|
161
|
+
|
|
162
|
+
async def _on_events(self, eventer_client, events):
|
|
163
|
+
if self._events_cb:
|
|
164
|
+
await aio.call(self._events_cb, self, eventer_client, events)
|
|
165
|
+
|
|
166
|
+
def _create_monitor_runner(self, monitor_component):
|
|
167
|
+
self._server_data = self._get_active_server_data(
|
|
168
|
+
monitor_component.state)
|
|
169
|
+
self._server_data_queue = aio.Queue(self._server_data_queue_size)
|
|
170
|
+
|
|
171
|
+
if self._server_data:
|
|
172
|
+
self._server_data_queue.put_nowait(self._server_data)
|
|
173
|
+
|
|
174
|
+
runner = aio.Group()
|
|
175
|
+
runner.spawn(self._server_data_loop, runner, self._server_data_queue)
|
|
176
|
+
|
|
177
|
+
return runner
|
|
178
|
+
|
|
179
|
+
def _get_active_server_data(self, state):
|
|
180
|
+
info = util.first(state.components, self._active_server_filter)
|
|
181
|
+
if not info:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
server_id = json.get(info.data, 'server_id')
|
|
185
|
+
host = json.get(info.data, ['eventer_server', 'host'])
|
|
186
|
+
port = json.get(info.data, ['eventer_server', 'port'])
|
|
187
|
+
server_token = json.get(info.data, 'server_token')
|
|
188
|
+
if (not isinstance(server_id, int) or
|
|
189
|
+
not isinstance(host, str) or
|
|
190
|
+
not isinstance(port, int) or
|
|
191
|
+
not isinstance(server_token, (str, types.NoneType))):
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
client_token = self._eventer_kwargs.get('client_token')
|
|
195
|
+
if client_token is not None and client_token != server_token:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
return ServerData(server_id=server_id,
|
|
199
|
+
addr=tcp.Address(host, port),
|
|
200
|
+
server_token=server_token)
|
|
201
|
+
|
|
202
|
+
def _active_server_filter(self, info):
|
|
203
|
+
return (info.group == self._server_group and
|
|
204
|
+
info.blessing_req.token is not None and
|
|
205
|
+
info.blessing_req.token == info.blessing_res.token)
|
|
206
|
+
|
|
207
|
+
async def _server_data_loop(self, async_group, server_data_queue):
|
|
208
|
+
try:
|
|
209
|
+
server_data = None
|
|
210
|
+
while True:
|
|
211
|
+
while not server_data or not server_data_queue.empty():
|
|
212
|
+
server_data = await server_data_queue.get_until_empty()
|
|
213
|
+
|
|
214
|
+
async with async_group.create_subgroup() as subgroup:
|
|
215
|
+
subgroup.spawn(self._client_loop, subgroup, server_data)
|
|
216
|
+
server_data = await server_data_queue.get_until_empty()
|
|
217
|
+
|
|
218
|
+
except Exception as e:
|
|
219
|
+
mlog.error("address loop error: %s", e, exc_info=e)
|
|
220
|
+
self.close()
|
|
221
|
+
|
|
222
|
+
finally:
|
|
223
|
+
async_group.close()
|
|
224
|
+
server_data_queue.close()
|
|
225
|
+
|
|
226
|
+
async def _client_loop(self, async_group, server_data):
|
|
227
|
+
try:
|
|
228
|
+
while True:
|
|
229
|
+
try:
|
|
230
|
+
kwargs = {**self._eventer_kwargs,
|
|
231
|
+
'status_cb': self._on_status,
|
|
232
|
+
'events_cb': self._on_events}
|
|
233
|
+
|
|
234
|
+
if 'server_id' not in kwargs:
|
|
235
|
+
kwargs['server_id'] = server_data.server_id
|
|
236
|
+
|
|
237
|
+
mlog.debug("connecting to server %s", server_data.addr)
|
|
238
|
+
client = await hat.event.eventer.client.connect(
|
|
239
|
+
addr=server_data.addr,
|
|
240
|
+
client_name=self._client_name,
|
|
241
|
+
**kwargs)
|
|
242
|
+
|
|
243
|
+
except Exception as e:
|
|
244
|
+
mlog.warning("error connecting to server: %s", e,
|
|
245
|
+
exc_info=e)
|
|
246
|
+
await asyncio.sleep(self._reconnect_delay)
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
mlog.debug("connected to server")
|
|
251
|
+
runner = await client.async_group.spawn(
|
|
252
|
+
aio.call, self._runner_cb, self, server_data, client)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
async with async_group.create_subgroup() as subgroup:
|
|
256
|
+
client_closing_task = subgroup.spawn(
|
|
257
|
+
client.wait_closing)
|
|
258
|
+
runner_closing_task = subgroup.spawn(
|
|
259
|
+
runner.wait_closing)
|
|
260
|
+
|
|
261
|
+
await asyncio.wait(
|
|
262
|
+
[client_closing_task, runner_closing_task],
|
|
263
|
+
return_when=asyncio.FIRST_COMPLETED)
|
|
264
|
+
|
|
265
|
+
if (runner_closing_task.done() and
|
|
266
|
+
not client_closing_task.done()):
|
|
267
|
+
self.close()
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
finally:
|
|
271
|
+
await aio.uncancellable(runner.async_close())
|
|
272
|
+
|
|
273
|
+
finally:
|
|
274
|
+
await aio.uncancellable(client.async_close())
|
|
275
|
+
|
|
276
|
+
mlog.debug("connection to server closed")
|
|
277
|
+
await asyncio.sleep(self._reconnect_delay)
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
mlog.error("client loop error: %s", e, exc_info=e)
|
|
281
|
+
self.close()
|
|
282
|
+
|
|
283
|
+
finally:
|
|
284
|
+
async_group.close()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Eventer communication protocol"""
|
|
2
|
+
|
|
3
|
+
from hat.event.eventer.client import (StatusCb,
|
|
4
|
+
EventsCb,
|
|
5
|
+
EventerInitError,
|
|
6
|
+
connect,
|
|
7
|
+
Client)
|
|
8
|
+
from hat.event.eventer.server import (ConnectionId,
|
|
9
|
+
ConnectionInfo,
|
|
10
|
+
ConnectionCb,
|
|
11
|
+
RegisterCb,
|
|
12
|
+
QueryCb,
|
|
13
|
+
listen,
|
|
14
|
+
Server)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ['StatusCb',
|
|
18
|
+
'EventsCb',
|
|
19
|
+
'EventerInitError',
|
|
20
|
+
'connect',
|
|
21
|
+
'Client',
|
|
22
|
+
'ConnectionId',
|
|
23
|
+
'ConnectionInfo',
|
|
24
|
+
'ConnectionCb',
|
|
25
|
+
'RegisterCb',
|
|
26
|
+
'QueryCb',
|
|
27
|
+
'listen',
|
|
28
|
+
'Server']
|