naeural-client 3.1.5__py3-none-any.whl → 3.2.2__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.
- naeural_client/_ver.py +1 -1
- naeural_client/base/generic_session.py +402 -89
- naeural_client/base/instance.py +38 -0
- naeural_client/base/pipeline.py +82 -6
- naeural_client/base_decentra_object.py +4 -2
- naeural_client/bc/__init__.py +1 -1
- naeural_client/bc/base.py +9 -9
- naeural_client/bc/evm.py +10 -1
- naeural_client/cli/cli.py +1 -0
- naeural_client/cli/cli_commands.py +19 -2
- naeural_client/cli/nodes.py +100 -8
- naeural_client/cli/oracles.py +6 -0
- naeural_client/comm/mqtt_wrapper.py +4 -2
- naeural_client/const/base.py +2 -0
- naeural_client/const/environment.py +3 -0
- naeural_client/const/payload.py +7 -1
- naeural_client/utils/config.py +64 -75
- naeural_client/utils/oracle_sync/oracle_tester.py +1 -1
- {naeural_client-3.1.5.dist-info → naeural_client-3.2.2.dist-info}/METADATA +4 -4
- {naeural_client-3.1.5.dist-info → naeural_client-3.2.2.dist-info}/RECORD +23 -23
- {naeural_client-3.1.5.dist-info → naeural_client-3.2.2.dist-info}/WHEEL +0 -0
- {naeural_client-3.1.5.dist-info → naeural_client-3.2.2.dist-info}/entry_points.txt +0 -0
- {naeural_client-3.1.5.dist-info → naeural_client-3.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -19,7 +19,7 @@ from time import sleep
|
|
19
19
|
from time import time as tm
|
20
20
|
|
21
21
|
from ..base_decentra_object import BaseDecentrAIObject
|
22
|
-
from ..bc import DefaultBlockEngine, _DotDict
|
22
|
+
from ..bc import DefaultBlockEngine, _DotDict, EE_VPN_IMPL
|
23
23
|
from ..const import (
|
24
24
|
COMMANDS, ENVIRONMENT, HB, PAYLOAD_DATA, STATUS_TYPE,
|
25
25
|
PLUGIN_SIGNATURES, DEFAULT_PIPELINES,
|
@@ -34,7 +34,9 @@ from .pipeline import Pipeline
|
|
34
34
|
from .webapp_pipeline import WebappPipeline
|
35
35
|
from .transaction import Transaction
|
36
36
|
from ..utils.config import (
|
37
|
-
load_user_defined_config, get_user_config_file, get_user_folder,
|
37
|
+
load_user_defined_config, get_user_config_file, get_user_folder,
|
38
|
+
seconds_to_short_format, log_with_color, set_client_alias,
|
39
|
+
EE_SDK_ALIAS_ENV_KEY, EE_SDK_ALIAS_DEFAULT
|
38
40
|
)
|
39
41
|
|
40
42
|
# from ..default.instance import PLUGIN_TYPES # circular import
|
@@ -45,6 +47,8 @@ DEBUG_MQTT_SERVER = "r9092118.ala.eu-central-1.emqxsl.com"
|
|
45
47
|
SDK_NETCONFIG_REQUEST_DELAY = 300
|
46
48
|
|
47
49
|
|
50
|
+
|
51
|
+
|
48
52
|
class GenericSession(BaseDecentrAIObject):
|
49
53
|
"""
|
50
54
|
A Session is a connection to a communication server which provides the channel to interact with nodes from the Naeural Edge Protocol network.
|
@@ -80,7 +84,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
80
84
|
user=None,
|
81
85
|
pwd=None,
|
82
86
|
secured=None,
|
83
|
-
name=
|
87
|
+
name=None,
|
84
88
|
encrypt_comms=True,
|
85
89
|
config={},
|
86
90
|
filter_workers=None,
|
@@ -89,9 +93,9 @@ class GenericSession(BaseDecentrAIObject):
|
|
89
93
|
on_notification=None,
|
90
94
|
on_heartbeat=None,
|
91
95
|
debug_silent=True,
|
92
|
-
debug=1,
|
93
|
-
silent=False,
|
96
|
+
debug=1, # TODO: debug or verbosity - fix this
|
94
97
|
verbosity=1,
|
98
|
+
silent=False,
|
95
99
|
dotenv_path=None,
|
96
100
|
show_commands=False,
|
97
101
|
blockchain_config=BLOCKCHAIN_CONFIG,
|
@@ -114,32 +118,42 @@ class GenericSession(BaseDecentrAIObject):
|
|
114
118
|
----------
|
115
119
|
host : str, optional
|
116
120
|
The hostname of the server. If None, it will be retrieved from the environment variable AIXP_HOSTNAME
|
121
|
+
|
117
122
|
port : int, optional
|
118
123
|
The port. If None, it will be retrieved from the environment variable AIXP_PORT
|
124
|
+
|
119
125
|
user : str, optional
|
120
126
|
The user name. If None, it will be retrieved from the environment variable AIXP_USERNAME
|
127
|
+
|
121
128
|
pwd : str, optional
|
122
129
|
The password. If None, it will be retrieved from the environment variable AIXP_PASSWORD
|
130
|
+
|
123
131
|
secured: bool, optional
|
124
132
|
True if connection is secured, by default None
|
133
|
+
|
125
134
|
name : str, optional
|
126
135
|
The name of this connection, used to identify owned pipelines on a specific Naeural Edge Protocol edge node.
|
127
136
|
The name will be used as `INITIATOR_ID` and `SESSION_ID` when communicating with Naeural Edge Protocol edge nodes, by default 'pySDK'
|
137
|
+
|
128
138
|
config : dict, optional
|
129
139
|
Configures the names of the channels this session will connect to.
|
130
140
|
If using a Mqtt server, these channels are in fact topics.
|
131
141
|
Modify this if you are absolutely certain of what you are doing.
|
132
142
|
By default {}
|
143
|
+
|
133
144
|
filter_workers: list, optional
|
134
145
|
If set, process the messages that come only from the nodes from this list.
|
135
146
|
Defaults to None
|
147
|
+
|
136
148
|
show_commands : bool
|
137
149
|
If True, will print the commands that are being sent to the Naeural Edge Protocol edge nodes.
|
138
150
|
Defaults to False
|
151
|
+
|
139
152
|
log : Logger, optional
|
140
153
|
A logger object which implements basic logging functionality and some other utils stuff. Can be ignored for now.
|
141
154
|
In the future, the documentation for the Logger base class will be available and developers will be able to use
|
142
155
|
custom-made Loggers.
|
156
|
+
|
143
157
|
on_payload : Callable[[Session, str, str, str, str, dict], None], optional
|
144
158
|
Callback that handles all payloads received from this network.
|
145
159
|
As arguments, it has a reference to this Session object, the node name, the pipeline, signature and instance, and the payload.
|
@@ -191,35 +205,45 @@ class GenericSession(BaseDecentrAIObject):
|
|
191
205
|
If True, the SDK will use the home folder as the base folder for the local cache.
|
192
206
|
NOTE: if you need to use development style ./_local_cache, set this to False.
|
193
207
|
"""
|
208
|
+
|
209
|
+
# TODO: clarify verbosity vs debug
|
210
|
+
|
194
211
|
debug = debug or not debug_silent
|
195
212
|
if isinstance(debug, bool):
|
196
213
|
debug = 2 if debug else 0
|
214
|
+
|
215
|
+
if verbosity > 1 and debug <=1:
|
216
|
+
debug = 2
|
197
217
|
|
198
218
|
self.__debug = int(debug) > 0
|
219
|
+
self._verbosity = verbosity
|
220
|
+
|
221
|
+
if self.__debug:
|
222
|
+
if not silent:
|
223
|
+
log_with_color(f"Debug mode enabled: {debug=}, {verbosity=}", color='y')
|
224
|
+
|
225
|
+
### END verbosity fix needed
|
199
226
|
|
200
227
|
self.__at_least_one_node_peered = False
|
201
228
|
self.__at_least_a_netmon_received = False
|
202
229
|
|
203
230
|
# TODO: maybe read config from file?
|
204
231
|
self._config = {**self.default_config, **config}
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
if isinstance(self._config[key]["TOPIC"], str) and self._config[key]["TOPIC"].startswith("{}"):
|
210
|
-
nr_empty = self._config[key]["TOPIC"].count("{}")
|
211
|
-
self._config[key]["TOPIC"] = self._config[key]["TOPIC"].format(root_topic, *(["{}"] * (nr_empty - 1)))
|
212
|
-
# end if root_topic
|
232
|
+
|
233
|
+
|
234
|
+
|
235
|
+
self.comms_root_topic = root_topic
|
213
236
|
|
214
237
|
self.__auto_configuration = auto_configuration
|
215
238
|
|
216
239
|
self.log = log
|
240
|
+
|
241
|
+
|
217
242
|
self.name = name
|
218
243
|
self.silent = silent
|
219
|
-
|
220
|
-
self.__eth_enabled = eth_enabled
|
221
244
|
|
222
|
-
self.
|
245
|
+
self._eth_enabled = eth_enabled
|
246
|
+
|
223
247
|
self.encrypt_comms = encrypt_comms
|
224
248
|
|
225
249
|
self._dct_online_nodes_pipelines: dict[str, Pipeline] = {}
|
@@ -283,6 +307,9 @@ class GenericSession(BaseDecentrAIObject):
|
|
283
307
|
# use_home_folder allows us to use the home folder as the base folder
|
284
308
|
local_cache_base_folder = str(get_user_folder())
|
285
309
|
# end if
|
310
|
+
|
311
|
+
## 1st config step before anything else - we prepare config via ~/.ratio1/config or .env
|
312
|
+
self.__load_user_config(dotenv_path=self.__dotenv_path)
|
286
313
|
|
287
314
|
|
288
315
|
super(GenericSession, self).__init__(
|
@@ -295,16 +322,15 @@ class GenericSession(BaseDecentrAIObject):
|
|
295
322
|
)
|
296
323
|
return
|
297
324
|
|
298
|
-
def Pd(self, *args, **kwargs):
|
299
|
-
if self.__debug:
|
325
|
+
def Pd(self, *args, verbosity=1, **kwargs):
|
326
|
+
if self.__debug and verbosity <= self._verbosity:
|
300
327
|
kwargs["color"] = 'd' if kwargs.get("color") != 'r' else 'r'
|
301
|
-
|
328
|
+
kwargs['forced_debug'] = True
|
329
|
+
self.D(*args, **kwargs)
|
302
330
|
return
|
303
331
|
|
304
332
|
|
305
|
-
def startup(self):
|
306
|
-
## 1st config step - we prepare config via ~/.naeural/config or .env
|
307
|
-
self.__load_user_config(dotenv_path=self.__dotenv_path)
|
333
|
+
def startup(self):
|
308
334
|
|
309
335
|
# TODO: needs refactoring - suboptimal design
|
310
336
|
# start the blockchain engine assuming config is already set
|
@@ -319,11 +345,27 @@ class GenericSession(BaseDecentrAIObject):
|
|
319
345
|
dauth_endp=None, # get from consts or env
|
320
346
|
add_env=self.__auto_configuration,
|
321
347
|
debug=False,
|
322
|
-
sender_alias=
|
348
|
+
sender_alias=self.name
|
323
349
|
)
|
324
350
|
# end bc_engine
|
325
351
|
# END TODO
|
326
352
|
|
353
|
+
|
354
|
+
str_topic = os.environ.get(ENVIRONMENT.EE_ROOT_TOPIC_ENV_KEY, self.comms_root_topic)
|
355
|
+
|
356
|
+
if str_topic != self.comms_root_topic:
|
357
|
+
self.P(f"Changing root topic from '{self.comms_root_topic}' to '{str_topic}'", color='y')
|
358
|
+
self.comms_root_topic = str_topic
|
359
|
+
|
360
|
+
if self.comms_root_topic is not None:
|
361
|
+
for key in self._config.keys():
|
362
|
+
if isinstance(self._config[key], dict) and 'TOPIC' in self._config[key]:
|
363
|
+
if isinstance(self._config[key]["TOPIC"], str) and self._config[key]["TOPIC"].startswith("{}"):
|
364
|
+
nr_empty = self._config[key]["TOPIC"].count("{}")
|
365
|
+
self._config[key]["TOPIC"] = self._config[key]["TOPIC"].format(self.comms_root_topic, *(["{}"] * (nr_empty - 1)))
|
366
|
+
# end if root_topic
|
367
|
+
|
368
|
+
|
327
369
|
## last config step
|
328
370
|
self.__fill_config(
|
329
371
|
host=self.__host,
|
@@ -371,6 +413,13 @@ class GenericSession(BaseDecentrAIObject):
|
|
371
413
|
self.__start_main_loop_thread()
|
372
414
|
super(GenericSession, self).startup()
|
373
415
|
|
416
|
+
|
417
|
+
def _shorten_addr(self, addr: str) -> str:
|
418
|
+
if not isinstance(addr, str) or len(addr) < 15 or '...' in addr:
|
419
|
+
return addr
|
420
|
+
return addr[:11] + '...' + addr[-4:]
|
421
|
+
|
422
|
+
|
374
423
|
# Message callbacks
|
375
424
|
if True:
|
376
425
|
def __create_user_callback_threads(self):
|
@@ -578,19 +627,32 @@ class GenericSession(BaseDecentrAIObject):
|
|
578
627
|
**kwargs
|
579
628
|
)
|
580
629
|
self.bc_engine.sign(msg_to_send)
|
581
|
-
self.P(f'Sending encrypted payload to <{node_addr}>', color='d')
|
630
|
+
self.P(f'Sending encrypted payload to <{self._shorten_addr(node_addr)}>', color='d')
|
582
631
|
self._send_payload(msg_to_send)
|
583
632
|
return
|
584
633
|
|
585
|
-
def __request_pipelines_from_net_config_monitor(self, node_addr):
|
634
|
+
def __request_pipelines_from_net_config_monitor(self, node_addr=None):
|
586
635
|
"""
|
587
|
-
Request the pipelines for a node
|
636
|
+
Request the pipelines for a node sending the payload to the
|
637
|
+
the net-config monitor plugin instance of that given node or nodes.
|
638
|
+
|
639
|
+
|
588
640
|
Parameters
|
589
641
|
----------
|
590
|
-
node_addr : str or list
|
642
|
+
node_addr : str or list (optional)
|
591
643
|
The address or list of the edge node(s) that sent the message.
|
644
|
+
If None, the request will be sent to all nodes that are allowed to receive messages.
|
645
|
+
|
646
|
+
OBSERVATION:
|
647
|
+
This method should be called without node_addr(s) as it will get all the known peered nodes
|
648
|
+
and request the pipelines from them. Formely, this method was called following a netmon message
|
649
|
+
however, this was not the best approach as the netmon message might contain limited amount of
|
650
|
+
peer information is some cases.
|
592
651
|
|
593
652
|
"""
|
653
|
+
if node_addr is None:
|
654
|
+
node_addr = [k for k, v in self._dct_can_send_to_node.items() if v]
|
655
|
+
# end if
|
594
656
|
assert node_addr is not None, "Node address cannot be None"
|
595
657
|
payload = {
|
596
658
|
NET_CONFIG.NET_CONFIG_DATA: {
|
@@ -603,17 +665,53 @@ class GenericSession(BaseDecentrAIObject):
|
|
603
665
|
}
|
604
666
|
if isinstance(node_addr, str):
|
605
667
|
node_addr = [node_addr]
|
606
|
-
|
607
|
-
|
608
|
-
]
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
668
|
+
|
669
|
+
# now we filter only the nodes that have not been requested recently
|
670
|
+
node_addr = [x for x in node_addr if self.__needs_netconfig_request(x)]
|
671
|
+
|
672
|
+
if len(node_addr) > 0:
|
673
|
+
dest = [
|
674
|
+
f"<{x}> '{self.__dct_node_address_to_alias.get(x, None)}'" for x in node_addr
|
675
|
+
]
|
676
|
+
self.D(f"<NC> Sending request to:\n{json.dumps(dest, indent=2)}")
|
677
|
+
|
678
|
+
self.send_encrypted_payload(
|
679
|
+
node_addr=node_addr, payload=payload,
|
680
|
+
additional_data=additional_data
|
681
|
+
)
|
682
|
+
for node in node_addr:
|
683
|
+
self._dct_netconfig_pipelines_requests[node] = tm()
|
684
|
+
# end if
|
616
685
|
return
|
686
|
+
|
687
|
+
|
688
|
+
|
689
|
+
def __needs_netconfig_request(self, node_addr : str) -> bool:
|
690
|
+
"""
|
691
|
+
Check if a net-config request is needed for a node.
|
692
|
+
|
693
|
+
Parameters
|
694
|
+
----------
|
695
|
+
node_addr : str
|
696
|
+
The address of the edge node.
|
697
|
+
|
698
|
+
Returns
|
699
|
+
-------
|
700
|
+
bool
|
701
|
+
True if a net-config request is needed, False otherwise
|
702
|
+
"""
|
703
|
+
short_addr = self._shorten_addr(node_addr)
|
704
|
+
last_requested_by_netmon = self._dct_netconfig_pipelines_requests.get(node_addr, 0)
|
705
|
+
elapsed = tm() - last_requested_by_netmon
|
706
|
+
str_elapsed = f"{elapsed:.0f}s ago" if elapsed < 9999999 else "never"
|
707
|
+
needs_netconfig_request = elapsed > SDK_NETCONFIG_REQUEST_DELAY
|
708
|
+
if needs_netconfig_request:
|
709
|
+
self.D(f"<NC> Node <{short_addr}> needs update as last request was {str_elapsed} > {SDK_NETCONFIG_REQUEST_DELAY}")
|
710
|
+
else:
|
711
|
+
self.D(f"<NC> Node <{short_addr}> does NOT need update as last request was {str_elapsed} < {SDK_NETCONFIG_REQUEST_DELAY}")
|
712
|
+
return needs_netconfig_request
|
713
|
+
|
714
|
+
|
617
715
|
|
618
716
|
def __track_allowed_node_by_netmon(self, node_addr, dict_msg):
|
619
717
|
"""
|
@@ -643,38 +741,47 @@ class GenericSession(BaseDecentrAIObject):
|
|
643
741
|
|
644
742
|
client_is_allowed = self.bc_engine.contains_current_address(node_whitelist)
|
645
743
|
can_send = not node_secured or client_is_allowed or self.bc_engine.address == node_addr
|
646
|
-
|
744
|
+
self._dct_can_send_to_node[node_addr] = can_send
|
745
|
+
short_addr = self._shorten_addr(node_addr)
|
746
|
+
if can_send:
|
647
747
|
if node_online:
|
648
748
|
# only attempt to request pipelines if the node is online and if not recently requested
|
649
|
-
|
650
|
-
if tm() - last_requested_by_netmon > SDK_NETCONFIG_REQUEST_DELAY:
|
651
|
-
needs_netconfig = True
|
652
|
-
else:
|
653
|
-
self.D(f"Node <{node_addr}> is online but pipelines were recently requested", color='y')
|
749
|
+
needs_netconfig= self.__needs_netconfig_request(node_addr)
|
654
750
|
else:
|
655
|
-
self.D(f"Node <{
|
751
|
+
self.D(f"<NC> Node <{short_addr}> is OFFLINE thus NOT sending net-config request")
|
656
752
|
# endif node seen for the first time
|
657
|
-
|
658
|
-
self._dct_can_send_to_node[node_addr] = can_send
|
659
753
|
return needs_netconfig
|
660
754
|
|
661
755
|
|
662
|
-
def __process_node_pipelines(
|
756
|
+
def __process_node_pipelines(
|
757
|
+
self,
|
758
|
+
node_addr : str,
|
759
|
+
pipelines : list,
|
760
|
+
plugins_statuses : list
|
761
|
+
):
|
663
762
|
"""
|
664
|
-
Given a list of pipeline configurations, create or update the pipelines for a node
|
763
|
+
Given a list of pipeline configurations, create or update the pipelines for a node
|
764
|
+
including the liveness of the plugins required for app monitoring
|
665
765
|
"""
|
666
766
|
new_pipelines = []
|
667
767
|
if node_addr not in self._dct_online_nodes_pipelines:
|
668
768
|
self._dct_online_nodes_pipelines[node_addr] = {}
|
669
769
|
for config in pipelines:
|
670
770
|
pipeline_name = config[PAYLOAD_DATA.NAME]
|
671
|
-
pipeline: Pipeline = self._dct_online_nodes_pipelines[node_addr].get(
|
771
|
+
pipeline: Pipeline = self._dct_online_nodes_pipelines[node_addr].get(
|
772
|
+
pipeline_name, None
|
773
|
+
)
|
672
774
|
if pipeline is not None:
|
673
|
-
pipeline._sync_configuration_with_remote(
|
775
|
+
pipeline._sync_configuration_with_remote(
|
776
|
+
config={k.upper(): v for k, v in config.items()},
|
777
|
+
plugins_statuses=plugins_statuses,
|
778
|
+
)
|
674
779
|
else:
|
675
|
-
|
676
|
-
node_addr, config
|
677
|
-
|
780
|
+
pipeline : Pipeline = self.__create_pipeline_from_config(
|
781
|
+
node_addr=node_addr, config=config, plugins_statuses=plugins_statuses
|
782
|
+
)
|
783
|
+
self._dct_online_nodes_pipelines[node_addr][pipeline_name] = pipeline
|
784
|
+
new_pipelines.append(pipeline)
|
678
785
|
return new_pipelines
|
679
786
|
|
680
787
|
def __on_heartbeat(self, dict_msg: dict, msg_node_addr, msg_pipeline, msg_signature, msg_instance):
|
@@ -685,12 +792,16 @@ class GenericSession(BaseDecentrAIObject):
|
|
685
792
|
----------
|
686
793
|
dict_msg : dict
|
687
794
|
The message received from the communication server
|
795
|
+
|
688
796
|
msg_node_addr : str
|
689
797
|
The address of the Naeural Edge Protocol edge node that sent the message.
|
798
|
+
|
690
799
|
msg_pipeline : str
|
691
800
|
The name of the pipeline that sent the message.
|
801
|
+
|
692
802
|
msg_signature : str
|
693
803
|
The signature of the plugin that sent the message.
|
804
|
+
|
694
805
|
msg_instance : str
|
695
806
|
The name of the instance that sent the message.
|
696
807
|
"""
|
@@ -714,22 +825,29 @@ class GenericSession(BaseDecentrAIObject):
|
|
714
825
|
)
|
715
826
|
|
716
827
|
msg_active_configs = dict_msg.get(HB.CONFIG_STREAMS)
|
828
|
+
whitelist = dict_msg.get(HB.EE_WHITELIST, [])
|
829
|
+
is_allowed = self.bc_engine.contains_current_address(whitelist)
|
717
830
|
if msg_active_configs is None:
|
718
831
|
msg_active_configs = []
|
719
832
|
# at this point we dont return if no active configs are present
|
720
833
|
# as the protocol should NOT send a heartbeat with active configs to
|
721
834
|
# the entire network, only to the interested parties via net-config
|
722
|
-
|
723
|
-
self.D("<HB> Received {} with {} pipelines".format(
|
724
|
-
|
835
|
+
short_addr = self._shorten_addr(msg_node_addr)
|
836
|
+
self.D("<HB> Received {} with {} pipelines (wl: {}, allowed: {})".format(
|
837
|
+
short_addr, len(msg_active_configs), len(whitelist), is_allowed
|
838
|
+
), verbosity=2
|
725
839
|
)
|
726
840
|
|
727
841
|
if len(msg_active_configs) > 0:
|
728
842
|
# this is for legacy and custom implementation where heartbeats still contain
|
729
843
|
# the pipeline configuration.
|
730
844
|
pipeline_names = [x.get(PAYLOAD_DATA.NAME, None) for x in msg_active_configs]
|
731
|
-
|
732
|
-
self.
|
845
|
+
received_plugins = dict_msg.get(HB.ACTIVE_PLUGINS, [])
|
846
|
+
self.D(f'<HB> Processing pipelines from <{short_addr}>:{pipeline_names}', color='y')
|
847
|
+
new_pipeliens = self.__process_node_pipelines(
|
848
|
+
node_addr=msg_node_addr, pipelines=msg_active_configs,
|
849
|
+
plugins_statuses=received_plugins,
|
850
|
+
)
|
733
851
|
|
734
852
|
# TODO: move this call in `__on_message_default_callback`
|
735
853
|
if self.__maybe_ignore_message(msg_node_addr):
|
@@ -780,7 +898,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
780
898
|
self.D("Received notification {} from <{}/{}>: {}"
|
781
899
|
.format(
|
782
900
|
notification_type,
|
783
|
-
msg_node_addr,
|
901
|
+
self._shorten_addr(msg_node_addr),
|
784
902
|
msg_pipeline,
|
785
903
|
notification),
|
786
904
|
color=color,
|
@@ -829,6 +947,8 @@ class GenericSession(BaseDecentrAIObject):
|
|
829
947
|
online_addresses = []
|
830
948
|
all_addresses = []
|
831
949
|
lst_netconfig_request = []
|
950
|
+
short_addr = self._shorten_addr(sender_addr)
|
951
|
+
self.D(f"<NM> Processing {len(current_network)} from <{short_addr}> `{ee_id}`")
|
832
952
|
for _ , node_data in current_network.items():
|
833
953
|
needs_netconfig = False
|
834
954
|
node_addr = node_data.get(PAYLOAD_DATA.NETMON_ADDRESS, None)
|
@@ -846,15 +966,22 @@ class GenericSession(BaseDecentrAIObject):
|
|
846
966
|
if needs_netconfig:
|
847
967
|
lst_netconfig_request.append(node_addr)
|
848
968
|
# end for each node in network map
|
849
|
-
self.Pd(f"
|
850
|
-
|
851
|
-
|
969
|
+
self.Pd(f"<NM> <{short_addr}> `{ee_id}`: {len(online_addresses)} online of total {len(all_addresses)} nodes")
|
970
|
+
first_request = len(self._dct_netconfig_pipelines_requests) == 0
|
971
|
+
if len(lst_netconfig_request) > 0 or first_request:
|
972
|
+
str_msg = "First request for" if first_request else "Requesting"
|
973
|
+
msg = f"<NC> {str_msg} pipelines from at least {len(lst_netconfig_request)} nodes"
|
974
|
+
if first_request:
|
975
|
+
self.P(msg, color='y')
|
976
|
+
else:
|
977
|
+
self.Pd(msg, verbosity=2)
|
978
|
+
self.__request_pipelines_from_net_config_monitor()
|
852
979
|
# end if needs netconfig
|
853
980
|
nr_peers = sum(self._dct_can_send_to_node.values())
|
854
981
|
if nr_peers > 0 and not self.__at_least_one_node_peered:
|
855
982
|
self.__at_least_one_node_peered = True
|
856
983
|
self.P(
|
857
|
-
f"Received {PLUGIN_SIGNATURES.NET_MON_01} from {sender_addr}, so far {nr_peers} peers that allow me: {json.dumps(self._dct_can_send_to_node, indent=2)}",
|
984
|
+
f"<NM> Received {PLUGIN_SIGNATURES.NET_MON_01} from {sender_addr}, so far {nr_peers} peers that allow me: {json.dumps(self._dct_can_send_to_node, indent=2)}",
|
858
985
|
color='g'
|
859
986
|
)
|
860
987
|
# end for each node in network map
|
@@ -878,7 +1005,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
878
1005
|
sender_addr = dict_msg.get(PAYLOAD_DATA.EE_SENDER, None)
|
879
1006
|
short_sender_addr = sender_addr[:8] + '...' + sender_addr[-4:]
|
880
1007
|
if self.client_address == sender_addr:
|
881
|
-
self.D("<
|
1008
|
+
self.D("<NC> Ignoring message from self", color='d')
|
882
1009
|
return
|
883
1010
|
receiver = dict_msg.get(PAYLOAD_DATA.EE_DESTINATION, None)
|
884
1011
|
if not isinstance(receiver, list):
|
@@ -888,26 +1015,32 @@ class GenericSession(BaseDecentrAIObject):
|
|
888
1015
|
op = dict_msg.get(NET_CONFIG.NET_CONFIG_DATA, {}).get(NET_CONFIG.OPERATION, "UNKNOWN")
|
889
1016
|
# drop any incoming request as we are not a net-config provider just a consumer
|
890
1017
|
if op == NET_CONFIG.REQUEST_COMMAND:
|
891
|
-
self.
|
1018
|
+
self.Pd(f"<NC> Dropping request from <{short_sender_addr}> `{ee_id}`")
|
892
1019
|
return
|
893
1020
|
|
894
1021
|
# check if I am allowed to see this payload
|
895
1022
|
if not self.bc_engine.contains_current_address(receiver):
|
896
|
-
self.P(f"<
|
1023
|
+
self.P(f"<NC> Received `{op}` from <{short_sender_addr}> `{ee_id}` but I am not in the receiver list: {receiver}", color='d')
|
897
1024
|
return
|
898
1025
|
|
899
1026
|
# encryption check. By now all should be decrypted
|
900
1027
|
is_encrypted = dict_msg.get(PAYLOAD_DATA.EE_IS_ENCRYPTED, False)
|
901
1028
|
if not is_encrypted:
|
902
|
-
self.P(f"<
|
1029
|
+
self.P(f"<NC> Received from <{short_sender_addr}> `{ee_id}` but it is not encrypted", color='r')
|
903
1030
|
return
|
904
1031
|
net_config_data = dict_msg.get(NET_CONFIG.NET_CONFIG_DATA, {})
|
905
|
-
received_pipelines = net_config_data.get(
|
906
|
-
|
907
|
-
|
1032
|
+
received_pipelines = net_config_data.get(NET_CONFIG.PIPELINES, [])
|
1033
|
+
received_plugins = net_config_data.get(NET_CONFIG.PLUGINS_STATUSES, [])
|
1034
|
+
self.D(f"<NC> Received {len(received_pipelines)} pipelines from <{sender_addr}> `{ee_id}`")
|
1035
|
+
if self._verbosity > 2:
|
1036
|
+
self.D(f"<NC> {ee_id} Netconfig data:\n{json.dumps(net_config_data, indent=2)}")
|
1037
|
+
new_pipelines = self.__process_node_pipelines(
|
1038
|
+
node_addr=sender_addr, pipelines=received_pipelines,
|
1039
|
+
plugins_statuses=received_plugins
|
1040
|
+
)
|
908
1041
|
pipeline_names = [x.name for x in new_pipelines]
|
909
1042
|
if len(new_pipelines) > 0:
|
910
|
-
self.P(f'<
|
1043
|
+
self.P(f'<NC> Received NEW pipelines from <{sender_addr}> `{ee_id}`:{pipeline_names}', color='y')
|
911
1044
|
return True
|
912
1045
|
|
913
1046
|
|
@@ -996,19 +1129,23 @@ class GenericSession(BaseDecentrAIObject):
|
|
996
1129
|
return
|
997
1130
|
|
998
1131
|
try:
|
1132
|
+
if EE_VPN_IMPL and self._eth_enabled:
|
1133
|
+
self.P("Disabling ETH for VPN implementation", color='r')
|
1134
|
+
self._eth_enabled = False
|
1135
|
+
|
999
1136
|
self.bc_engine = DefaultBlockEngine(
|
1000
1137
|
log=self.log,
|
1001
1138
|
name=self.name,
|
1002
1139
|
config=blockchain_config,
|
1003
1140
|
verbosity=self._verbosity,
|
1004
1141
|
user_config=user_config,
|
1005
|
-
eth_enabled=self.
|
1142
|
+
eth_enabled=self._eth_enabled,
|
1006
1143
|
)
|
1007
1144
|
except:
|
1008
1145
|
raise ValueError("Failure in private blockchain setup:\n{}".format(traceback.format_exc()))
|
1009
1146
|
|
1010
1147
|
# extra setup flag for re-connections with same multiton instance
|
1011
|
-
self.bc_engine.set_eth_flag(self.
|
1148
|
+
self.bc_engine.set_eth_flag(self._eth_enabled)
|
1012
1149
|
return
|
1013
1150
|
|
1014
1151
|
def __start_main_loop_thread(self):
|
@@ -1329,8 +1466,8 @@ class GenericSession(BaseDecentrAIObject):
|
|
1329
1466
|
if True:
|
1330
1467
|
|
1331
1468
|
def __load_user_config(self, dotenv_path):
|
1332
|
-
# if the ~/.
|
1333
|
-
if not load_user_defined_config(verbose=not self.
|
1469
|
+
# if the ~/.ratio1/config file exists, load the credentials from there else try to load them from .env
|
1470
|
+
if not load_user_defined_config(verbose=not self.silent):
|
1334
1471
|
# this method will search for the credentials in the environment variables
|
1335
1472
|
# the path to env file, if not specified, will be search in the following order:
|
1336
1473
|
# 1. current working directory
|
@@ -1338,14 +1475,33 @@ class GenericSession(BaseDecentrAIObject):
|
|
1338
1475
|
load_dotenv(dotenv_path=dotenv_path, verbose=False)
|
1339
1476
|
if not self.silent:
|
1340
1477
|
keys = [k for k in os.environ if k.startswith("EE_")]
|
1341
|
-
|
1478
|
+
if not self.silent:
|
1479
|
+
log_with_color(f"Loaded credentials from environment variables: {keys}", color='y')
|
1342
1480
|
self.__user_config_loaded = False
|
1343
1481
|
else:
|
1344
1482
|
if not self.silent:
|
1345
1483
|
keys = [k for k in os.environ if k.startswith("EE_")]
|
1346
|
-
|
1484
|
+
if not self.silent:
|
1485
|
+
log_with_color(f"Loaded credentials from `{get_user_config_file()}`: {keys}.", color='y')
|
1347
1486
|
self.__user_config_loaded = True
|
1348
1487
|
# endif config loading from ~ or ./.env
|
1488
|
+
|
1489
|
+
if self.name is None:
|
1490
|
+
from naeural_client.logging.logger_mixins.utils_mixin import _UtilsMixin
|
1491
|
+
random_name = _UtilsMixin.get_random_name()
|
1492
|
+
default = EE_SDK_ALIAS_DEFAULT + '-' + random_name
|
1493
|
+
self.name = os.environ.get(EE_SDK_ALIAS_ENV_KEY, default)
|
1494
|
+
if EE_SDK_ALIAS_ENV_KEY not in os.environ:
|
1495
|
+
if not self.silent:
|
1496
|
+
log_with_color(f"Using default SDK alias: {self.name}. Writing the user config file...", color='y')
|
1497
|
+
set_client_alias(self.name)
|
1498
|
+
#end with
|
1499
|
+
else:
|
1500
|
+
if not self.silent:
|
1501
|
+
log_with_color(f"SDK Alias (from env): {self.name}.", color='y')
|
1502
|
+
#end if
|
1503
|
+
#end name is None
|
1504
|
+
return self.__user_config_loaded
|
1349
1505
|
|
1350
1506
|
def __fill_config(self, host, port, user, pwd, secured):
|
1351
1507
|
"""
|
@@ -1758,7 +1914,12 @@ class GenericSession(BaseDecentrAIObject):
|
|
1758
1914
|
self.__open_transactions.append(transaction)
|
1759
1915
|
return transaction
|
1760
1916
|
|
1761
|
-
def __create_pipeline_from_config(
|
1917
|
+
def __create_pipeline_from_config(
|
1918
|
+
self,
|
1919
|
+
node_addr : str,
|
1920
|
+
config : dict,
|
1921
|
+
plugins_statuses : list = None,
|
1922
|
+
):
|
1762
1923
|
pipeline_config = {k.lower(): v for k, v in config.items()}
|
1763
1924
|
name = pipeline_config.pop('name', None)
|
1764
1925
|
plugins = pipeline_config.pop('plugins', None)
|
@@ -1771,6 +1932,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
1771
1932
|
name=name,
|
1772
1933
|
plugins=plugins,
|
1773
1934
|
existing_config=pipeline_config,
|
1935
|
+
plugins_statuses=plugins_statuses,
|
1774
1936
|
)
|
1775
1937
|
|
1776
1938
|
return pipeline
|
@@ -2288,9 +2450,9 @@ class GenericSession(BaseDecentrAIObject):
|
|
2288
2450
|
bool
|
2289
2451
|
True if the node is online, False otherwise.
|
2290
2452
|
"""
|
2291
|
-
|
2453
|
+
short_addr = self._shorten_addr(node)
|
2292
2454
|
if verbose:
|
2293
|
-
self.
|
2455
|
+
self.Pd("Waiting for node '{}' to appear online...".format(short_addr))
|
2294
2456
|
|
2295
2457
|
_start = tm()
|
2296
2458
|
found = self.check_node_online(node)
|
@@ -2301,9 +2463,9 @@ class GenericSession(BaseDecentrAIObject):
|
|
2301
2463
|
|
2302
2464
|
if verbose:
|
2303
2465
|
if found:
|
2304
|
-
self.P("Node '{}' is online.".format(
|
2466
|
+
self.P("Node '{}' is online.".format(short_addr))
|
2305
2467
|
else:
|
2306
|
-
self.P("Node '{}' did not appear online in {:.1f}s.".format(
|
2468
|
+
self.P("Node '{}' did not appear online in {:.1f}s.".format(short_addr, tm() - _start), color='r')
|
2307
2469
|
return found
|
2308
2470
|
|
2309
2471
|
def wait_for_node_configs(
|
@@ -2328,8 +2490,8 @@ class GenericSession(BaseDecentrAIObject):
|
|
2328
2490
|
bool
|
2329
2491
|
True if the node has its configurations loaded, False otherwise.
|
2330
2492
|
"""
|
2331
|
-
|
2332
|
-
self.P("Waiting for node '{}' to have its configurations loaded...".format(
|
2493
|
+
short_addr = self._shorten_addr(node)
|
2494
|
+
self.P("Waiting for node '{}' to have its configurations loaded...".format(short_addr))
|
2333
2495
|
|
2334
2496
|
_start = tm()
|
2335
2497
|
found = self.check_node_config_received(node)
|
@@ -2339,7 +2501,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
2339
2501
|
sleep(0.1)
|
2340
2502
|
found = self.check_node_config_received(node)
|
2341
2503
|
if not found and not additional_request_sent and (tm() - _start) > request_time_thr and attempt_additional_requests:
|
2342
|
-
self.P("Re-requesting configurations of node '{}'...".format(
|
2504
|
+
self.P("Re-requesting configurations of node '{}'...".format(short_addr), show=True)
|
2343
2505
|
node_addr = self.__get_node_address(node)
|
2344
2506
|
self.__request_pipelines_from_net_config_monitor(node_addr)
|
2345
2507
|
additional_request_sent = True
|
@@ -2347,9 +2509,9 @@ class GenericSession(BaseDecentrAIObject):
|
|
2347
2509
|
|
2348
2510
|
if verbose:
|
2349
2511
|
if found:
|
2350
|
-
self.P(f"Received configurations of node '{
|
2512
|
+
self.P(f"Received configurations of node '{short_addr}'.")
|
2351
2513
|
else:
|
2352
|
-
self.P(f"Node '{
|
2514
|
+
self.P(f"Node '{short_addr}' did not send configs in {(tm() - _start)}. Client might not be authorized!", color='r')
|
2353
2515
|
return found
|
2354
2516
|
|
2355
2517
|
def check_node_config_received(self, node):
|
@@ -2624,6 +2786,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
2624
2786
|
name,
|
2625
2787
|
signature=PLUGIN_SIGNATURES.TELEGRAM_BASIC_BOT_01,
|
2626
2788
|
message_handler=None,
|
2789
|
+
processor_handler=None,
|
2627
2790
|
telegram_bot_token=None,
|
2628
2791
|
telegram_bot_token_env_key=ENVIRONMENT.TELEGRAM_BOT_TOKEN_ENV_KEY,
|
2629
2792
|
telegram_bot_name=None,
|
@@ -2648,6 +2811,10 @@ class GenericSession(BaseDecentrAIObject):
|
|
2648
2811
|
message_handler : callable, optional
|
2649
2812
|
The message handler function that will be called when a message is received. Defaults to None.
|
2650
2813
|
|
2814
|
+
processor_handler : callable, optional
|
2815
|
+
The processor handler function that will be called in a processing loop within the
|
2816
|
+
Telegram bot plugin in parallel with the message handler. Defaults to None.
|
2817
|
+
|
2651
2818
|
telegram_bot_token : str, optional
|
2652
2819
|
The Telegram bot token. Defaults to None.
|
2653
2820
|
|
@@ -2687,6 +2854,11 @@ class GenericSession(BaseDecentrAIObject):
|
|
2687
2854
|
)
|
2688
2855
|
|
2689
2856
|
func_name, func_args, func_base64_code = pipeline._get_method_data(message_handler)
|
2857
|
+
|
2858
|
+
proc_func_args, proc_func_base64_code =[], None
|
2859
|
+
if processor_handler is not None:
|
2860
|
+
_, proc_func_args, proc_func_base64_code = pipeline._get_method_data(processor_handler)
|
2861
|
+
|
2690
2862
|
if len(func_args) != 2:
|
2691
2863
|
raise ValueError("The message handler function must have exactly 3 arguments: `plugin`, `message` and `user`.")
|
2692
2864
|
|
@@ -2700,6 +2872,8 @@ class GenericSession(BaseDecentrAIObject):
|
|
2700
2872
|
message_handler=func_base64_code,
|
2701
2873
|
message_handler_args=func_args, # mandatory message and user
|
2702
2874
|
message_handler_name=func_name, # not mandatory
|
2875
|
+
processor_handler=proc_func_base64_code, # not mandatory
|
2876
|
+
processor_handler_args=proc_func_args, # not mandatory
|
2703
2877
|
**kwargs
|
2704
2878
|
)
|
2705
2879
|
return pipeline, instance
|
@@ -2714,8 +2888,8 @@ class GenericSession(BaseDecentrAIObject):
|
|
2714
2888
|
telegram_bot_token=None,
|
2715
2889
|
telegram_bot_token_env_key=ENVIRONMENT.TELEGRAM_BOT_TOKEN_ENV_KEY,
|
2716
2890
|
telegram_bot_name=None,
|
2717
|
-
telegram_bot_name_env_key=ENVIRONMENT.TELEGRAM_BOT_NAME_ENV_KEY,
|
2718
|
-
|
2891
|
+
telegram_bot_name_env_key=ENVIRONMENT.TELEGRAM_BOT_NAME_ENV_KEY,
|
2892
|
+
processor_handler=None,
|
2719
2893
|
system_prompt=None,
|
2720
2894
|
agent_type="API",
|
2721
2895
|
api_token_env_key=ENVIRONMENT.TELEGRAM_API_AGENT_TOKEN_ENV_KEY,
|
@@ -2789,6 +2963,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
2789
2963
|
if telegram_bot_name is None:
|
2790
2964
|
message = f"Warning! No Telegram bot name provided as via env {ENVIRONMENT.TELEGRAM_BOT_NAME_ENV_KEY} or explicitly as `telegram_bot_name` param."
|
2791
2965
|
raise ValueError(message)
|
2966
|
+
|
2792
2967
|
|
2793
2968
|
|
2794
2969
|
pipeline: Pipeline = self.create_pipeline(
|
@@ -2796,6 +2971,10 @@ class GenericSession(BaseDecentrAIObject):
|
|
2796
2971
|
name=name,
|
2797
2972
|
# default TYPE is "Void"
|
2798
2973
|
)
|
2974
|
+
|
2975
|
+
proc_func_args, proc_func_base64_code =[], None
|
2976
|
+
if processor_handler is not None:
|
2977
|
+
_, proc_func_args, proc_func_base64_code = pipeline._get_method_data(processor_handler)
|
2799
2978
|
|
2800
2979
|
|
2801
2980
|
obfuscated_token = telegram_bot_token[:4] + "*" * (len(telegram_bot_token) - 4)
|
@@ -2803,8 +2982,13 @@ class GenericSession(BaseDecentrAIObject):
|
|
2803
2982
|
instance = pipeline.create_plugin_instance(
|
2804
2983
|
signature=signature,
|
2805
2984
|
instance_id=self.log.get_unique_id(),
|
2985
|
+
|
2806
2986
|
telegram_bot_token=telegram_bot_token,
|
2807
2987
|
telegram_bot_name=telegram_bot_name,
|
2988
|
+
|
2989
|
+
processor_handler=proc_func_base64_code, # not mandatory
|
2990
|
+
processor_handler_args=proc_func_args, # not mandatory
|
2991
|
+
|
2808
2992
|
system_prompt=system_prompt,
|
2809
2993
|
agent_type=agent_type,
|
2810
2994
|
api_token=api_token,
|
@@ -3133,3 +3317,132 @@ class GenericSession(BaseDecentrAIObject):
|
|
3133
3317
|
if df_only:
|
3134
3318
|
return dct_result[SESSION_CT.NETSTATS_REPORT]
|
3135
3319
|
return dct_result
|
3320
|
+
|
3321
|
+
|
3322
|
+
def get_nodes_apps(
|
3323
|
+
self,
|
3324
|
+
node=None,
|
3325
|
+
owner=None,
|
3326
|
+
show_full=False,
|
3327
|
+
as_json=False,
|
3328
|
+
show_errors=False,
|
3329
|
+
as_df=False
|
3330
|
+
):
|
3331
|
+
"""
|
3332
|
+
Get the workload status of a node.
|
3333
|
+
|
3334
|
+
Parameters
|
3335
|
+
----------
|
3336
|
+
|
3337
|
+
node : str, optional
|
3338
|
+
The address or name of the Naeural Edge Protocol edge node. Defaults to None.
|
3339
|
+
|
3340
|
+
owner : str, optional
|
3341
|
+
The owner of the apps to filter. Defaults to None.
|
3342
|
+
|
3343
|
+
show_full : bool, optional
|
3344
|
+
If True, will show the full configuration of the apps. Defaults to False.
|
3345
|
+
|
3346
|
+
as_json : bool, optional
|
3347
|
+
If True, will return the result as a JSON. Defaults to False.
|
3348
|
+
|
3349
|
+
show_errors : bool, optional
|
3350
|
+
If True, will show the errors. Defaults to False.
|
3351
|
+
|
3352
|
+
as_df : bool, optional
|
3353
|
+
If True, will return the result as a Pandas DataFrame. Defaults to False.
|
3354
|
+
|
3355
|
+
|
3356
|
+
Returns
|
3357
|
+
-------
|
3358
|
+
|
3359
|
+
list
|
3360
|
+
A list of dictionaries containing the workload status
|
3361
|
+
of the specified node.
|
3362
|
+
|
3363
|
+
|
3364
|
+
"""
|
3365
|
+
lst_plugin_instance_data = []
|
3366
|
+
if node is None:
|
3367
|
+
nodes = self.get_active_nodes()
|
3368
|
+
else:
|
3369
|
+
nodes = [node]
|
3370
|
+
found_nodes = []
|
3371
|
+
for node in nodes:
|
3372
|
+
short_addr = self._shorten_addr(node)
|
3373
|
+
# 2. Wait for node to appear online
|
3374
|
+
node_found = self.wait_for_node(node)
|
3375
|
+
if node_found:
|
3376
|
+
found_nodes.append(node)
|
3377
|
+
|
3378
|
+
# 3. Check if the node is peered with the client
|
3379
|
+
is_allowed = self.is_peered(node)
|
3380
|
+
if not is_allowed:
|
3381
|
+
if show_errors:
|
3382
|
+
log_with_color(f"Node {short_addr} is not peered with this client. Skipping..", color='r')
|
3383
|
+
continue
|
3384
|
+
|
3385
|
+
# 4. Wait for node to send the configuration.
|
3386
|
+
self.wait_for_node_configs(node)
|
3387
|
+
apps = self.get_active_pipelines(node)
|
3388
|
+
if apps is None:
|
3389
|
+
if show_errors:
|
3390
|
+
log_with_color(f"No apps found on node {short_addr}. Client might not be authorized", color='r')
|
3391
|
+
continue
|
3392
|
+
|
3393
|
+
# 5. Maybe exclude admin application.
|
3394
|
+
if not show_full:
|
3395
|
+
apps = {k: v for k, v in apps.items() if str(k).lower() != 'admin_pipeline'}
|
3396
|
+
|
3397
|
+
# 6. Show the apps
|
3398
|
+
if as_json:
|
3399
|
+
# Will print a big JSON with all the app configurations.
|
3400
|
+
lst_plugin_instance_data.append({k: v.get_full_config() for k, v in apps.items()})
|
3401
|
+
else:
|
3402
|
+
for pipeline_name, pipeline in apps.items():
|
3403
|
+
pipeline_owner = pipeline.config.get("INITIATOR_ADDR")
|
3404
|
+
if owner is not None and owner != pipeline_owner:
|
3405
|
+
continue
|
3406
|
+
pipeline_alias = pipeline.config.get("INITIATOR_ID")
|
3407
|
+
for instance in pipeline.lst_plugin_instances:
|
3408
|
+
instance_status = instance.get_status()
|
3409
|
+
if len(instance_status) == 0:
|
3410
|
+
# this instance is only present in config but is NOT loaded so ignore it
|
3411
|
+
continue
|
3412
|
+
start_time = instance_status.get('INIT_TIMESTAMP')
|
3413
|
+
last_probe = instance_status.get('EXEC_TIMESTAMP')
|
3414
|
+
last_data = instance_status.get('LAST_PAYLOAD_TIME')
|
3415
|
+
dates = [start_time, last_probe, last_data]
|
3416
|
+
for i in range(len(dates)):
|
3417
|
+
if isinstance(dates[i], str):
|
3418
|
+
if dates[i].startswith('1970'):
|
3419
|
+
dates[i] = 'Never'
|
3420
|
+
elif '.' in dates[i]:
|
3421
|
+
dates[i] = dates[i].split('.')[0]
|
3422
|
+
lst_plugin_instance_data.append({
|
3423
|
+
'Node' : node,
|
3424
|
+
'Owner' : pipeline_owner,
|
3425
|
+
'Alias' : pipeline_alias,
|
3426
|
+
'App': pipeline_name,
|
3427
|
+
'Plugin': instance.signature,
|
3428
|
+
'Id': instance.instance_id,
|
3429
|
+
'Start' : dates[0],
|
3430
|
+
'Probe' : dates[1],
|
3431
|
+
'Data' : dates[2],
|
3432
|
+
})
|
3433
|
+
# endfor instances in app
|
3434
|
+
# endfor apps
|
3435
|
+
# endif as_json or as dict-for-df
|
3436
|
+
# endfor nodes
|
3437
|
+
if len(found_nodes) == 0:
|
3438
|
+
log_with_color(f'Node(s) {nodes} not found. Please check the configuration.', color='r')
|
3439
|
+
return
|
3440
|
+
if as_df:
|
3441
|
+
df = pd.DataFrame(lst_plugin_instance_data)
|
3442
|
+
if not (df.empty or df.shape[0] == 0):
|
3443
|
+
df['Node'] = df['Node'].apply(lambda x: self._shorten_addr(x))
|
3444
|
+
df['Owner'] = df['Owner'].apply(lambda x: self._shorten_addr(x))
|
3445
|
+
# end if not empty
|
3446
|
+
return df
|
3447
|
+
return lst_plugin_instance_data
|
3448
|
+
|