Commit acb2cfd5d882b91c9a9886f48b1eb7e55eb7b85f
1 parent
ff72df76
Exists in
dev
fixing issue when launching agent with new-start command with test argument
Showing
2 changed files
with
2646 additions
and
2 deletions
Show diff stats
... | ... | @@ -0,0 +1,2646 @@ |
1 | +#!/usr/bin/env python3 | |
2 | + | |
3 | +#DEBUG=True | |
4 | +#DEBUG=False | |
5 | + | |
6 | +""" | |
7 | +================================================================= | |
8 | +Agent class | |
9 | +================================================================= | |
10 | + | |
11 | +This class is the base class for all AgentXXX classes (AgentSST, AgentScheduler, AgentImagesProcessor...) | |
12 | + | |
13 | +It runs an infinite loop (called main_loop) doing at each iteration | |
14 | +some routine tasks (process_routine before and after) | |
15 | +and executing commands sent by other agents. | |
16 | + | |
17 | +To start it up, from the project root : | |
18 | + | |
19 | +- In normal mode : | |
20 | +- ./PYROS start -fg agent -o tnc | |
21 | +(add -d after the PYROS command for debug mode) | |
22 | + | |
23 | +- In test mode (the agent will execute a scenario | |
24 | +as a suite of commands (in TEST_COMMANDS_LIST) | |
25 | +to send to himself (or other agents) | |
26 | +and execute them on reception at each iteration : | |
27 | +- ./PYROS -t start -fg agent -o tnc | |
28 | + | |
29 | +""" | |
30 | + | |
31 | + | |
32 | +### | |
33 | +# ================================================================= | |
34 | +# SETUP FOR DJANGO | |
35 | +# | |
36 | +# (see https://docs.djangoproject.com/en/dev/topics/settings) | |
37 | +# (see also https://docs.djangoproject.com/en/dev/ref/settings) | |
38 | +# ================================================================= | |
39 | +### | |
40 | + | |
41 | + | |
42 | +# For cmd parsing | |
43 | +from typing import List, Tuple | |
44 | +import ast | |
45 | + | |
46 | +import os | |
47 | +from pathlib import Path | |
48 | +import sys | |
49 | + | |
50 | +import logging | |
51 | + | |
52 | +from django.conf import settings as djangosettings | |
53 | + | |
54 | +# Conseil sur le net: | |
55 | +#https://stackoverflow.com/questions/16853649/how-to-execute-a-python-script-from-the-django-shell | |
56 | +#"" | |
57 | +#import sys, os | |
58 | +#sys.path.append('/path/to/your/django/app') | |
59 | +#os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' | |
60 | +#from django.conf import settings | |
61 | +#"" | |
62 | + | |
63 | +# To avoid a "ModuleNotFoundError: No module named 'dashboard'"... (not even 1 app found) : | |
64 | +##sys.path.insert(0, os.path.abspath("..")) | |
65 | +##sys.path.insert(0, os.path.abspath("src")) | |
66 | +##sys.path.insert(0, "../src") | |
67 | +##sys.path.insert(0, "src") | |
68 | +# To avoid a "ModuleNotFoundError: No module named 'dashboard'" | |
69 | +## sys.path.append("..") | |
70 | +py_pwd = os.path.normpath(os.getcwd() + "/..") | |
71 | +if (py_pwd not in os.sys.path): | |
72 | + (os.sys.path).append(py_pwd) | |
73 | +# To avoid a "ModuleNotFoundError: No module named 'src'" | |
74 | +## sys.path.append("../../../..") | |
75 | +py_pwd = os.path.normpath(os.getcwd() + "/../../../..") | |
76 | +if (py_pwd not in os.sys.path): | |
77 | + (os.sys.path).append(py_pwd) | |
78 | +##sys.path.append("src") | |
79 | + | |
80 | +from src.pyros_logger import log, handler_filebyagent | |
81 | +#from src.pyros_logger import logger as logg, handler_filebyagent, | |
82 | +##from src import pyros_logger | |
83 | + | |
84 | +''' | |
85 | +def printd(*args, **kwargs): | |
86 | + if os.environ.get('PYROS_DEBUG', '0')=='1': print(*args, **kwargs) | |
87 | +''' | |
88 | + | |
89 | +#printd("Starting with this sys.path", sys.path) | |
90 | +log.debug("Starting with this sys.path" + str(sys.path)) | |
91 | + | |
92 | +# DJANGO setup | |
93 | +# self.printd("file is", __file__) | |
94 | +# mypath = os.getcwd() | |
95 | +# Go into src/ | |
96 | +##os.chdir("..") | |
97 | +##os.chdir("src") | |
98 | +#printd("Current directory : " + str(os.getcwd())) | |
99 | +log.debug("Current directory : " + str(os.getcwd())) | |
100 | + | |
101 | +#os.environ.setdefault("DJANGO_SETTINGS_MODULE", "src.core.pyros_django.pyros.settings") | |
102 | +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "src.core.pyros_django.pyros.settings") | |
103 | +# os.environ['SECRET_KEY'] = 'abc' | |
104 | +# os.environ['ENVIRONMENT'] = 'production' | |
105 | +import django | |
106 | + | |
107 | +django.setup() | |
108 | + | |
109 | +#printd("DB2 used is:", djangosettings.DATABASES["default"]["NAME"]) | |
110 | +log.debug("DB2 used is:" + djangosettings.DATABASES["default"]["NAME"]) | |
111 | + | |
112 | + | |
113 | +### | |
114 | +# ================================================================= | |
115 | +# IMPORT PYTHON PACKAGES | |
116 | +#================================================================= | |
117 | +### | |
118 | + | |
119 | +# --- GENERAL PURPOSE IMPORT --- | |
120 | +#from __future__ import absolute_import | |
121 | +##import utils.Logger as L | |
122 | +import platform | |
123 | +import random | |
124 | +import threading | |
125 | +#import multiprocessing | |
126 | +import time | |
127 | +''' | |
128 | +from threading import Thread | |
129 | +import socket | |
130 | +''' | |
131 | +#import ctypes | |
132 | +#import copy | |
133 | + | |
134 | +# --- DJANGO IMPORT --- | |
135 | +from django.db import transaction | |
136 | +from django import db | |
137 | +# from django.core.exceptions import ObjectDoesNotExist | |
138 | +# from django.db.models import Q | |
139 | +#from django.shortcuts import get_object_or_404 | |
140 | +#from django.conf import settings as djangosettings | |
141 | + | |
142 | +# --- SPECIFIC IMPORT --- | |
143 | +# Many ways to import configuration settings: | |
144 | +##import config | |
145 | +import config.old_config as config_old | |
146 | +#from config import PYROS_ENV, ROOT_DIR, DOC_DIR | |
147 | +#from config import * | |
148 | + | |
149 | +from common.models import AgentSurvey, AgentCmd, AgentLogs | |
150 | + | |
151 | +#from config.configpyros import ConfigPyros | |
152 | +from config.old_config.configpyros import ConfigPyros as ConfigPyrosOld | |
153 | +from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig | |
154 | + | |
155 | +#from dashboard.views import get_sunelev | |
156 | +#from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault | |
157 | +#from utils.JDManipulator import * | |
158 | + | |
159 | +##from agent.logpyros import LogPyros | |
160 | +##from src.logpyros import LogPyros | |
161 | + | |
162 | +''' | |
163 | +from device_controller.abstract_component.device_controller import ( | |
164 | + DCCNotFoundException, UnknownGenericCmdException, | |
165 | + UnimplementedGenericCmdException, UnknownNativeCmdException | |
166 | +) | |
167 | +''' | |
168 | + | |
169 | + | |
170 | + | |
171 | +### | |
172 | +# ================================================================= | |
173 | +# GENERAL MODULE CONSTANT & FUNCTIONS DEFINITIONS | |
174 | +# ================================================================= | |
175 | +### | |
176 | + | |
177 | +#DEBUG_FILE = False | |
178 | + | |
179 | +##log = L.setupLogger("AgentLogger", "Agent") | |
180 | + | |
181 | +IS_WINDOWS = platform.system() == "Windows" | |
182 | + | |
183 | + | |
184 | +class Colors: | |
185 | + HEADER = "\033[95m" | |
186 | + BLUE = "\033[94m" | |
187 | + GREEN = "\033[92m" | |
188 | + WARNING = "\033[93m" | |
189 | + FAIL = "\033[91m" | |
190 | + ENDC = "\033[0m" | |
191 | + BOLD = "\033[1m" | |
192 | + UNDERLINE = "\033[4m" | |
193 | + | |
194 | +def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False): | |
195 | + #system = platform.system() | |
196 | + """ | |
197 | + if (self.disp == False and forced == False): | |
198 | + return 0 | |
199 | + """ | |
200 | + #if system == "Windows": | |
201 | + if IS_WINDOWS: | |
202 | + print(message, file=file, end=eol) | |
203 | + else: | |
204 | + print(color + message + Colors.ENDC, file=file, end=eol) | |
205 | + return 0 | |
206 | + | |
207 | +def printFullTerm(color: Colors, string: str): | |
208 | + #system = platform.system() | |
209 | + columns = 100 | |
210 | + row = 1000 | |
211 | + disp = True | |
212 | + value = int(columns / 2 - len(string) / 2) | |
213 | + printColor(color, "-" * value, eol="") | |
214 | + printColor(color, string, eol="") | |
215 | + value += len(string) | |
216 | + printColor(color, "-" * (columns - value)) | |
217 | + return 0 | |
218 | + | |
219 | + | |
220 | +""" | |
221 | +================================================================= | |
222 | + class StoppableThread | |
223 | +================================================================= | |
224 | +""" | |
225 | +''' | |
226 | +class StoppableThreadEvenWhenSleeping(threading.Thread): | |
227 | + # Thread class with a stop() method. The thread itself has to check | |
228 | + # regularly for the stopped() condition. | |
229 | + # It stops even if sleeping | |
230 | + # See https://python.developpez.com/faq/?page=Thread#ThreadKill | |
231 | + # See also https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s03.html | |
232 | + | |
233 | + def __init__(self, *args, **kwargs): | |
234 | + #super(StoppableThreadSimple, self).__init__(*args, **kwargs) | |
235 | + super().__init__(*args, **kwargs) | |
236 | + self._stop_event = threading.Event() | |
237 | + | |
238 | + #def stop(self): | |
239 | + def terminate(self): | |
240 | + self._stop_event.set() | |
241 | + | |
242 | + def stopped(self): | |
243 | + return self._stop_event.is_set() | |
244 | + | |
245 | + def wait(self, nbsec:float=2.0): | |
246 | + self._stop_event.wait(nbsec) | |
247 | +''' | |
248 | + | |
249 | + | |
250 | +### | |
251 | +# ================================================================= | |
252 | +# EXCEPTIONS classes | |
253 | +# ================================================================= | |
254 | +### | |
255 | + | |
256 | +class AgentCmdException(Exception): | |
257 | + ''' Base class for all Agent command exceptions ''' | |
258 | + # pass | |
259 | + def __init__(self, cmd:AgentCmd): | |
260 | + self.cmd = cmd | |
261 | + ''' | |
262 | + def __str__(self): | |
263 | + return f"The Agent command '{self.cmd.name}' is unknown to the agent" | |
264 | + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller" | |
265 | + ''' | |
266 | + | |
267 | +class UnknownCmdException(AgentCmdException): | |
268 | + ''' Raised when an Agent (specific) cmd is NOT known by the agent ''' | |
269 | + def __str__(self): | |
270 | + return f"The Agent command '{self.cmd.name}' is unknown to the agent" | |
271 | + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller" | |
272 | + | |
273 | +class AgentCmdUnimplementedException(AgentCmdException): | |
274 | + ''' Raised when an Agent Specific cmd is known by the agent but not implemented ''' | |
275 | + def __str__(self): | |
276 | + return f"The Agent command '{self.cmd.name}' is known by the agent but not implemented" | |
277 | + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller" | |
278 | + | |
279 | +class AgentCmdBadArgsException(AgentCmdException): | |
280 | + ''' Raised when an Agent cmd has bad, missing, or too many argument(s) ''' | |
281 | + def __str__(self): | |
282 | + return f"The Agent command '{self.cmd.name}' has bad, missing, or too many argument(s)" | |
283 | + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller" | |
284 | + | |
285 | + | |
286 | + | |
287 | +### | |
288 | +# ================================================================= | |
289 | +# class Agent | |
290 | +# ================================================================= | |
291 | +### | |
292 | + | |
293 | +class Agent: | |
294 | + """ | |
295 | + See Agent_activity_diag.pu for PlantUML activity diagram | |
296 | + | |
297 | + Behavior of an agent: | |
298 | + - If idle : | |
299 | + - still does routine_process() and general_process() | |
300 | + - does not do specific_process() | |
301 | + - Once a command has been sent to another agent : | |
302 | + - It waits (non blocking) for the end of execution of the command and get its result | |
303 | + - If command is timed out or has been skipped or killed, then it is NOT re-executed at next iteration (except if needed explicitely) | |
304 | + """ | |
305 | + | |
306 | + # --- | |
307 | + # --- CLASS (STATIC) attributes (CONSTANTS) | |
308 | + # --- If agent is instance of Agent: | |
309 | + # --- - CLASS attributes are accessible via agent.__class__.__dict__ | |
310 | + # --- - INSTANCE attributes are accessible via agent.__dict__ | |
311 | + # --- | |
312 | + | |
313 | + # Default modes | |
314 | + DEBUG_MODE = False | |
315 | + WITH_SIMULATOR = False | |
316 | + #TEST_MODE = False | |
317 | + | |
318 | + # By default, a command is valid during 5s (and then perempted) | |
319 | + DEFAULT_CMD_VALIDITY_DURATION = 5 | |
320 | + | |
321 | + # Wait a fixed number of seconds before each loop ? | |
322 | + #WITH_RANDOM_WAIT = False | |
323 | + # 1 sec by default | |
324 | + _DELAY_NB_SEC = 1 | |
325 | + # - YES if TEST mode (in init()) | |
326 | + | |
327 | + # Default LOG level is INFO | |
328 | + #PYROS_DEFAULT_GLOBAL_LOG_LEVEL = LogPyros.LOG_LEVEL_INFO # INFO | |
329 | + | |
330 | + # This Agent Specific commands list, that he is able to execute | |
331 | + # To be overriden by subclasses (here are just a few examples of commands) | |
332 | + # Format : ("cmd",timeout) with : | |
333 | + # - cmd : the command name | |
334 | + # - timeout : the command timeout (in sec) | |
335 | + AGENT_SPECIFIC_COMMANDS = [ | |
336 | + #"do_specific1", | |
337 | + #"set_specific2", | |
338 | + #"do_specific3", | |
339 | + ("do_specific1", 10), | |
340 | + ("set_specific2", 5), | |
341 | + ("do_specific3", 3), | |
342 | + ] | |
343 | + | |
344 | + # | |
345 | + # --- FOR TEST ONLY --- | |
346 | + # | |
347 | + | |
348 | + # By default, NOT in test mode | |
349 | + #TEST_MODE = False | |
350 | + | |
351 | + # Maximum duration of this agent (only for SIMULATION mode) | |
352 | + # If set to None, it will never exit except if asked (or CTRL-C) | |
353 | + # If set to 20, it will exit after 20s | |
354 | + TEST_MAX_DURATION_SEC = None | |
355 | + #TEST_MAX_DURATION_SEC = 30 | |
356 | + # Run this agent in simulator mode | |
357 | + #TEST_MODE = True | |
358 | + WITH_SIMULATOR = False | |
359 | + # Run the assertion tests at the end | |
360 | + TEST_WITH_FINAL_TEST = False | |
361 | + | |
362 | + CMD_STATUS = AgentCmd.CMD_STATUS_CODES | |
363 | + | |
364 | + # (EP 2022/07) Default SCENARIO to be executed (only in TEST mode), and then STOP | |
365 | + # | |
366 | + # It is a list of commands to be sent by this agent to other agents ("self" means himself) | |
367 | + # | |
368 | + # Format : List of tuples (command, validity, expected_res, expected_status), with : | |
369 | + # | |
370 | + # - command : the format is "recipient cmd args", with : | |
371 | + # - recipient : name of the Agent that the command is to be sent to (use "self" to mean himself) | |
372 | + # - cmd : the command name | |
373 | + # - args : (optional) the list of command arguments, separated by blanks : arg1 arg2 arg3 ... | |
374 | + # | |
375 | + # - validity : the command is valid for this duration, afterwards you can forget it | |
376 | + # | |
377 | + # - expected_res : the expected result | |
378 | + # | |
379 | + # - expected_status : the status of the command expected after execution (expired, killed, skipped, executed...) | |
380 | + # | |
381 | + # Ex : | |
382 | + # - "AgentScheduler set_state ATTENTIVE" => means to send the command "set_state ATTENTIVE" to the agent AgentScheduler | |
383 | + # - "AgentX set_state ATTENTIVE" => means to send the command "set_state ATTENTIVE" to the agent AgentX | |
384 | + # - "self set_state ATTENTIVE" => means to send the command "set_state ATTENTIVE" to MYSELF | |
385 | + # - "self do_restart_loop" => means to send the command "do_restart_loop" to MYSELF (no args) | |
386 | + # | |
387 | + TEST_COMMANDS_LIST = [ | |
388 | + | |
389 | + # 1) First, 3 EXCEPTION CASES (uncomment to activate exception) | |
390 | + # Each of these lines will stop execution with an exception | |
391 | + # ------------------------------------------------------------ | |
392 | + | |
393 | + # - Agent command, unknown => ko, UnknownCmdException | |
394 | + #("self do_unknown", 10, None,None), | |
395 | + | |
396 | + # - Agent general command malformed (missing arg) => ko, AgentCmdBadArgsException | |
397 | + #("Agent set_mode", 10, None,None), | |
398 | + | |
399 | + # - Agent specific command, known but not implemented => ko, AgentCmdUnimplementedException | |
400 | + #("self set_specific2", 10, None,None), | |
401 | + | |
402 | + # - Agent specific command, implemented but missing args => ko, AgentCmdBadArgsException | |
403 | + #(" self do_specific1 1 ", 10, None,None), | |
404 | + | |
405 | + | |
406 | + # 2) NORMAL CASES (test scenario) | |
407 | + # All these commands should be executed without error, from the 1st to the last one | |
408 | + # ------------------------------- | |
409 | + | |
410 | + # This command has a validity of 0s and thus should be tagged as "expired" | |
411 | + ("self set_mode ATTENTIVE", 0, "MODE = ATTENTIVE", CMD_STATUS.CMD_OUTOFDATE), | |
412 | + | |
413 | + # Agent general command | |
414 | + ("self set_mode ATTENTIVE", 200, "MODE = ATTENTIVE", CMD_STATUS.CMD_EXECUTED), | |
415 | + # => should get "ATTENTIVE" | |
416 | + ("self get_mode", 100, "MODE = ATTENTIVE", None), | |
417 | + | |
418 | + # => should get "7" | |
419 | + ("self do_eval 3+5-1", 200, 7, None), | |
420 | + | |
421 | + # END, will not go further | |
422 | + #("self do_exit", 2, "STOPPING"), | |
423 | + | |
424 | + # Agent specific commands => should be executed | |
425 | + ("self do_specific3", 200, None, None), | |
426 | + ("self do_exit", 500, "STOPPING", None), | |
427 | + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, 7, None), | |
428 | + | |
429 | + ("self set_mode ROUTINE", 200, "MODE = ROUTINE", None), | |
430 | + # => should get "ROUTINE" | |
431 | + ("self get_mode", 200, "MODE = ROUTINE", None), | |
432 | + # Agent specific command => should be skipped (because not ATTENTIVE) | |
433 | + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, "SKIPPED", None), | |
434 | + | |
435 | + # From now on, should not run anymore process_before/after | |
436 | + # => and should skip next specific commands | |
437 | + ("self set_mode IDLE", 200, "MODE = IDLE", None), | |
438 | + # => should get "IDLE" | |
439 | + ("self get_mode", 200, "MODE = IDLE", None), | |
440 | + # Agent specific command => should be skipped (because not ATTENTIVE) | |
441 | + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, 'SKIPPED', None), | |
442 | + | |
443 | + # TODO: test priority commands : do_abort, do_flush_cmds, ... | |
444 | + # - Stop executing new commands (just let them accumulate) | |
445 | + ##("self do_stop_exec",), | |
446 | + ##("self set_mode ATTENTIVE",), | |
447 | + ##("self get_mode",), | |
448 | + ##("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]",), | |
449 | + ##("self set_mode ROUTINE",), | |
450 | + ##("self get_mode",), | |
451 | + ##("self do_abort",), | |
452 | + # - Now, resume execution of commands : | |
453 | + # we should have the previous list pending, | |
454 | + # but as "do_abort" is priority, it should be executed first even if last ! | |
455 | + ##("self do_resume_exec",), | |
456 | + | |
457 | + # Restart the restart loop (from init()) | |
458 | + ("self do_restart_loop", 200, "RESTARTING", None), | |
459 | + | |
460 | + # Now stop | |
461 | + ("self do_exit", 200, "STOPPING", None), | |
462 | + | |
463 | + ''' | |
464 | + # specific0 not_executed_because_idle | |
465 | + "specific0", | |
466 | + | |
467 | + "set_state:active", | |
468 | + | |
469 | + # specific1 executed_because_not_idle, should complete ok | |
470 | + "specific1", | |
471 | + | |
472 | + # specific2 will be executed only when specific1 is finished, | |
473 | + # and should be aborted before end of execution, | |
474 | + # because of the 1st coming "do_abort" command below | |
475 | + "specific2", | |
476 | + | |
477 | + # specific3 should be executed only when specific2 is finished (in fact, aborted), | |
478 | + # and should be aborted before end of execution, | |
479 | + # because of the 2nd coming "do_abort" command below | |
480 | + "specific3", | |
481 | + | |
482 | + # These commands should not have the time to be processed | |
483 | + # because the "do_exit" command below should be executed before | |
484 | + "specific4", | |
485 | + "specific5", | |
486 | + "specific6", | |
487 | + "specific7", | |
488 | + "specific8", | |
489 | + | |
490 | + # Should abort the current running command (which should normally be specific2) | |
491 | + # even if commands above (specific3, ..., specific8) are already pending | |
492 | + "do_abort", | |
493 | + | |
494 | + # These commands (except abort) won't be executed | |
495 | + # because too many commands are already pending (above) | |
496 | + "specific9", | |
497 | + "do_abort", | |
498 | + "set_state:active", | |
499 | + "set_state:idle", | |
500 | + | |
501 | + # Should stop the agent even before the previous pending commands are executed | |
502 | + "do_exit", | |
503 | + | |
504 | + # Because of the previous "do_exit" command, | |
505 | + # these following commands should not be executed, | |
506 | + # and not even be added to the database command table | |
507 | + "set_state:active", | |
508 | + "specific10" | |
509 | + ''' | |
510 | + ] | |
511 | + #TEST_COMMANDS = iter(TEST_COMMANDS_LIST) | |
512 | + | |
513 | + | |
514 | + | |
515 | + ''' | |
516 | + # with thread | |
517 | + RUN_IN_THREAD = True | |
518 | + # with process | |
519 | + #RUN_IN_THREAD = False | |
520 | + _thread_total_steps_number = 1 | |
521 | + ''' | |
522 | + | |
523 | + | |
524 | + #COMMANDS_PEREMPTION_HOURS = 48 | |
525 | + #COMMANDS_PEREMPTION_HOURS = 60/60 | |
526 | + | |
527 | + name = "Generic Agent" | |
528 | + status = None | |
529 | + mode = None | |
530 | + config = None | |
531 | + | |
532 | + # STATUS | |
533 | + STATUS_LAUNCH = "LAUNCHED" | |
534 | + STATUS_INIT = "INITIALIZING" | |
535 | + STATUS_MAIN_LOOP = "IN_MAIN_LOOP" | |
536 | + STATUS_GET_NEXT_COMMAND = "IN_GET_NEXT_COMMAND" | |
537 | + STATUS_GENERAL_PROCESS = "IN_GENERAL_PROCESS" | |
538 | + STATUS_ROUTINE_PROCESS = "IN_ROUTINE_PROCESS" | |
539 | + ###STATUS_SPECIFIC_PROCESS = "IN_SPECIFIC_PROCESS" | |
540 | + STATUS_EXIT = "EXITING" | |
541 | + | |
542 | + | |
543 | + # MODE | |
544 | + # | |
545 | + # In all modes, the Agent listens to commands sent to him and executes Agent level GENERAL ones. | |
546 | + # - MODE_IDLE : "idle" mode, does nothing, only executes Agent level GENERAL commands (DO_RESTART, DO_EXIT, DO_ABORT, DO_FLUSH, SET_ACTIVE, ...) | |
547 | + # - MODE_ROUTINE : idem IDLE + executes routine process (before & after) | |
548 | + # - MODE_ATTENTIVE : idem ROUTINE + executes Agent level SPECIFIC commands (commands specific to this agent, that only this agent understands and can execute) | |
549 | + # | |
550 | + # Default mode is MODE_ATTENTIVE (most active mode) | |
551 | + MODE_IDLE = "IDLE" | |
552 | + MODE_ROUTINE = "ROUTINE" | |
553 | + MODE_ATTENTIVE = "ATTENTIVE" | |
554 | + | |
555 | + ''' Moved to more central file : config.config_base | |
556 | + PYROS_DJANGO_BASE_DIR = Path("src/core/pyros_django") # pathlib | |
557 | + DEFAULT_CONFIG_FILE_NAME = "config_unit_simulunit1.xml" | |
558 | + CONFIG_DIR_NAME = "config" | |
559 | + ''' | |
560 | + | |
561 | + # My own ConfigPyros instance (Observatory Configuration) | |
562 | + _oc = None | |
563 | + | |
564 | + # Parameters from config file | |
565 | + # for /src/ | |
566 | + #_path_data = '../../config' | |
567 | + # for /src/core/pyros_django/ | |
568 | + #_path_data = '../../../../config' | |
569 | + # Path to config | |
570 | + _path_data = '' | |
571 | + _computer_alias = '' | |
572 | + _computer_description = '' | |
573 | + | |
574 | + # Current and next command to send | |
575 | + _cmdts: AgentCmd = None | |
576 | + _next_cmdts = None | |
577 | + | |
578 | + _agent_survey = None | |
579 | + _pending_commands = [] | |
580 | + | |
581 | + ''' | |
582 | + _current_device_cmd = None | |
583 | + _current_device_cmd_thread = None | |
584 | + ''' | |
585 | + | |
586 | + # List of agents I will send commands to | |
587 | + _my_client_agents_aliases = [] | |
588 | + _my_client_agents = {} | |
589 | + | |
590 | + _iter_num = None | |
591 | + | |
592 | + # Log object | |
593 | + _log = None | |
594 | + | |
595 | + # new obsconfig init for agent: | |
596 | + ##def __init__(self, RUN_IN_THREAD=True): | |
597 | + def __init__(self): | |
598 | + # Agent is by default in mode ATTENTIVE (most active mode) | |
599 | + self.mode = self.MODE_ATTENTIVE | |
600 | + log.addHandler(handler_filebyagent(logging.INFO, self.__class__.__name__)) | |
601 | + log.debug("start Agent init") | |
602 | + obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"] | |
603 | + path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"] | |
604 | + unit = os.environ["unit_name"] | |
605 | + oc = OBSConfig(obs_config_file_path,unit) | |
606 | + self.set_config(oc, obs_config_file_path, path_to_obs_config_folder, unit) | |
607 | + | |
608 | + self.name = self.__class__.__name__ | |
609 | + # set real unit name (the current unit used) | |
610 | + if unit == "": | |
611 | + unit = oc.unit_name | |
612 | + agent_name_from_config = self.get_config().get_agent_name_from_config(self.__class__.__name__) | |
613 | + if agent_name_from_config: | |
614 | + self.name = agent_name_from_config | |
615 | + self.unit = unit | |
616 | + print(f"Agent name : {self.name}") | |
617 | + print(f"Unit name : {self.unit}") | |
618 | + | |
619 | + # (EP) moved to AgentDevice | |
620 | + ###self._set_agent_device_aliases_from_config(self.name) | |
621 | + | |
622 | + self._set_mode_from_config(self.name) | |
623 | + log.debug("Step __init__") | |
624 | + | |
625 | + self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST) | |
626 | + ##self.RUN_IN_THREAD = RUN_IN_THREAD | |
627 | + self._set_status(self.STATUS_LAUNCH) | |
628 | + ####self._set_idle() | |
629 | + | |
630 | + # Create 1st survey if none | |
631 | + #tmp = AgentSurvey.objects.filter(name=self.name) | |
632 | + #if len(tmp) == 0: | |
633 | + #nb_agents = AgentSurvey.objects.filter(name=self.name).count() | |
634 | + #if nb_agents == 0: | |
635 | + if AgentSurvey.objects.filter(name=self.name).exists(): | |
636 | + self._agent_survey = AgentSurvey.objects.get(name=self.name) | |
637 | + else: | |
638 | + self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration=60, mode=self.mode, status=self.status, iteration=-1) | |
639 | + log.debug("Agent survey is" + str(self._agent_survey)) | |
640 | + #self.printd("Agent survey is", self._agent_survey) | |
641 | + | |
642 | + ("end Agent __init__") | |
643 | + | |
644 | + | |
645 | + #def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True): | |
646 | + #def __init__(self, config_filename:str=None, RUN_IN_THREAD=True, DEBUG_MODE=False): | |
647 | + # def __init__(self, config_filename:str=None, RUN_IN_THREAD=True): | |
648 | + | |
649 | + # ''' | |
650 | + # print('PYROS_ENV', PYROS_ENV) | |
651 | + # print('ROOT_DIR', ROOT_DIR) | |
652 | + # print('DOC_DIR', DOC_DIR) | |
653 | + # ''' | |
654 | + | |
655 | + # # Set logger | |
656 | + # ##pyros_logger.set_logger_config() | |
657 | + # ##logg = logging.getLogger('pyroslogger') | |
658 | + # log.addHandler(handler_filebyagent(logging.INFO, self.__class__.__name__)) | |
659 | + | |
660 | + # log.debug("start Agent init") | |
661 | + | |
662 | + # # SET CONFIG INSTANCE | |
663 | + | |
664 | + # # - OLD CONFIG | |
665 | + # log.debug(f'config file is {config_filename}') | |
666 | + # ##log.info('PYROS_ENV', config.PYROS_ENV) | |
667 | + # log.debug('PYROS_ENV' + config_old.PYROS_ENV) | |
668 | + # log.debug('ROOT_DIR' + config_old.ROOT_DIR) | |
669 | + # log.debug('DOC_DIR' + config_old.DOC_DIR) | |
670 | + # ##if config.is_dev_env(): print("DEV ENV") | |
671 | + # if config_old.is_dev_env(): log.debug("DEV ENV") | |
672 | + # if config_old.is_prod_env(): log.debug("PROD ENV") | |
673 | + # if config_old.is_debug(): log.debug("IN DEBUG MODE") | |
674 | + | |
675 | + # # - NEW CONFIG | |
676 | + # obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"] | |
677 | + # path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"] | |
678 | + # unit = os.environ["unit_name"] | |
679 | + # oc = OBSConfig(obs_config_file_path) | |
680 | + # self.set_config(oc, obs_config_file_path, path_to_obs_config_folder, unit) | |
681 | + # log.debug("Step __init__") | |
682 | + | |
683 | + # self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST) | |
684 | + # self.RUN_IN_THREAD = RUN_IN_THREAD | |
685 | + # self._set_status(self.STATUS_LAUNCH) | |
686 | + # self._set_idle() | |
687 | + | |
688 | + # self._set_agent_device_aliases_from_config(self.name) | |
689 | + # self._set_mode_from_config(self.name) | |
690 | + # #self.name = name | |
691 | + # self.name = self.__class__.__name__ | |
692 | + | |
693 | + | |
694 | + # ''' | |
695 | + # printd("*** ENVIRONMENT VARIABLE PYROS_DEBUG is:", os.environ.get('PYROS_DEBUG'), '***') | |
696 | + # ##self.DEBUG_MODE = DEBUG_MODE | |
697 | + # self.DEBUG_MODE = os.environ.get('PYROS_DEBUG', '0')=='1' | |
698 | + # ''' | |
699 | + # #self.DEBUG_MODE = config.PYROS_ENV | |
700 | + # ##self.log = LogPyros(self.name, AgentLogs) | |
701 | + # ##self.DEBUG_MODE = config.is_debug() | |
702 | + # self.DEBUG_MODE = config_old.is_debug() | |
703 | + # ##self.log.debug_level = DEBUG_MODE | |
704 | + # ''' | |
705 | + # # Default LOG level is INFO | |
706 | + # log_level = LogPyros.LOG_LEVEL_INFO # INFO | |
707 | + # self.log.set_global_log_level(LogPyros.LOG_LEVEL_DEBUG) if self.DEBUG_MODE else self.log.set_global_log_level(log_level) | |
708 | + # ''' | |
709 | + # #global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else self.PYROS_DEFAULT_GLOBAL_LOG_LEVEL | |
710 | + # ##global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else config.PYROS_DEFAULT_GLOBAL_LOG_LEVEL | |
711 | + # ##global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else config_old.PYROS_DEFAULT_GLOBAL_LOG_LEVEL | |
712 | + # ##self.log.set_global_log_level(global_log_level) | |
713 | + # ##self.printd("LOG LEVEL IS:", self.log.debug_level) | |
714 | + # ##self.print("LOG LEVEL IS:", self.log.get_global_log_level()) | |
715 | + | |
716 | + | |
717 | + # # ------------- OLD CONFIG ------------------- | |
718 | + # # Est-ce bien utile ??? | |
719 | + # # New way with PathLib | |
720 | + # # my_parent_abs_dir = Path(__file__).resolve().parent | |
721 | + # #TODO: on doit pouvoir faire mieux avec pathlib (sans utiliser str()) | |
722 | + # ##self._path_data = str( Path( str(my_parent_abs_dir).split(str(self.PYROS_DJANGO_BASE_DIR))[0] ) / self.CONFIG_DIR_NAME ) | |
723 | + # ##self._path_data = config.CONFIG_DIR | |
724 | + # # self._path_data = config_old.CONFIG_DIR | |
725 | + | |
726 | + # #self._set_mode(self.MODE_IDLE) | |
727 | + # # config_filename = self.get_config_filename(config_filename) | |
728 | + # #self.printd(f"*** Config file used is={config_filename}") | |
729 | + # # log.debug(f"*** Config file used is={config_filename}") | |
730 | + # # self.config = ConfigPyrosOld(config_filename) | |
731 | + # # if self.config.get_last_errno() != self.config.NO_ERROR: | |
732 | + # # raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") | |
733 | + | |
734 | + # #TODO: : à mettre dans la config | |
735 | + # ''' | |
736 | + # 'AgentDeviceTelescope1': 'AgentDeviceTelescopeGemini', | |
737 | + # 'AgentDeviceFilterSelector1': 'AgentDeviceSBIG', | |
738 | + # 'AgentDeviceShutter1': 'AgentDeviceSBIG', | |
739 | + # 'AgentDeviceSensor1': 'AgentDeviceSBIG', | |
740 | + # ''' | |
741 | + # #self._my_client_agents = {} | |
742 | + | |
743 | + # ### self._agent_logs = AgentLogs.objects.create(name=self.name, message="Step __init__") | |
744 | + # #self.printdb("Step __init__") | |
745 | + # log.debug("Step __init__") | |
746 | + | |
747 | + # self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST) | |
748 | + # self.RUN_IN_THREAD = RUN_IN_THREAD | |
749 | + # self._set_status(self.STATUS_LAUNCH) | |
750 | + # self._set_idle() | |
751 | + | |
752 | + # self._set_agent_device_aliases_from_config(self.name) | |
753 | + # self._set_mode_from_config(self.name) | |
754 | + # # TODO: remove | |
755 | + # #self._set_idle() | |
756 | + # self._set_active() | |
757 | + | |
758 | + # # Create 1st survey if none | |
759 | + # #tmp = AgentSurvey.objects.filter(name=self.name) | |
760 | + # #if len(tmp) == 0: | |
761 | + # #nb_agents = AgentSurvey.objects.filter(name=self.name).count() | |
762 | + # #if nb_agents == 0: | |
763 | + # if AgentSurvey.objects.filter(name=self.name).exists(): | |
764 | + # self._agent_survey = AgentSurvey.objects.get(name=self.name) | |
765 | + # else: | |
766 | + # self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration=60, mode=self.mode, status=self.status, iteration=-1) | |
767 | + # log.debug("Agent survey is" + str(self._agent_survey)) | |
768 | + # #self.printd("Agent survey is", self._agent_survey) | |
769 | + | |
770 | + # ("end Agent init") | |
771 | + | |
772 | + | |
773 | + | |
774 | + def set_config(self, oc: OBSConfig, obs_config_file_path: str, path_to_obs_config_folder: str, unit: str): | |
775 | + self._oc = { | |
776 | + 'config' : oc, | |
777 | + 'env' : [ | |
778 | + obs_config_file_path, | |
779 | + path_to_obs_config_folder, | |
780 | + unit | |
781 | + ] | |
782 | + } | |
783 | + ''' | |
784 | + config_env = { | |
785 | + obs_config_file_path: obs_config_file_path, | |
786 | + path_to_obs_config_folder: path_to_obs_config_folder, | |
787 | + unit: unit | |
788 | + } | |
789 | + ''' | |
790 | + | |
791 | + def get_config(self): return self._oc['config'] | |
792 | + | |
793 | + def get_config_env(self): return self._oc['env'] | |
794 | + | |
795 | + def show_config(self): | |
796 | + VERBOSE = False | |
797 | + | |
798 | + oc = self.get_config() | |
799 | + obs_config_file_path, path_to_obs_config_folder, unit = self.get_config_env() | |
800 | + #self.printd(obs_config_file_path) | |
801 | + log.debug(obs_config_file_path) | |
802 | + log.debug(path_to_obs_config_folder) | |
803 | + log.debug(unit) | |
804 | + #log.warning("petit warning bidon") | |
805 | + print("\n") | |
806 | + print("- Observatory:" + oc.get_obs_name()) | |
807 | + my_unit_name = oc.get_units_name()[0] | |
808 | + my_unit = (oc.get_units()[my_unit_name]) | |
809 | + | |
810 | + #print("- Unit description:", my_unit) | |
811 | + | |
812 | + if VERBOSE: | |
813 | + print("\n") | |
814 | + print("- Computers:", oc.get_computers()) | |
815 | + | |
816 | + print("\n") | |
817 | + print("- Active Computers:", oc.get_active_computers()) | |
818 | + | |
819 | + print("\n") | |
820 | + print("- Active Devices:", oc.get_active_devices()) | |
821 | + | |
822 | + print("\n") | |
823 | + print("- Unit:", my_unit_name) | |
824 | + VERBOSE and print(oc.get_unit_by_name(my_unit_name)) | |
825 | + | |
826 | + if VERBOSE: | |
827 | + print("\n") | |
828 | + print("- Unit topology:", oc.get_topology(my_unit_name)) | |
829 | + | |
830 | + print("\n") | |
831 | + print("- Unit active Agents:", oc.get_active_agents(my_unit_name)) | |
832 | + VERBOSE and print(oc.get_agents(my_unit_name)) | |
833 | + | |
834 | + print("\n") | |
835 | + print("- Unit Agents per computer:", oc.get_agents_per_computer(my_unit_name)) | |
836 | + | |
837 | + print("\n") | |
838 | + print("- Unit Agents per device:", oc.get_agents_per_device(my_unit_name)) | |
839 | + | |
840 | + if VERBOSE: | |
841 | + print("\n") | |
842 | + print("- Unit Channel groups:", oc.get_channel_groups(my_unit_name)) | |
843 | + | |
844 | + print("\n") | |
845 | + print("- Unit Channels:", oc.get_channels(my_unit_name)) | |
846 | + | |
847 | + #print("\n") | |
848 | + #print("- Unit/Channel info:", oc.get_channel_information(my_unit_name, 'OpticalChannel_up')) | |
849 | + | |
850 | + if VERBOSE: | |
851 | + print("\n") | |
852 | + print("- Unit Components agents:", oc.get_components_agents(my_unit_name)) | |
853 | + | |
854 | + if VERBOSE: | |
855 | + print("\n") | |
856 | + print("- Unit database:", oc.get_database_for_unit(my_unit_name)) | |
857 | + | |
858 | + print("\n") | |
859 | + print("- Devices names:", oc.get_devices_names()) | |
860 | + if VERBOSE: | |
861 | + print("\n") | |
862 | + print("- Devices names & files:", oc.get_devices_names_and_file()) | |
863 | + print("\n") | |
864 | + print("- Devices:", oc.get_devices()) | |
865 | + | |
866 | + print("\n") | |
867 | + | |
868 | + | |
869 | + | |
870 | + | |
871 | + | |
872 | + | |
873 | + def get_config_filename(self, config_filename: str): | |
874 | + if not config_filename: | |
875 | + #config_filename = self.DEFAULT_CONFIG_FILE_NAME | |
876 | + ##config_filename = config.DEFAULT_CONFIG_FILE_NAME | |
877 | + config_filename = config_old.DEFAULT_CONFIG_FILE_NAME | |
878 | + # If config file name is RELATIVE (i.e. without path, just the file name) | |
879 | + # => give it an absolute path (and remove "src/agent/" from it) | |
880 | + if config_filename == os.path.basename(config_filename): | |
881 | + ##config_filename = os.path.join(config.CONFIG_DIR, config_filename) | |
882 | + config_filename = os.path.join(config_old.CONFIG_DIR, config_filename) | |
883 | + ''' | |
884 | + # Build abs path including current agent dir | |
885 | + config_filename = os.path.abspath(self.CONFIG_DIR_NAME + os.sep + config_filename) | |
886 | + # Remove "src/agent_name/" from abs dir : | |
887 | + # (1) Remove "src/core/pyros_django/" | |
888 | + config_filename = config_filename.replace(str(self.PYROS_DJANGO_BASE_DIR)+os.sep, os.sep) | |
889 | + # (2) Remove "agent_name/" | |
890 | + #TODO: bidouille, faire plus propre | |
891 | + config_filename = config_filename.replace(os.sep+"agent"+os.sep, os.sep) | |
892 | + config_filename = config_filename.replace(os.sep+"monitoring"+os.sep, os.sep) | |
893 | + ''' | |
894 | + return os.path.normpath(config_filename) | |
895 | + | |
896 | + def __repr__(self): | |
897 | + return "I am agent " + self.name | |
898 | + | |
899 | + def __str__(self): | |
900 | + return self.__repr__() | |
901 | + #return "I am agent " + self.name | |
902 | + | |
903 | + # Normal print | |
904 | + ##def print(self, *args, **kwargs): self.log.print(*args, **kwargs) | |
905 | + """ | |
906 | + if args: | |
907 | + self.printd(f"({self.name}): ", *args, **kwargs) | |
908 | + else: | |
909 | + self.printd() | |
910 | + """ | |
911 | + """ | |
912 | + # DEBUG print shortcut | |
913 | + ##def printd(self, *args, **kwargs): self.log.printd(*args, **kwargs) | |
914 | + #if DEBUG: self.printd(d(*args, **kwargs) | |
915 | + def log_d(self, *args, **kwargs): self.log.log_d(*args, **kwargs) | |
916 | + def log_i(self, *args, **kwargs): self.log.log_i(*args, **kwargs) | |
917 | + def log_w(self, *args, **kwargs): self.log.log_w(*args, **kwargs) | |
918 | + def log_e(self, *args, **kwargs): self.log.log_e(*args, **kwargs) | |
919 | + def log_c(self, *args, **kwargs): self.log.log_c(*args, **kwargs) | |
920 | + def printdb(self, *args, **kwargs): self.log.db( *args, **kwargs) | |
921 | + """ | |
922 | + | |
923 | + | |
924 | + | |
925 | + def _get_real_agent_name(self, agent_alias_name:str)->str: | |
926 | + #self.printd("key is", agent_alias_name) | |
927 | + ''' | |
928 | + if not self._my_client_agents: return agent_alias_name | |
929 | + return self._my_client_agents[agent_alias_name] | |
930 | + ''' | |
931 | + return self._my_client_agents.get(agent_alias_name) | |
932 | + | |
933 | + | |
934 | + | |
935 | + def run(self, nb_iter:int=None, FOR_REAL:bool=True): | |
936 | + """ | |
937 | + FOR_REAL: set to False if you don't want Agent to send commands to devices but just print messages without really doing anything | |
938 | + """ | |
939 | + | |
940 | + ("in run()") | |
941 | + #return | |
942 | + | |
943 | + # TEST MODE ONLY | |
944 | + # IF in test mode but with REAL devices (no SIMULATOR), delete all dangerous commands from the test commands list scenario: | |
945 | + self._TEST_prepare() | |
946 | + | |
947 | + #self._DO_EXIT = False | |
948 | + | |
949 | + # No agent specific cmd currently running (thread) | |
950 | + self.AGENT_SPECIFIC_CMD_RUNNING = False | |
951 | + | |
952 | + # No Routine process (before or after) currently running (thread) | |
953 | + self.ROUTINE_PROCESS_RUNNING = False | |
954 | + | |
955 | + ################ | |
956 | + # RESTART loop # | |
957 | + ###############@ | |
958 | + # REPEAT UNTIL not DO_RESTART_LOOP | |
959 | + self.DO_RESTART_LOOP = True | |
960 | + while self.DO_RESTART_LOOP: | |
961 | + # By default, no restart after exit from main loop | |
962 | + self.DO_RESTART_LOOP = False | |
963 | + | |
964 | + self.start_time = time.time() | |
965 | + #log.debug("on est ici: " + os.getcwd()) | |
966 | + | |
967 | + self._load_config() | |
968 | + | |
969 | + self.print_TEST_MODE() | |
970 | + | |
971 | + self.init() | |
972 | + ''' testing log: | |
973 | + self.log_e("ERROR") | |
974 | + self.log_c("FATAL critical ERROR") | |
975 | + ''' | |
976 | + #self.log_w("WARNING", "watch your step !") | |
977 | + #log.warning("WARNING"+ "watch your step !") | |
978 | + | |
979 | + # Avoid blocking on false "running" commands | |
980 | + # (old commands that stayed with "running" status when agent was killed) | |
981 | + AgentCmd.delete_commands_with_running_status_for_agent(self.name) | |
982 | + | |
983 | + self._iter_num = 1 | |
984 | + | |
985 | + ############# | |
986 | + # MAIN loop # | |
987 | + ############@ | |
988 | + self.DO_MAIN_LOOP = True | |
989 | + while self.DO_MAIN_LOOP: | |
990 | + try: | |
991 | + self._main_loop(nb_iter,FOR_REAL) | |
992 | + #if not self.DO_MAIN_LOOP: break | |
993 | + except KeyboardInterrupt: # CTRL-C | |
994 | + # In case of CTRL-C, kill the current thread (process) before dying (in error) | |
995 | + #log.info("CTRL-C Interrupted, I kill the current thread (process) before exiting (if exists)") | |
996 | + log.info("CTRL-C Interrupted, trying to stop cleanly") | |
997 | + #self._kill_running_device_cmd_if_exists("USER_CTRLC") | |
998 | + #self.do_things_before_exit("USER_CTRLC") | |
999 | + self._cleanup_before_exit("USER_CTRLC") | |
1000 | + exit(1) | |
1001 | + | |
1002 | + # TEST mode only | |
1003 | + self._TEST_test_results() | |
1004 | + #if self._DO_EXIT: exit(0) | |
1005 | + | |
1006 | + | |
1007 | + | |
1008 | + #def _kill_running_device_cmd_if_exists(self, abort_cmd_sender): | |
1009 | + # to be overriden by subclass | |
1010 | + def do_things_before_exit(self, stopper_agent_name=None): | |
1011 | + pass | |
1012 | + | |
1013 | + | |
1014 | + def _main_loop(self, nb_iter:int=None, FOR_REAL:bool=True): | |
1015 | + | |
1016 | + self._main_loop_start(nb_iter) | |
1017 | + #if not self.DO_MAIN_LOOP: return | |
1018 | + | |
1019 | + self._reload_config_if_changed() # only if changed | |
1020 | + | |
1021 | + # better to do this in a subclass | |
1022 | + #self.show_config() | |
1023 | + | |
1024 | + # Log this agent status (update my current mode and status in DB) | |
1025 | + self._log_agent_status() | |
1026 | + | |
1027 | + #self.printd("====== START COMMMANDS PROCESSING ======") | |
1028 | + | |
1029 | + # SIMU | |
1030 | + #self.send_cmd_to("AgentScheduler", "do_replan") | |
1031 | + | |
1032 | + # ROUTINE process BEFORE | |
1033 | + # To be specified by subclass | |
1034 | + if not self.IS_IDLE(): self._routine_process_before() | |
1035 | + #self.printd("I am IDLE, so I bypass the routine_process (do not send any new command)") | |
1036 | + | |
1037 | + # Processing the next pending command if exists | |
1038 | + # If Agent general level command like (DO_RESTART, DO_EXIT, DO_ABORT) | |
1039 | + # => will set DO_MAIN_LOOP=False and/or DO_RESTART_LOOP=True | |
1040 | + print() | |
1041 | + print() | |
1042 | + log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n') | |
1043 | + try : | |
1044 | + cmd = self._process_next_command_if_exists() | |
1045 | + except (AgentCmdUnimplementedException, AgentCmdBadArgsException, UnknownCmdException) as e : | |
1046 | + print(e) | |
1047 | + log.error(f"EXCEPTION on Agent command '{e.cmd.name}'") | |
1048 | + if type(e) is UnknownCmdException: | |
1049 | + e.cmd.set_as_skipped("EXCEPTION: unknown command") | |
1050 | + else : | |
1051 | + e.cmd.set_as_processed("EXCEPTION: command known but unimplemented or bad args") | |
1052 | + self._cleanup_before_exit() | |
1053 | + raise | |
1054 | + log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n") | |
1055 | + | |
1056 | + # only if in TEST mode | |
1057 | + self._TEST_check_cmd_res_and_status(cmd) | |
1058 | + | |
1059 | + if not self.DO_MAIN_LOOP: return | |
1060 | + | |
1061 | + ''' | |
1062 | + # if restart, exit this loop to restart from beginning | |
1063 | + if self.DO_RESTART or self.DO_EXIT or self.DO_ABORT: | |
1064 | + self.DO_MAIN_LOOP = False | |
1065 | + return | |
1066 | + ''' | |
1067 | + | |
1068 | + if not self.IS_IDLE(): self._routine_process_after() | |
1069 | + | |
1070 | + # TEST MODE only : execute test routine_process always (even if IDLE) in order to (always) send next command from test scenario | |
1071 | + self._TEST_test_routine_process() | |
1072 | + | |
1073 | + #self.printd("====== END COMMMANDS PROCESSING ======") | |
1074 | + | |
1075 | + #self.waitfor(self.mainloop_waittime) | |
1076 | + | |
1077 | + self._main_loop_end() | |
1078 | + | |
1079 | + self._iter_num += 1 | |
1080 | + | |
1081 | + | |
1082 | + | |
1083 | + def _main_loop_start(self, nb_iter:int=None): | |
1084 | + | |
1085 | + for i in range(3): print() | |
1086 | + #self.printd("-"*80) | |
1087 | + log.info("*"*73) | |
1088 | + log.info("*"*20 + f" MAIN LOOP ITERATION {self._iter_num} (START) " + "*"*20) | |
1089 | + log.info("*"*73 + '\n') | |
1090 | + #self.print(f"Iteration {self._iter_num}") | |
1091 | + | |
1092 | + # EXIT because of nb of iterations ? | |
1093 | + if nb_iter is not None: | |
1094 | + # Bad number of iterations or nb iterations reached => exit | |
1095 | + if nb_iter <= 0 or nb_iter < self._iter_num: | |
1096 | + log.info(f"Exit because number of iterations asked ({nb_iter}) has been reached") | |
1097 | + self.DO_MAIN_LOOP = False | |
1098 | + return | |
1099 | + | |
1100 | + # Temporizing (delay) : Wait a random number of sec before starting iteration | |
1101 | + # (to avoid to busy to much the processor) | |
1102 | + # (also to let another agent having the chance to send a me command) | |
1103 | + if self._DELAY_NB_SEC : | |
1104 | + #random_waiting_sec = random.randint(0,5) | |
1105 | + log.info(f"Waiting {self._DELAY_NB_SEC} sec (random) before starting new iteration...") | |
1106 | + #time.sleep(random_waiting_sec) | |
1107 | + self.waitfor(self._DELAY_NB_SEC) | |
1108 | + | |
1109 | + # (Test only) | |
1110 | + # EXIT because max duration reached ? | |
1111 | + if self.TEST_MODE and self.TEST_MAX_DURATION_SEC and (time.time()-self.start_time > self.TEST_MAX_DURATION_SEC): | |
1112 | + log.info("Exit because of max duration set to ", self.TEST_MAX_DURATION_SEC, "s") | |
1113 | + #self._kill_running_device_cmd_if_exists(self.name) | |
1114 | + self._cleanup_before_exit(self.name) | |
1115 | + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: self._TEST_test_results() | |
1116 | + self.DO_MAIN_LOOP = False | |
1117 | + return | |
1118 | + | |
1119 | + self._set_status(self.STATUS_MAIN_LOOP) | |
1120 | + self.show_mode_and_status() | |
1121 | + | |
1122 | + self.main_loop_start() | |
1123 | + | |
1124 | + # To be overriden by subclass | |
1125 | + def main_loop_start(self): | |
1126 | + pass | |
1127 | + | |
1128 | + def _main_loop_end(self): | |
1129 | + self.main_loop_end() | |
1130 | + log.info("*"*20 + " MAIN LOOP ITERATION (END) " + "*"*20) | |
1131 | + #self.do_log(LOG_DEBUG, "Ending main loop iteration") | |
1132 | + | |
1133 | + # To be overriden by subclass | |
1134 | + def main_loop_end(self): | |
1135 | + pass | |
1136 | + | |
1137 | + | |
1138 | + # To be overriden by subclass (AgentDevice) | |
1139 | + def process_device_level_cmd(self): | |
1140 | + pass | |
1141 | + ''' | |
1142 | + log.info("(DEVICE LEVEL CMD)") | |
1143 | + try: | |
1144 | + self.exec_device_cmd_if_possible(cmd) | |
1145 | + except (UnimplementedGenericCmdException) as e: | |
1146 | + #except (UnknownGenericCmdException, UnimplementedGenericCmdException, UnknownNativeCmdException) as e: | |
1147 | + log.e(f"EXCEPTION caught by {type(self).__name__} (from Agent mainloop) for command '{cmd.name}'", e) | |
1148 | + log.e("Thus ==> ignore this command") | |
1149 | + cmd.set_result(e) | |
1150 | + #cmd.set_as_killed_by(type(self).__name__) | |
1151 | + cmd.set_as_skipped() | |
1152 | + #raise | |
1153 | + ''' | |
1154 | + | |
1155 | + | |
1156 | + | |
1157 | + def _process_next_command_if_exists(self)->AgentCmd: | |
1158 | + ''' Processing the next pending command if exists ''' | |
1159 | + | |
1160 | + #print() | |
1161 | + #print() | |
1162 | + #log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n') | |
1163 | + | |
1164 | + | |
1165 | + # Purge commands (every N iterations, starting from 1st, delete old commands) | |
1166 | + N=5 | |
1167 | + if ((self._iter_num-1) % N) == 0: | |
1168 | + log.info("Purging expired commands if exists") | |
1169 | + #AgentCmd.purge_old_commands_for_agent(self.name) | |
1170 | + self._purge_old_commands_sent_to_me() | |
1171 | + | |
1172 | + # Get next command and process it (if exists) | |
1173 | + cmd = self._get_next_valid_and_not_running_command() | |
1174 | + self.CURRENT_CMD = cmd | |
1175 | + #self._set_status(self.STATUS_GENERAL_PROCESS) | |
1176 | + #if cmd: self.command_process(cmd) | |
1177 | + | |
1178 | + # SIMU | |
1179 | + #cmd = "do_replan" | |
1180 | + #self.send_cmd_to("AgentScheduler", "do_replan") | |
1181 | + | |
1182 | + # No new command => nothing to do | |
1183 | + if not cmd: return cmd | |
1184 | + | |
1185 | + log.info('-'*6) | |
1186 | + log.info('-'*6 + " RECEIVED NEW COMMAND TO PROCESS: ") | |
1187 | + log.info('-'*6 + str(cmd)) | |
1188 | + if self.is_in_test_mode() and hasattr(self._cmdts,"expected_res"): | |
1189 | + log.info(f"*** (with expected result : " + str(self._cmdts.expected_res) + ')') | |
1190 | + log.info('-'*6) | |
1191 | + | |
1192 | + cmd.set_read_time() | |
1193 | + | |
1194 | + # CASE 1 - AGENT GENERAL command | |
1195 | + # (DO_RESTART, DO_EXIST, DO_ABORT, DO_ABORT_COMMAND, DO_FLUSH_COMMANDS, ...) | |
1196 | + # This processing can set DO_MAIN_LOOP and DO_RESTART_LOOP to False | |
1197 | + if self._is_agent_general_cmd(cmd): | |
1198 | + try: | |
1199 | + self._process_agent_general_cmd(cmd) | |
1200 | + except AgentCmdUnimplementedException as e: | |
1201 | + #print(e) | |
1202 | + #cmd.set_as_skipped("ERROR: Unimplemented Agent General command") | |
1203 | + #self._cleanup_before_exist() | |
1204 | + # This exception is managed at higher level : | |
1205 | + raise | |
1206 | + #except ValueError as e: | |
1207 | + except AgentCmdBadArgsException as e: | |
1208 | + #print(e) | |
1209 | + #cmd.set_as_skipped("ERROR: Bad Argument(s)") | |
1210 | + #self._cleanup_before_exit() | |
1211 | + # This exception is managed at higher level : | |
1212 | + raise | |
1213 | + #raise AgentCmdBadArgsException(cmd.name) | |
1214 | + # Must I stop or restart ? | |
1215 | + if cmd.name in ('do_stop','do_restart_loop','do_exit','do_abort') : | |
1216 | + self.DO_MAIN_LOOP = False | |
1217 | + if cmd.name == 'do_abort': | |
1218 | + self._abort_current_running_cmd_if_exists() | |
1219 | + self._cleanup_before_exit() | |
1220 | + if cmd.name != 'do_restart_loop': | |
1221 | + self.DO_RESTART_LOOP = False | |
1222 | + return cmd | |
1223 | + | |
1224 | + # CASE 2 - AGENT SPECIFIC command | |
1225 | + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel) | |
1226 | + if self._is_agent_specific_cmd(cmd): | |
1227 | + #log.info("(AGENT LEVEL CMD)") | |
1228 | + if not self.IS_ATTENTIVE(): | |
1229 | + cmd.set_as_skipped("Skipped because I am not ATTENTIVE") | |
1230 | + return cmd | |
1231 | + try: | |
1232 | + self._process_agent_specific_cmd(cmd) | |
1233 | + #self._exec_agent_cmd(cmd) | |
1234 | + #except AttributeError as e: | |
1235 | + except (AgentCmdUnimplementedException, AgentCmdBadArgsException) as e: | |
1236 | + ##print(e) | |
1237 | + #self.log_e(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e) | |
1238 | + #self.log_e("Thus => I ignore this command...") | |
1239 | + ##log.error(f"EXCEPTION: Agent specific command '{cmd.name}' unknown (not implemented as a function) :", e) | |
1240 | + #log.e("Thus => I ignore this command...") | |
1241 | + #cmd.set_result("ERROR: UNIMPLEMENTED AGENT SPECIFIC COMMAND", False) | |
1242 | + #cmd.set_as_pending() | |
1243 | + ##cmd.set_as_skipped("ERROR: UNIMPLEMENTED AGENT SPECIFIC COMMAND") | |
1244 | + ##self._cleanup_before_exit() | |
1245 | + ##raise AgentCmdUnimplementedException(cmd.name) | |
1246 | + # These exceptions are managed at higher level : | |
1247 | + raise | |
1248 | + return cmd | |
1249 | + | |
1250 | + # CASE 3 - OTHER level command (DEVsICE level, only for AgentDevice) | |
1251 | + if self.is_device_level_cmd(cmd): | |
1252 | + self.process_device_level_cmd(cmd) | |
1253 | + return cmd | |
1254 | + | |
1255 | + # CASE 4 - INVALID COMMAND | |
1256 | + #raise Exception("INVALID COMMAND: " + cmd.name) | |
1257 | + log.warning("******************************************************") | |
1258 | + log.warning("*************** ERROR: INVALID COMMAND (UNKNOWN) ***************") | |
1259 | + log.warning("******************************************************") | |
1260 | + #log.warning("Thus => I ignore this command...") | |
1261 | + #cmd.set_result("ERROR: INVALID COMMAND") | |
1262 | + ##cmd.set_as_skipped("ERROR: INVALID AGENT COMMAND") | |
1263 | + ##self._cleanup_before_exit() | |
1264 | + ##raise UnknownCmdException(cmd.name) | |
1265 | + raise UnknownCmdException(cmd) | |
1266 | + | |
1267 | + #print() | |
1268 | + #log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n") | |
1269 | + | |
1270 | + | |
1271 | + | |
1272 | + | |
1273 | + def _abort_current_running_cmd_if_exists(self): | |
1274 | + pass | |
1275 | + | |
1276 | + def _cleanup_before_exit(self, stopper_agent_name:str=None): | |
1277 | + if not stopper_agent_name: stopper_agent_name = self.name | |
1278 | + self._set_status(self.STATUS_EXIT) | |
1279 | + self._log_agent_status() | |
1280 | + | |
1281 | + log.info("Trying to stop cleanly") | |
1282 | + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :") | |
1283 | + #commands = AgentCmd.get_commands_sent_to_agent(self.name) | |
1284 | + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name) | |
1285 | + AgentCmd.show_commands(commands, True) | |
1286 | + self.do_flush_commands() | |
1287 | + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results() | |
1288 | + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: | |
1289 | + self._TEST_test_results() | |
1290 | + #self._DO_EXIT=True | |
1291 | + #exit(0) | |
1292 | + | |
1293 | + self.do_things_before_exit(stopper_agent_name) | |
1294 | + | |
1295 | + | |
1296 | + def print_TEST_MODE(self): | |
1297 | + if self.TEST_MODE: | |
1298 | + log.debug("[IN TEST MODE]") | |
1299 | + log.info("Flush previous commands to be sure to start in clean state") | |
1300 | + AgentCmd.delete_pending_commands_for_agent(self.name) | |
1301 | + else: | |
1302 | + log.debug("[IN NORMAL MODE]") | |
1303 | + self.TEST_MAX_DURATION_SEC=None | |
1304 | + | |
1305 | + | |
1306 | + def _purge_old_commands_sent_to_me(self): | |
1307 | + AgentCmd.purge_old_commands_sent_to_agent(self.name) | |
1308 | + | |
1309 | + | |
1310 | + def _routine_process_before(self): | |
1311 | + """ | |
1312 | + Routine processing BEFORE processing received commands. | |
1313 | + | |
1314 | + IMPORTANT : | |
1315 | + this processing must be as SHORT as possible, | |
1316 | + so that the agent can quickly read its received commands and process them | |
1317 | + | |
1318 | + This is a command or set of processings that this agent does or commands that it sends regularly, | |
1319 | + at each iteration | |
1320 | + """ | |
1321 | + print() | |
1322 | + print() | |
1323 | + log.info("*"*10+ " ROUTINE PROCESSING BEFORE (START) "+ "*"*10+ '\n') | |
1324 | + | |
1325 | + self._set_status(self.STATUS_ROUTINE_PROCESS) | |
1326 | + self.routine_process_before_body() | |
1327 | + print() | |
1328 | + log.info("*"*10 + " ROUTINE PROCESSING BEFORE (END) "+ "*"*10) | |
1329 | + | |
1330 | + | |
1331 | + def _routine_process_after(self): | |
1332 | + """ | |
1333 | + Routine processing AFTER processing received commands. | |
1334 | + | |
1335 | + This processing can be longer than the "before" one above, | |
1336 | + as the agent has already processed its received commands, | |
1337 | + but not too long anyway, otherwise it will take too long before the next iteration starts... | |
1338 | + | |
1339 | + This is a command or set of processings that this agent does or commands that it sends regularly, | |
1340 | + at each iteration | |
1341 | + """ | |
1342 | + print() | |
1343 | + print() | |
1344 | + log.info("*"*10+ " ROUTINE PROCESSING AFTER (START) "+ "*"*10+ '\n') | |
1345 | + | |
1346 | + self._set_status(self.STATUS_ROUTINE_PROCESS) | |
1347 | + self.routine_process_after_body() | |
1348 | + print() | |
1349 | + log.info("*"*10 + " ROUTINE PROCESSING AFTER (END) "+ "*"*10) | |
1350 | + | |
1351 | + | |
1352 | + # To be overridden by subclasses | |
1353 | + def routine_process_before_body(self): | |
1354 | + pass | |
1355 | + | |
1356 | + # To be overridden by subclasses | |
1357 | + def routine_process_after_body(self): | |
1358 | + #if self.TEST_MODE: self._TEST_test_routine_process() | |
1359 | + pass | |
1360 | + | |
1361 | + """ | |
1362 | + def purge_commands(self): | |
1363 | + ### | |
1364 | + Delete commands (which I am recipient of) older than COMMANDS_PEREMPTION_HOURS (like 48h) | |
1365 | + ATTENTION !!! EXCEPT the RUNNING command !!! | |
1366 | + | |
1367 | + NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc) | |
1368 | + ### | |
1369 | + | |
1370 | + self.printd("Looking for old commands to purge...") | |
1371 | + ### | |
1372 | + COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS) | |
1373 | + #self.printd("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW) | |
1374 | + old_commands = AgentCmd.objects.filter( | |
1375 | + # only commands for me | |
1376 | + recipient = self.name, | |
1377 | + # only pending commands | |
1378 | + sender_deposit_time__lt = COMMAND_PEREMPTION_DATE_FROM_NOW, | |
1379 | + ) | |
1380 | + ### | |
1381 | + old_commands = AgentCmd.get_old_commands_for_agent(self.name) | |
1382 | + if old_commands.exists(): | |
1383 | + self.printd("Found old commands to delete:") | |
1384 | + for cmd in old_commands: self.printd(cmd) | |
1385 | + old_commands.delete() | |
1386 | + """ | |
1387 | + | |
1388 | + def waitfor(self, nbsec:float=2.0): | |
1389 | + ''' | |
1390 | + # thread | |
1391 | + if self._current_device_cmd_thread and self.RUN_IN_THREAD: | |
1392 | + self._current_device_cmd_thread.wait(nbsec) | |
1393 | + # process (or main thread) | |
1394 | + else: | |
1395 | + time.sleep(nbsec) | |
1396 | + ''' | |
1397 | + log.info(f"Now, waiting for {nbsec} second(s)...") | |
1398 | + time.sleep(nbsec) | |
1399 | + | |
1400 | + def sleep(self, nbsec): | |
1401 | + self.waitfor(nbsec) | |
1402 | + | |
1403 | + def _set_status(self, status:str): | |
1404 | + #self.printd(f"[{status}] (switching from status {self.status})") | |
1405 | + log.debug(f"[{status}]") | |
1406 | + self.status = status | |
1407 | + return False | |
1408 | + | |
1409 | + def _set_mode(self, mode:str): | |
1410 | + #self.printd(f"Switching from mode {self.mode} to mode {mode}") | |
1411 | + log.info(f"[NEW MODE {mode}]") | |
1412 | + self.mode = mode | |
1413 | + | |
1414 | + # Test mode | |
1415 | + def IS_IDLE(self): return self.mode == self.MODE_IDLE | |
1416 | + def IS_ROUTINE(self): return self.mode == self.MODE_ROUTINE | |
1417 | + def IS_ATTENTIVE(self): return self.mode == self.MODE_ATTENTIVE | |
1418 | + | |
1419 | + def show_mode_and_status(self): | |
1420 | + log.info(f"CURRENT MODE is {self.mode} (status is {self.status})") | |
1421 | + | |
1422 | + def get_specifics_cmd(self): | |
1423 | + specific_commands = "" | |
1424 | + for index, command_tuple in enumerate(self.AGENT_SPECIFIC_COMMANDS): | |
1425 | + specific_commands += f"{command_tuple[0]}" | |
1426 | + if index != len(self.AGENT_SPECIFIC_COMMANDS)-1: | |
1427 | + specific_commands += ";" | |
1428 | + return specific_commands | |
1429 | + | |
1430 | + def get_mode(self): | |
1431 | + return self.mode | |
1432 | + | |
1433 | + def get_state(self): | |
1434 | + return self.status | |
1435 | + | |
1436 | + def set_idle(self): | |
1437 | + self._set_mode(self.MODE_IDLE) | |
1438 | + def set_routine(self): | |
1439 | + self._set_mode(self.MODE_ROUTINE) | |
1440 | + def set_attentive(self): | |
1441 | + self._set_mode(self.MODE_ATTENTIVE) | |
1442 | + | |
1443 | + | |
1444 | + def die(self): | |
1445 | + self._set_status(self.STATUS_EXIT) | |
1446 | + | |
1447 | + """ | |
1448 | + suspend/resume | |
1449 | + """ | |
1450 | + def suspend(self): | |
1451 | + """ | |
1452 | + TODO: | |
1453 | + Mode IDLE (doit rester à l'écoute d'un resume, | |
1454 | + et doit continuer à alimenter les tables pour informer de son état via tables agents_logs, | |
1455 | + et lire table agents_command pour reprendre via resume, | |
1456 | + et update la table agents_survey pour donner son status "idle" | |
1457 | + """ | |
1458 | + self._set_idle() | |
1459 | + return True | |
1460 | + | |
1461 | + def resume(self): | |
1462 | + """ | |
1463 | + Quit suspend() mode | |
1464 | + """ | |
1465 | + self._set_active() | |
1466 | + return True | |
1467 | + | |
1468 | + | |
1469 | + ''' | |
1470 | + (EP) moved to AgentDevice | |
1471 | + def _set_agent_device_aliases_from_config(self, agent_alias): | |
1472 | + for a in self._my_client_agents_aliases: | |
1473 | + # TODO: activer | |
1474 | + ##self._my_client_agents[a] = self.config.get_paramvalue(a,'general','real_agent_device_name') | |
1475 | + pass | |
1476 | + ''' | |
1477 | + | |
1478 | + # new config (obsconfig) | |
1479 | + def _set_mode_from_config(self, agent_name): | |
1480 | + # all agent are active ? | |
1481 | + | |
1482 | + #mode = self.MODE_ACTIVE | |
1483 | + mode = self.MODE_ATTENTIVE | |
1484 | + self._set_mode(mode) | |
1485 | + return True | |
1486 | + # old config | |
1487 | + # def _set_mode_from_config(self, agent_name): | |
1488 | + # # --- Get the startmode of the AgentX | |
1489 | + # modestr = self.config.get_paramvalue(agent_name,'general','startmode') | |
1490 | + # if self.config.get_last_errno() != self.config.NO_ERROR: | |
1491 | + # raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") | |
1492 | + # if (modestr == None): | |
1493 | + # return True | |
1494 | + # # --- Set the mode according the startmode value | |
1495 | + # mode = self.MODE_IDLE | |
1496 | + # if modestr.upper() == 'RUN': | |
1497 | + # mode = self.MODE_ACTIVE | |
1498 | + # self._set_mode(mode) | |
1499 | + # return True | |
1500 | + | |
1501 | + | |
1502 | + def set_delay(self, delay_nb_sec:int): | |
1503 | + self._DELAY_NB_SEC = delay_nb_sec | |
1504 | + | |
1505 | + """ | |
1506 | + ======================================================================================= | |
1507 | + Generic methods that may be specialized (overriden) by subclasses (except if private) | |
1508 | + ======================================================================================= | |
1509 | + """ | |
1510 | + | |
1511 | + # To be overridden by subclasses | |
1512 | + def init(self): | |
1513 | + #log.debug("*** Initializing... ***") | |
1514 | + log.info("*** INITIALIZING... ***") | |
1515 | + self._set_status(self.STATUS_INIT) | |
1516 | + if self.TEST_MODE: self.set_delay(2) | |
1517 | + | |
1518 | + | |
1519 | + def _reload_config_if_changed(self): | |
1520 | + self._load_config() | |
1521 | + | |
1522 | + def _load_config(self): | |
1523 | + """ | |
1524 | + TODO: | |
1525 | + only si date fichier xml changée => en RAM, un objet Config avec méthodes d'accès, appelle le parser de AK (classe Config.py indépendante) | |
1526 | + """ | |
1527 | + ''' | |
1528 | + # SETUP | |
1529 | + try: | |
1530 | + self.config = get_object_or_404(Config, id=1) | |
1531 | + # By default, set mode to SCHEDULER (False = REMOTE, which should never be the default) | |
1532 | + self.config.global_mode = True | |
1533 | + self.config.save() | |
1534 | + # self.config = Config.objects.get(pk=1) | |
1535 | + # self.config = Config.objects.get()[0] | |
1536 | + except Exception as e: | |
1537 | + # except Config.ObjectDoesNotExist: | |
1538 | + self.printd("Config read (or write) exception", str(e)) | |
1539 | + return -1 | |
1540 | + ''' | |
1541 | + log.debug("Loading the config file...") | |
1542 | + | |
1543 | + # - NEW CONFIG | |
1544 | + self.get_config().load(self.get_config_env()[0]) | |
1545 | + | |
1546 | + | |
1547 | + # - OLD CONFIG | |
1548 | + # self.config.load() | |
1549 | + # if self.config.get_last_errno() != self.config.NO_ERROR: | |
1550 | + # raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") | |
1551 | + # if not self.config.is_config_contents_changed(): | |
1552 | + # return | |
1553 | + | |
1554 | + # === display informations | |
1555 | + # --- Get the assembly aliases of the unit | |
1556 | + # assembled_mount_aliases = [] | |
1557 | + # assembled_channel_aliases = [] | |
1558 | + # assembled_aliases = [] | |
1559 | + # unit_alias = self.config.get_aliases('unit')[0] | |
1560 | + # params = self.config.get_params(unit_alias) | |
1561 | + # for param in params: | |
1562 | + # if param['section']=="assembly" and param['key']=="alias": | |
1563 | + # assembled_aliases.append(param['value']) | |
1564 | + #self.printd(f"Unit {unit_alias} is the assembly of {assembled_aliases}") | |
1565 | + | |
1566 | + # log.debug("--------- Components of the unit -----------") | |
1567 | + # log.debug("Configuration file is {}".format(self.config.get_configfile())) | |
1568 | + # alias = self.config.get_aliases('unit')[0] | |
1569 | + # namevalue = self.config.get_paramvalue(alias,'unit','description') | |
1570 | + # log.debug(f"Unit alias is {alias}. Description is {namevalue}:") | |
1571 | + # unit_subtags = self.config.get_unit_subtags() | |
1572 | + # for unit_subtag in unit_subtags: | |
1573 | + # aliases = self.config.get_aliases(unit_subtag) | |
1574 | + # for alias in aliases: | |
1575 | + # namevalue = self.config.get_paramvalue(alias,unit_subtag,'description') | |
1576 | + # log.debug(f"- {unit_subtag} alias is {alias}. Description is {namevalue}") | |
1577 | + # # --- fill the list of mount and channel assembled | |
1578 | + # if alias in assembled_aliases: | |
1579 | + # if unit_subtag=="mount": | |
1580 | + # assembled_mount_aliases.append(alias) | |
1581 | + # elif unit_subtag=="channel": | |
1582 | + # assembled_channel_aliases.append(alias) | |
1583 | + | |
1584 | + # log.debug("--------- Assembly of the unit -----------") | |
1585 | + # log.debug(f"Assembled mount aliases: {assembled_mount_aliases}") | |
1586 | + # log.debug(f"Assembled channel aliases: {assembled_channel_aliases}") | |
1587 | + | |
1588 | + # --- Get the home of the mount[0] | |
1589 | + # mount_alias = assembled_mount_aliases[0] | |
1590 | + # home = self.config.get_paramvalue(mount_alias,'MountPointing','home') | |
1591 | + | |
1592 | + # log.debug("------------------------------------------") | |
1593 | + # hostname = socket.gethostname() | |
1594 | + # self._computer_alias = '' | |
1595 | + # unit_subtag = 'computer' | |
1596 | + # aliases = self.config.get_aliases(unit_subtag) | |
1597 | + # for alias in aliases: | |
1598 | + # log.debug("alias" + alias) | |
1599 | + # value = self.config.get_paramvalue(alias,'local','hostname') | |
1600 | + # log.debug("value" + value) | |
1601 | + # if value == hostname: | |
1602 | + # log.debug("value" + value) | |
1603 | + # self._computer_alias = alias | |
1604 | + # value = self.config.get_paramvalue(alias,unit_subtag,'description') | |
1605 | + # self._computer_description = value | |
1606 | + # value = self.config.get_paramvalue(alias,'path','data') | |
1607 | + # # Overrides default value | |
1608 | + # self._path_data = value | |
1609 | + # break | |
1610 | + # log.debug(f"hostname = {hostname}") | |
1611 | + # log.debug(f"path_data = {self._path_data}") | |
1612 | + # log.debug(f"home = {home}") | |
1613 | + # log.debug("------------------------------------------") | |
1614 | + | |
1615 | + # --- update the log parameters | |
1616 | + ##self.log.path_data = self._path_data | |
1617 | + ##print("new self.log.path_data is", self.log.path_data) | |
1618 | + ##self.log.set_global_path_data(self._path_data) | |
1619 | + ##self.printd("new self.log.global_path_data is", self.log.get_global_path_data()) | |
1620 | + ##self.log.home = home | |
1621 | + | |
1622 | + | |
1623 | + #def update_survey(self): | |
1624 | + def _log_agent_status(self): | |
1625 | + """ | |
1626 | + Save (update) this agent current mode and status in DB | |
1627 | + """ | |
1628 | + "Updating the agent survey database table..." | |
1629 | + #self.printd("- fetching table line for agent", self.name) | |
1630 | + # only necessary when using process (not necessary with threads) | |
1631 | + #with transaction.atomic(): | |
1632 | + #self._agent_survey = AgentSurvey.objects.get(name=self.name) | |
1633 | + self._agent_survey.mode = self.mode | |
1634 | + self._agent_survey.status = self.status | |
1635 | + self._agent_survey.iteration = self._iter_num | |
1636 | + self._agent_survey.save() | |
1637 | + #self._agent_survey.save(update_fields=["mode", "status"]) | |
1638 | + | |
1639 | + | |
1640 | + """ | |
1641 | + def send_command(self, cmd_name): | |
1642 | + recipient_agent = self.name if self.TEST_COMMANDS_DEST=="myself" else self.TEST_COMMANDS_DEST | |
1643 | + AgentCmd.objects.create(sender=self.name, recipient=recipient_agent, name=cmd_name) | |
1644 | + """ | |
1645 | + #def send_command(self, to_agent, cmd_type, cmd_name, cmd_args=None): | |
1646 | + def send_cmd_to(self, to_agent:str, cmd_name:str, cmd_args:str=None, validity:int=None, timeout:int=None): | |
1647 | + """ | |
1648 | + #ex: send_command(“AgentX”,”GENERIC”,”EVAL”,“3+4”) | |
1649 | + ex: send_command(“AgentX”,"EVAL”,“3+4”) | |
1650 | + """ | |
1651 | + #return AgentCmd.send_cmd_from_to(self.name, self._get_real_agent_name_for_alias(to_agent), cmd_name, cmd_args) | |
1652 | + #cmd = self.create_cmd_for(to_agent, cmd_name, cmd_args, validity, timeout).send() | |
1653 | + cmd = self.create_cmd_for(to_agent, cmd_name, cmd_args, validity).send() | |
1654 | + #cmd.send() | |
1655 | + return cmd | |
1656 | + | |
1657 | + def create_cmd_for(self, to_agent:str, cmd_name:str, cmd_args:str=None, validity:int=None) -> AgentCmd: | |
1658 | + | |
1659 | + ''' | |
1660 | + real_agent_name = self._get_real_agent_name(to_agent) | |
1661 | + real_cmd_name = cmd_name | |
1662 | + if '.' in real_agent_name: | |
1663 | + real_agent_name, component_name = real_agent_name.split('.') | |
1664 | + real_cmd_name = component_name+'.'+cmd_name | |
1665 | + return AgentCmd.create(self.name, real_agent_name, real_cmd_name, cmd_args) | |
1666 | + try: | |
1667 | + real_agent_name = self._get_real_agent_name(to_agent) | |
1668 | + except KeyError as e: | |
1669 | + ''' | |
1670 | + return AgentCmd.create(self.name, to_agent, cmd_name, cmd_args, validity) | |
1671 | + ''' | |
1672 | + real_agent_name = self._get_real_agent_name(to_agent) | |
1673 | + if not real_agent_name: | |
1674 | + return AgentCmd.create(self.name, to_agent, cmd_name, cmd_args) | |
1675 | + # log.e("UNKNOWN AgentDevice ALIAS", to_agent) | |
1676 | + # #self.log_e("Exception raised", e) | |
1677 | + # log.e(f"=> Thus, I do not send this command '{cmd_name}'") | |
1678 | + # return None | |
1679 | + return AgentCmd.create(self.name, real_agent_name, cmd_name, cmd_args) | |
1680 | + ''' | |
1681 | + ''' | |
1682 | + return AgentCmd( | |
1683 | + sender=self.name, | |
1684 | + recipient=self._get_real_agent_name_for_alias(recipient_agent_alias_name), | |
1685 | + name=cmd_name | |
1686 | + ) | |
1687 | + ''' | |
1688 | + | |
1689 | + def _get_next_valid_and_not_running_command(self) -> AgentCmd: | |
1690 | + """ | |
1691 | + Return next VALID (not expired) command (read from the DB command table) | |
1692 | + which is relevant to this agent. | |
1693 | + Commands are read in chronological order | |
1694 | + """ | |
1695 | + self._set_status(self.STATUS_GET_NEXT_COMMAND) | |
1696 | + log.info("Looking for a new command to process (sent by another agent):") | |
1697 | + | |
1698 | + # 1) Get all pending commands for me (return if None) | |
1699 | + # Not sure this is necessary to do it in a transaction, | |
1700 | + # but there might be a risk | |
1701 | + # that a command status is modified while we are reading... | |
1702 | + with transaction.atomic(): | |
1703 | + self._pending_commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name) | |
1704 | + commands = self._pending_commands | |
1705 | + if not commands.exists(): | |
1706 | + log.info("<None>") | |
1707 | + return None | |
1708 | + "Current pending (or running) commands are (time ordered):" | |
1709 | + AgentCmd.show_commands(commands) | |
1710 | + | |
1711 | + # 2) Check if a PRIORITY command is in the list (even at the very end), | |
1712 | + # and if so return this command | |
1713 | + # (must be still valid and not yet running) | |
1714 | + for cmd in commands: | |
1715 | + #if cmd.name in ("do_exit", "do_abort", "do_flush_commands"): break | |
1716 | + #if cmd.name in ("do_exit", "do_abort"): break | |
1717 | + if cmd.is_agent_general_priority_cmd(): | |
1718 | + if cmd.is_running(): | |
1719 | + return None | |
1720 | + if cmd.is_expired(): | |
1721 | + cmd.set_as_outofdate() | |
1722 | + return None | |
1723 | + return cmd | |
1724 | + ''' | |
1725 | + #if cmd.name in ("do_exit", "do_abort", "do_flush_commands"): | |
1726 | + if cmd.name in ("do_exit", "do_abort"): | |
1727 | + if cmd.is_running(): | |
1728 | + return None | |
1729 | + if cmd.is_expired(): | |
1730 | + cmd.set_as_outofdate() | |
1731 | + return None | |
1732 | + return cmd | |
1733 | + ''' | |
1734 | + | |
1735 | + # 3) If first (oldest) command is currently running | |
1736 | + # (status CMD_RUNNING), then do nothing and return | |
1737 | + """ | |
1738 | + cmd_executing = Command.objects.filter( | |
1739 | + # only commands for me | |
1740 | + recipient = self.name, | |
1741 | + # only pending commands | |
1742 | + recipient_status_code = Command.CMD_STATUS_CODES.CMD_RUNNING, | |
1743 | + ).first() | |
1744 | + #if cmd_executing.exists(): | |
1745 | + if cmd_executing: | |
1746 | + """ | |
1747 | + #cmd = commands[0] | |
1748 | + cmd = commands.first() | |
1749 | + | |
1750 | + if cmd.is_expired(): | |
1751 | + cmd.set_as_outofdate() | |
1752 | + return None | |
1753 | + | |
1754 | + if cmd.is_running(): | |
1755 | + #self.printd(f"There is currently a running command ({cmd_executing.first().name}), so I do nothing (wait for end of execution)") | |
1756 | + log.info(f"There is currently a running command ({cmd.name})") | |
1757 | + """ | |
1758 | + # Check that this command is not expired | |
1759 | + if cmd.is_expired(): | |
1760 | + self.printd("But this command is expired, so set its status to OUTOFDATE, and go on") | |
1761 | + cmd_executing.set_as_outofdate() | |
1762 | + else: | |
1763 | + """ | |
1764 | + log.info(f"Thus, I won't execute any new command until this command execution is finished") | |
1765 | + # TODO: kill si superieur a MAX_EXEC_TIME | |
1766 | + return None | |
1767 | + | |
1768 | + ''' | |
1769 | + # 4) Tag all expired commands | |
1770 | + for cmd in commands: | |
1771 | + if cmd.is_expired(): cmd.set_as_outofdate() | |
1772 | + # break at 1st "valid" command (not expired) | |
1773 | + else: break | |
1774 | + | |
1775 | + # 5) If no more commands to process, return None | |
1776 | + if cmd.is_expired(): return None | |
1777 | + ''' | |
1778 | + | |
1779 | + # 6) Current cmd must now be a valid (not expired) and PENDING one, | |
1780 | + # so return it for execution | |
1781 | + #self.printd(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}") | |
1782 | + #self.printd(f"Starting processing of this command") | |
1783 | + return cmd | |
1784 | + | |
1785 | + | |
1786 | + ''' | |
1787 | + #def _exec_agent_general_cmd(self, cmd:Command): | |
1788 | + def _exec_agent_cmd(self, cmd:AgentCmd): | |
1789 | + | |
1790 | + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...") | |
1791 | + log.info(f"Starting execution of an AGENT LEVEL cmd...") | |
1792 | + | |
1793 | + # Update read time to say that the command has been READ | |
1794 | + cmd.set_read_time() | |
1795 | + cmd.set_as_running() | |
1796 | + | |
1797 | + # SPECIFIC command (only related to me, not to any agent) | |
1798 | + if self._is_agent_specific_cmd(cmd): | |
1799 | + log.info("(Agent level SPECIFIC cmd)") | |
1800 | + # Execute method self."cmd.name"() | |
1801 | + # This can raise an exception (caught by this method caller) | |
1802 | + self.exec_cmd_from_its_name(cmd) | |
1803 | + #'' | |
1804 | + try: | |
1805 | + except AttributeError as e: | |
1806 | + self.printd(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e) | |
1807 | + self.printd("Thus => I ignore this command...") | |
1808 | + cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND") | |
1809 | + cmd.set_as_pending() | |
1810 | + cmd.set_as_skipped() | |
1811 | + return | |
1812 | + #'' | |
1813 | + cmd.set_result("Agent level SPECIFIC cmd done") | |
1814 | + cmd.set_as_processed() | |
1815 | + log.info("...Agent level SPECIFIC cmd has been executed") | |
1816 | + return | |
1817 | + | |
1818 | + # GENERAL command (related to any agent) | |
1819 | + log.info("(Agent level GENERAL CMD)") | |
1820 | + _,cmd_name,cmd_args = cmd.get_full_name_parts() | |
1821 | + #cmd_name, cmd_args = cmd.tokenize() | |
1822 | + #if cmd.name == "set_state:active": | |
1823 | + #elif cmd.name == "set_state:idle": | |
1824 | + if cmd_name == "set_state": | |
1825 | + if not cmd_args: raise ValueError() | |
1826 | + state = cmd_args[0] | |
1827 | + if state == "active": self._set_active() | |
1828 | + if state == "idle": self._set_idle() | |
1829 | + cmd.set_result("I am now " + state) | |
1830 | + #time.sleep(1) | |
1831 | + self.waitfor(1) | |
1832 | + elif cmd_name in ("do_flush_commands"): | |
1833 | + "flush_commands received: Delete all pending commands" | |
1834 | + AgentCmd.delete_pending_commands_for_agent(self.name) | |
1835 | + cmd.set_result('DONE') | |
1836 | + elif cmd_name in ("do_abort", "do_exit", "do_restart_init"): | |
1837 | + #self.printd("Current pending commands are:") | |
1838 | + #Command.show_commands(self._pending_commands) | |
1839 | + log.info("Aborting current executing command if exists:") | |
1840 | + #self._kill_running_device_cmd_if_exists(cmd.sender) | |
1841 | + self.do_things_before_exit(cmd.sender) | |
1842 | + if cmd_name == "do_restart_init": | |
1843 | + log.info("restart_init received: Restarting from init()") | |
1844 | + self._DO_RESTART=True | |
1845 | + elif cmd.name == "do_exit": | |
1846 | + self._DO_EXIT=True | |
1847 | + cmd.set_result('SHOULD BE DONE NOW') | |
1848 | + else: | |
1849 | + #'' | |
1850 | + name = cmd.name | |
1851 | + args = None | |
1852 | + if " " in name: name,args = name.split() | |
1853 | + if name == "do_eval": | |
1854 | + if args==None: raise(ValueError) | |
1855 | + cmd.set_result(eval(args)) | |
1856 | + #'' | |
1857 | + if cmd_name == "do_eval": | |
1858 | + if not cmd_args: raise ValueError() | |
1859 | + cmd.set_result(eval(cmd_args)) | |
1860 | + | |
1861 | + cmd.set_as_processed() | |
1862 | + log.info("...Agent level GENERAL cmd has been executed") | |
1863 | + | |
1864 | + # If cmd is "do_exit", kill myself (without any question, this is an order soldier !) | |
1865 | + # This "do_exit" should normally kill any current thread (to be checked...) | |
1866 | + if cmd.name == "do_exit": | |
1867 | + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :") | |
1868 | + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name) | |
1869 | + AgentCmd.show_commands(commands, True) | |
1870 | + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results() | |
1871 | + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: | |
1872 | + self._TEST_test_results() | |
1873 | + #self._DO_EXIT=True | |
1874 | + #exit(0) | |
1875 | + ''' | |
1876 | + | |
1877 | + | |
1878 | + def _process_agent_general_cmd(self, cmd:AgentCmd): | |
1879 | + # GENERAL command (related to any agent) | |
1880 | + | |
1881 | + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...") | |
1882 | + log.info(f"Starting execution of an AGENT GENERAL cmd...") | |
1883 | + | |
1884 | + # Update read time to say that the command has been READ | |
1885 | + #cmd.set_read_time() | |
1886 | + cmd.set_as_running() | |
1887 | + | |
1888 | + log.info("(Agent level GENERAL CMD)") | |
1889 | + cmd_name,cmd_args = cmd.name_and_args | |
1890 | + #cmd_name, cmd_args = cmd.tokenize() | |
1891 | + #if cmd.name == "set_state:active": | |
1892 | + #elif cmd.name == "set_state:idle": | |
1893 | + | |
1894 | + # Default result (null) | |
1895 | + result = None | |
1896 | + | |
1897 | + #if cmd_name in ("do_abort", "do_exit", "do_restart_init"): | |
1898 | + if cmd_name in ("do_abort", "do_exit", "do_restart_loop"): | |
1899 | + #self.printd("Current pending commands are:") | |
1900 | + #Command.show_commands(self._pending_commands) | |
1901 | + log.info("Stopping/Aborting current executing command if exists:") | |
1902 | + #self._kill_running_device_cmd_if_exists(cmd.sender) | |
1903 | + ''' | |
1904 | + self.do_things_before_exit(cmd.sender) | |
1905 | + if cmd_name == "do_restart_init": | |
1906 | + log.info("restart_init received: Restarting from init()") | |
1907 | + self._DO_RESTART=True | |
1908 | + elif cmd.name == "do_exit": | |
1909 | + self._DO_EXIT=True | |
1910 | + cmd.set_result('SHOULD BE DONE NOW') | |
1911 | + ''' | |
1912 | + result = "RESTARTING" if cmd_name == "do_restart_loop" else "STOPPING" | |
1913 | + | |
1914 | + elif cmd_name == "get_state": | |
1915 | + result = "I am now " + self.get_state() | |
1916 | + | |
1917 | + elif cmd_name == "get_mode": | |
1918 | + result = "MODE = " + self.get_mode() | |
1919 | + | |
1920 | + elif cmd_name == "set_mode": | |
1921 | + #if not cmd_args: raise ValueError() | |
1922 | + if not cmd_args: raise AgentCmdBadArgsException(cmd) | |
1923 | + mode = cmd_args[0] | |
1924 | + if mode == "IDLE": self.set_idle() | |
1925 | + elif mode == "ROUTINE": self.set_routine() | |
1926 | + elif mode == "ATTENTIVE": self.set_attentive() | |
1927 | + else: raise AgentCmdBadArgsException(cmd) | |
1928 | + #cmd.set_result("I am now " + state) | |
1929 | + result = "MODE = " + mode | |
1930 | + #time.sleep(1) | |
1931 | + #self.waitfor(1) | |
1932 | + | |
1933 | + #elif cmd_name in ("do_flush_commands"): | |
1934 | + elif cmd_name == "do_flush_commands": | |
1935 | + "flush_commands received: Delete all pending commands" | |
1936 | + self.do_flush_commands() | |
1937 | + #cmd.set_result('DONE') | |
1938 | + result = "FLUSH DONE" | |
1939 | + | |
1940 | + elif cmd_name == "do_eval": | |
1941 | + #if not cmd_args: raise ValueError() | |
1942 | + if not cmd_args: raise AgentCmdBadArgsException(cmd) | |
1943 | + #cmd.set_result(eval(cmd_args)) | |
1944 | + #result = eval(cmd_args) | |
1945 | + result = self.do_eval(cmd_args[0]) | |
1946 | + elif cmd_name == "get_specifics_cmd": | |
1947 | + result = self.get_specifics_cmd() | |
1948 | + | |
1949 | + cmd.set_as_processed(result) | |
1950 | + log.info("...Agent level GENERAL cmd has been executed") | |
1951 | + | |
1952 | + ''' | |
1953 | + # If cmd is "do_exit", kill myself (without any question, this is an order soldier !) | |
1954 | + # This "do_exit" should normally kill any current thread (to be checked...) | |
1955 | + if cmd.name == "do_exit": | |
1956 | + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :") | |
1957 | + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name) | |
1958 | + AgentCmd.show_commands(commands, True) | |
1959 | + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results() | |
1960 | + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: | |
1961 | + self._TEST_test_results() | |
1962 | + #self._DO_EXIT=True | |
1963 | + #exit(0) | |
1964 | + ''' | |
1965 | + | |
1966 | + | |
1967 | + | |
1968 | + | |
1969 | + def _process_agent_specific_cmd(self, cmd:AgentCmd): | |
1970 | + | |
1971 | + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...") | |
1972 | + log.info(f"Starting execution of an AGENT level SPECIFIC cmd...") | |
1973 | + | |
1974 | + # Update read time to say that the command has been READ | |
1975 | + #cmd.set_read_time(False) | |
1976 | + cmd.set_as_running() | |
1977 | + | |
1978 | + log.info("(Agent SPECIFIC cmd)") | |
1979 | + # Execute method self."cmd.name"() | |
1980 | + # This can raise an exception (caught by this method caller) | |
1981 | + try: | |
1982 | + res = self.exec_cmd_from_its_name(cmd) | |
1983 | + except (AgentCmdUnimplementedException, AgentCmdBadArgsException) as e: | |
1984 | + # These exceptions are managed at higher level : | |
1985 | + raise | |
1986 | + ''' | |
1987 | + except AttributeError as e: | |
1988 | + self.printd(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e) | |
1989 | + self.printd("Thus => I ignore this command...") | |
1990 | + cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND") | |
1991 | + cmd.set_as_pending() | |
1992 | + cmd.set_as_skipped() | |
1993 | + return | |
1994 | + ''' | |
1995 | + #cmd.set_result("Agent level SPECIFIC cmd done") | |
1996 | + #res = res if res else "Agent SPECIFIC cmd done" | |
1997 | + cmd.set_as_processed(res) | |
1998 | + log.info("...Agent SPECIFIC cmd has been executed") | |
1999 | + | |
2000 | + | |
2001 | + | |
2002 | + | |
2003 | + ''' | |
2004 | + def do_log(self): | |
2005 | + #"" | |
2006 | + log à 2 endroits ou 1 seul | |
2007 | + - in file | |
2008 | + - in db | |
2009 | + #"" | |
2010 | + self.printd("Logging data...") | |
2011 | + ''' | |
2012 | + | |
2013 | + def exec_cmd_from_its_name(self, cmd:AgentCmd): | |
2014 | + methods_list = [method for method in dir(self) if callable(getattr(self, method))] | |
2015 | + #print(methodsList) | |
2016 | + func = cmd.name | |
2017 | + if func not in methods_list: raise AgentCmdUnimplementedException(cmd) | |
2018 | + #for arg in cmd.args: print(arg) | |
2019 | + #print(cmd.args) | |
2020 | + #print(*cmd.args) | |
2021 | + ''' | |
2022 | + try: | |
2023 | + if not cmd.args: | |
2024 | + # equivalent to calling self.func() | |
2025 | + return getattr(self, func)() | |
2026 | + else: | |
2027 | + args = [] | |
2028 | + # Convert all args to their real type | |
2029 | + for arg in cmd.args: | |
2030 | + #print(arg) | |
2031 | + #try: | |
2032 | + # Evaluate arg only if it is not a word (letters) | |
2033 | + if not arg[0].isalpha(): | |
2034 | + arg = ast.literal_eval(arg) | |
2035 | + #except ValueError as e: newarg = arg | |
2036 | + args.append(arg) | |
2037 | + #print(args) | |
2038 | + # equivalent to calling self.func(*cmd.args) | |
2039 | + return getattr(self, func)(*args) | |
2040 | + ''' | |
2041 | + args = [] | |
2042 | + # Convert all args to their real type | |
2043 | + for arg in cmd.args: | |
2044 | + #print(arg) | |
2045 | + #try: | |
2046 | + # Evaluate arg only if it is not a word (letters) | |
2047 | + if not arg[0].isalpha(): | |
2048 | + arg = ast.literal_eval(arg) | |
2049 | + #except ValueError as e: newarg = arg | |
2050 | + args.append(arg) | |
2051 | + try: | |
2052 | + # equivalent to calling self.func(*cmd.args) | |
2053 | + return getattr(self, func)(*args) | |
2054 | + except (TypeError, AttributeError, ValueError) as e: | |
2055 | + #raise e | |
2056 | + # "from None" pour ne pas afficher l'exception AttributeError (car interne) | |
2057 | + raise AgentCmdBadArgsException(cmd) from None | |
2058 | + #print("I know this specific command but it is not yet implemented : ", func) | |
2059 | + ##tb = sys.exc_info()[2] | |
2060 | + ##raise AgentCmdUnimplementedException(cmd).with_traceback(tb) | |
2061 | + ##raise AgentCmdUnimplementedException(cmd).with_traceback(None) | |
2062 | + | |
2063 | + def _is_agent_level_cmd(self, cmd:AgentCmd): | |
2064 | + return self._is_agent_general_cmd(cmd) or self._is_agent_specific_cmd(cmd) | |
2065 | + | |
2066 | + def _is_agent_general_cmd(self, cmd:AgentCmd): | |
2067 | + return cmd.is_agent_general_cmd() | |
2068 | + | |
2069 | + | |
2070 | + | |
2071 | + ''' | |
2072 | + def _exec_agent_cmd(self, cmd:Command): | |
2073 | + # AGENT "GENERAL LEVEL" command | |
2074 | + # => I process it directly without asking my DC | |
2075 | + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel) | |
2076 | + if cmd.is_agent_level_general_cmd(): | |
2077 | + self.printd("********** -- AGENT LEVEL GENERAL CMD *********") | |
2078 | + self._exec_agent_general_cmd(cmd) | |
2079 | + | |
2080 | + # AGENT "SPECIFIC LEVEL" command | |
2081 | + # => I process it directly without asking my DC | |
2082 | + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel) | |
2083 | + #elif self._is_agent_level_specific_cmd(cmd): | |
2084 | + else: | |
2085 | + self.printd("********** -- AGENT LEVEL SPECIFIC CMD *********") | |
2086 | + self._exec_agent_specific_cmd(cmd) | |
2087 | + ''' | |
2088 | + | |
2089 | + def _is_agent_specific_cmd(self, cmd:AgentCmd): | |
2090 | + #return cmd.name in self.AGENT_SPECIFIC_COMMANDS | |
2091 | + #return (cmd.name,) in self.AGENT_SPECIFIC_COMMANDS | |
2092 | + for (cmd_name,timeout) in self.AGENT_SPECIFIC_COMMANDS: | |
2093 | + if cmd.name == cmd_name : return True | |
2094 | + | |
2095 | + ''' | |
2096 | + def _exec_agent_specific_cmd(self, cmd:Command): | |
2097 | + # Execute method self."cmd.name"() | |
2098 | + self.exec_cmd_from_its_name(cmd) | |
2099 | + ''' | |
2100 | + | |
2101 | + def is_in_test_mode(self): | |
2102 | + return self.TEST_MODE | |
2103 | + | |
2104 | + | |
2105 | + | |
2106 | + ### | |
2107 | + # ================================================================================================ | |
2108 | + # AGENT GENERAL FUNCTIONS | |
2109 | + # ================================================================================================ | |
2110 | + ### | |
2111 | + | |
2112 | + def do_eval(self, eval_str:str): | |
2113 | + return eval(eval_str) | |
2114 | + | |
2115 | + def do_flush_commands(self): | |
2116 | + AgentCmd.delete_pending_commands_for_agent(self.name) | |
2117 | + | |
2118 | + def do_exec_commands(self, what:str): | |
2119 | + # - Temporaly stop execution of new commands (let them accumulate) | |
2120 | + if what == "stop": pass | |
2121 | + # - Resume execution of commands (accumulated since "do_stop_exec_cmd") | |
2122 | + if what == "resume": pass | |
2123 | + # NOT PRIO | |
2124 | + if what == "noprio": pass | |
2125 | + # Bad arg | |
2126 | + raise AgentCmdBadArgsException(self.CURRENT_CMD) | |
2127 | + | |
2128 | + # Stop currently running cmd or routine | |
2129 | + def do_stop_current(self, what:str): | |
2130 | + if what == "cmd": self.do_stop_current_cmd() | |
2131 | + if what == "routine": self.do_stop_current_routine() | |
2132 | + if what == "both": | |
2133 | + self.do_stop_current_cmd() | |
2134 | + self.do_stop_current_routine() | |
2135 | + # Bad arg | |
2136 | + raise AgentCmdBadArgsException(self.CURRENT_CMD) | |
2137 | + def do_stop_current_cmd(self): | |
2138 | + pass | |
2139 | + def do_stop_current_routine(self): | |
2140 | + pass | |
2141 | + | |
2142 | + def do_exit(self): self.do_stop("asap"); | |
2143 | + def do_abort(self): self.do_stop("now"); | |
2144 | + # Stop agent asap or now | |
2145 | + def do_stop(self, when:str): | |
2146 | + # PRIO | |
2147 | + if when == "asap": | |
2148 | + pass | |
2149 | + if when == "now": | |
2150 | + pass | |
2151 | + # NOT PRIO | |
2152 | + if when == "noprio": | |
2153 | + pass | |
2154 | + # Bad arg | |
2155 | + raise AgentCmdBadArgsException(self.CURRENT_CMD) | |
2156 | + | |
2157 | + | |
2158 | + # Stop agent asap or now | |
2159 | + def do_restart_loop(self, when:str): | |
2160 | + # PRIO | |
2161 | + if when == "asap": | |
2162 | + pass | |
2163 | + if when == "now": | |
2164 | + pass | |
2165 | + # NOT PRIO | |
2166 | + if when == "noprio": | |
2167 | + pass | |
2168 | + # Bad arg | |
2169 | + raise AgentCmdBadArgsException(self.CURRENT_CMD) | |
2170 | + | |
2171 | + | |
2172 | + | |
2173 | + | |
2174 | + ### | |
2175 | + # ================================================================================================ | |
2176 | + # AGENT SPECIFIC FUNCTIONS | |
2177 | + # (here just to serve as examples) | |
2178 | + # ================================================================================================ | |
2179 | + ### | |
2180 | + | |
2181 | + #def do_specific1(self, arg1:int, arg2:int=2, arg3:int=3) -> int: | |
2182 | + #def do_specific1(self, arg1:int, arg2:int=2, arg3:float=3.1, arg4:list=[1,2,3]) -> float: | |
2183 | + def do_specific1(self, | |
2184 | + arg1:int, | |
2185 | + arg2:int, | |
2186 | + arg3:float=3.1, | |
2187 | + arg4:str='toto', | |
2188 | + arg5:Tuple[int,str,int]=(1,'toto',3), | |
2189 | + #arg5:Tuple[int,int,int]=(1,2,3), | |
2190 | + arg6:List[int]=[] | |
2191 | + ) -> float: | |
2192 | + ''' | |
2193 | + arg1 = int(arg1) | |
2194 | + arg2 = int(arg2) | |
2195 | + arg3 = float(arg3) | |
2196 | + arg5 = ast.literal_eval(arg5) | |
2197 | + print(arg5[1]) | |
2198 | + arg6 = ast.literal_eval(arg6) | |
2199 | + ''' | |
2200 | + print(arg4) | |
2201 | + res = arg1 + arg2 + arg3 + arg5[0] + arg5[2] | |
2202 | + if arg6: res += arg6[0] | |
2203 | + return res | |
2204 | + | |
2205 | + #def set_specific2(self, arg1:str, arg2:int): pass | |
2206 | + | |
2207 | + def do_specific3(self): | |
2208 | + pass | |
2209 | + | |
2210 | + | |
2211 | + ### | |
2212 | + # ================================================================================================ | |
2213 | + # DEVICE SPECIFIC FUNCTIONS (abstract for Agent, overriden and implemented by AgentDevice) | |
2214 | + # ================================================================================================ | |
2215 | + ### | |
2216 | + | |
2217 | + # To be overriden by subclass (AgentDevice...) | |
2218 | + # @abstract | |
2219 | + def is_device_level_cmd(self, cmd): | |
2220 | + return False | |
2221 | + | |
2222 | + ''' | |
2223 | + # to be overriden by subclass (AgentDevice) | |
2224 | + # @abstract | |
2225 | + def exec_device_cmd_if_possible(self, cmd:AgentCmd): | |
2226 | + pass | |
2227 | + | |
2228 | + # TO BE OVERRIDEN by subclass (AgentDevice) | |
2229 | + # @abstract | |
2230 | + def exec_device_cmd(self, cmd:AgentCmd): | |
2231 | + #self.exec_cmd_from_its_name(cmd) | |
2232 | + pass | |
2233 | + ''' | |
2234 | + | |
2235 | + | |
2236 | + | |
2237 | + | |
2238 | + | |
2239 | + """ | |
2240 | + ================================================================= | |
2241 | + TEST DEDICATED FUNCTIONS | |
2242 | + ================================================================= | |
2243 | + """ | |
2244 | + | |
2245 | + def _set_debug_mode(self, mode:bool): | |
2246 | + self.DEBUG_MODE=mode | |
2247 | + | |
2248 | + def _set_with_simulator(self, mode:bool): | |
2249 | + self.WITH_SIMULATOR=mode | |
2250 | + | |
2251 | + def _set_test_mode(self, mode:bool): | |
2252 | + self.TEST_MODE = mode | |
2253 | + if self.TEST_MODE: log.info("in TEST MODE") | |
2254 | + | |
2255 | + def _TEST_get_next_command_to_send(self)->AgentCmd: | |
2256 | + #cmd_full_name, validity, res_expected, after_status = next(self.TEST_COMMANDS, (None,None,None,None)) | |
2257 | + cmd_full_name, validity, expected_res, expected_status = next(self.TEST_COMMANDS) | |
2258 | + #print(expected_final_status) | |
2259 | + #print(cmd_full_name, res_expected) | |
2260 | + #return cmd_name | |
2261 | + if cmd_full_name is None: return None | |
2262 | + # Remove excessive spaces | |
2263 | + import re | |
2264 | + cmd_full_name = re.sub(r"\s+", " ", cmd_full_name).strip() | |
2265 | + if ' ' not in cmd_full_name: raise Exception('Command is malformed:', cmd_full_name) | |
2266 | + agent_recipient, cmd_name_and_args = cmd_full_name.split(' ', 1) | |
2267 | + if agent_recipient == 'self': agent_recipient = self.name | |
2268 | + cmd_name, cmd_args = cmd_name_and_args, None | |
2269 | + if ' ' in cmd_name_and_args: cmd_name,cmd_args = cmd_name_and_args.split(' ', 1) | |
2270 | + cmd = self.create_cmd_for(agent_recipient, cmd_name, cmd_args, validity) | |
2271 | + cmd.expected_res = expected_res | |
2272 | + cmd.expected_status = expected_status | |
2273 | + # If no cmd created (because of error, bad AgentDevice name), call again this method for next cmd | |
2274 | + #if cmd is None: return self._TEST_get_next_command_to_send() | |
2275 | + return cmd | |
2276 | + | |
2277 | + """ | |
2278 | + def simulator_send_next_command(self): | |
2279 | + #self._current_test_cmd = "set_state:idle" if self._current_test_cmd=="set_state:active" else "set_state:active" | |
2280 | + #if self._nb_test_cmds == 4: self._current_test_cmd = "do_exit" | |
2281 | + cmd_name = next(self.TEST_COMMANDS, None) | |
2282 | + #self.printd("next cmd is ", cmd_name) | |
2283 | + if cmd_name is None: return | |
2284 | + #Command.objects.create(sender=self.name, recipient=self.name, name=cmd_name) | |
2285 | + recipient_agent = self.name if self.TEST_COMMANDS_DEST=="myself" else self.TEST_COMMANDS_DEST | |
2286 | + Command.objects.create(sender=self.name, recipient=recipient_agent, name=cmd_name) | |
2287 | + #time.sleep(1) | |
2288 | + #self._TEST_current_cmd_idx += 1 | |
2289 | + #self._nb_test_cmds += 1 | |
2290 | + """ | |
2291 | + | |
2292 | + def _TEST_check_cmd_res_and_status(self, cmd:AgentCmd): | |
2293 | + | |
2294 | + if not self.is_in_test_mode(): return | |
2295 | + if not cmd: return | |
2296 | + | |
2297 | + log.debug("*** CHECK ***") | |
2298 | + #if hasattr(self._cmdts,'expected_res'): | |
2299 | + | |
2300 | + log.debug(cmd.result) | |
2301 | + log.debug(self._cmdts.expected_res) | |
2302 | + if cmd.is_executed() and self._cmdts.expected_res: | |
2303 | + assert(cmd.result == self._cmdts.expected_res) | |
2304 | + | |
2305 | + log.debug(cmd.state) | |
2306 | + log.debug(self._cmdts.expected_status) | |
2307 | + #if hasattr(self._cmdts,'expected_status'): | |
2308 | + if self._cmdts.expected_status: | |
2309 | + assert(cmd.state == self._cmdts.expected_status) | |
2310 | + | |
2311 | + | |
2312 | + def _TEST_test_routine_process(self): | |
2313 | + """ | |
2314 | + TEST MODE ONLY | |
2315 | + | |
2316 | + Send next command from scenario defined in TEST_COMMANDS_LIST | |
2317 | + (only if previous command finished) | |
2318 | + """ | |
2319 | + | |
2320 | + if not self.is_in_test_mode(): return | |
2321 | + | |
2322 | + log.info("(TEST mode) Trying to send a new command if possible...") | |
2323 | + | |
2324 | + # There is a current command being processed | |
2325 | + # => check if next command is "do_abort" | |
2326 | + # => if so, instantly send a "do_abort" to abort previous command | |
2327 | + if self._cmdts is not None: | |
2328 | + log.info(f"Waiting for end execution of cmd '{self._cmdts.name}' (sent to {self._cmdts.recipient}) ...") | |
2329 | + # Update cmdts fields from DB | |
2330 | + self._cmdts.refresh_from_db() | |
2331 | + | |
2332 | + # Current cmd is pending or running | |
2333 | + if self._cmdts.is_pending() or self._cmdts.is_running(): | |
2334 | + if self._next_cmdts is None: | |
2335 | + # If next command is "do_abort" then abort becomes the new current command (to be sent) | |
2336 | + self._next_cmdts = self._TEST_get_next_command_to_send() | |
2337 | + if self._next_cmdts and self._next_cmdts.name == "do_abort": | |
2338 | + # Wait a little to give a chance to agentB to start execution of current command, | |
2339 | + # so that we can abort it then (otherwise it won't be aborted!!) | |
2340 | + #time.sleep(4) | |
2341 | + self.waitfor(4) | |
2342 | + self._cmdts = self._next_cmdts | |
2343 | + self._next_cmdts = None | |
2344 | + log.info("***") | |
2345 | + #log.info(f"*** SEND ", self._cmdts) | |
2346 | + log.info(f"***" + str(self._cmdts)) | |
2347 | + log.info("***") | |
2348 | + self._cmdts.send() | |
2349 | + | |
2350 | + # Current cmd is no more running | |
2351 | + else: | |
2352 | + | |
2353 | + # Execution was not completed | |
2354 | + #if self._cmdts.is_expired() or self._cmdts.is_skipped() or self._cmdts.is_killed(): | |
2355 | + if self._cmdts.is_skipped() or self._cmdts.is_killed(): | |
2356 | + log.info("Command was not completed") | |
2357 | + # 2 possible scenarios: | |
2358 | + # - (1) Send the SAME command again | |
2359 | + ''' | |
2360 | + self.printd("Command was not completed, so I send it again") | |
2361 | + # The command was not completed, so, make a copy of it and send it again | |
2362 | + # For this, it is enough to set primary key to None, | |
2363 | + # then the send() command below will save a NEW command | |
2364 | + #self._cmdts = copy.copy(self._cmdts) | |
2365 | + self._cmdts.id = None | |
2366 | + SEND_A_NEW_COMMAND = True | |
2367 | + ''' | |
2368 | + # - (2) Send next command | |
2369 | + #self._cmdts = None | |
2370 | + | |
2371 | + # Execution was completeted => get result | |
2372 | + elif self._cmdts.is_executed(): | |
2373 | + cmdts_res = self._cmdts.get_result() | |
2374 | + print("toto") | |
2375 | + log.info(f"Cmd executed. Result is '{cmdts_res}'") | |
2376 | + #cmdts_is_processed = True | |
2377 | + ''' Optimisation possible pour gagner une iteration: | |
2378 | + self._cmdts = self.simulator_get_next_command_to_send() | |
2379 | + # No more command to send (from simulator) => return | |
2380 | + if self._cmdts is None: return | |
2381 | + SEND_A_NEW_COMMAND = True | |
2382 | + ''' | |
2383 | + # Set cmdts to None so that a new command will be sent at next iteration | |
2384 | + self._cmdts = None | |
2385 | + | |
2386 | + # No currently running command => get a new command and SEND it | |
2387 | + if self._cmdts is None: | |
2388 | + if self._next_cmdts is not None: | |
2389 | + self._cmdts = self._next_cmdts | |
2390 | + self._next_cmdts = None | |
2391 | + else: | |
2392 | + self._cmdts = self._TEST_get_next_command_to_send() | |
2393 | + # No more command to send (from simulator) => return and EXIT | |
2394 | + if self._cmdts is None: | |
2395 | + self.DO_MAIN_LOOP = False | |
2396 | + return | |
2397 | + # Send cmd (= set as pending and save) | |
2398 | + log.info("***") | |
2399 | + #self.printd(f"*** SEND ", self._cmdts) | |
2400 | + log.info(f"*** NEW COMMAND TO SEND is: " + str(self._cmdts)) | |
2401 | + if hasattr(self._cmdts,"expected_res") : | |
2402 | + log.info(f"*** (with expected result & final status : " + "'"+str(self._cmdts.expected_res)+"' & '"+str(self._cmdts.expected_status) + "')") | |
2403 | + log.info("***") | |
2404 | + #self._cmdts.set_as_pending() | |
2405 | + # SEND | |
2406 | + self._cmdts.send() | |
2407 | + #cmdts_is_processed = False | |
2408 | + #cmdts_res = None | |
2409 | + | |
2410 | + | |
2411 | + def _TEST_test_results(self): | |
2412 | + if not (self.TEST_MODE and self.TEST_WITH_FINAL_TEST): return | |
2413 | + if self.TEST_COMMANDS_LIST == []: return | |
2414 | + | |
2415 | + nb_commands_to_send = len(self.TEST_COMMANDS_LIST) | |
2416 | + nb_commands_sent, commands = self._TEST_test_results_start() | |
2417 | + #nb_commands_to_send = len(self.TEST_COMMANDS_LIST) | |
2418 | + | |
2419 | + # General (default) test | |
2420 | + #self.printd(commands[0].name, "compared to", self.TEST_COMMANDS_LIST[0].split()[1]) | |
2421 | + assert commands[0].name == self.TEST_COMMANDS_LIST[0].split()[1] | |
2422 | + last_cmd = commands[-1] | |
2423 | + assert last_cmd.name == self.TEST_COMMANDS_LIST[-1].split()[1] | |
2424 | + assert last_cmd.name == "do_exit" | |
2425 | + assert last_cmd.is_executed() | |
2426 | + assert last_cmd.get_result() == "SHOULD BE DONE NOW" | |
2427 | + | |
2428 | + nb_asserted = 0 | |
2429 | + nb_agent_general = 0 | |
2430 | + nb_unknown = 0 | |
2431 | + nb_unimplemented = 0 | |
2432 | + for cmd in commands: | |
2433 | + assert cmd.is_executed() or cmd.is_killed() or cmd.is_skipped() | |
2434 | + nb_asserted += 1 | |
2435 | + if cmd.is_agent_general_cmd(): | |
2436 | + nb_agent_general += 1 | |
2437 | + if cmd.name == "do_unknown": | |
2438 | + assert cmd.is_skipped() | |
2439 | + #assert "UnimplementedGenericCmdException" in cmd.get_result() | |
2440 | + assert "INVALID COMMAND" in cmd.get_result() | |
2441 | + nb_unknown += 1 | |
2442 | + #if cmd.name in ["do_unimplemented", "do_unknown"]: | |
2443 | + if cmd.name == "do_unimplemented": | |
2444 | + assert cmd.is_skipped() | |
2445 | + assert "UnimplementedGenericCmdException" in cmd.get_result() | |
2446 | + nb_unimplemented += 1 | |
2447 | + assert nb_asserted == nb_commands_sent | |
2448 | + log.info(f"{nb_commands_to_send} cmds I had to send <==> {nb_asserted} cmds executed (or killed), {nb_commands_to_send-nb_commands_sent} cmd ignored") | |
2449 | + log.info("Among executed commands:") | |
2450 | + log.info(f"- {nb_agent_general} AGENT general command(s)") | |
2451 | + log.info(f"- {nb_unimplemented} unimplemented command(s) => UnimplementedGenericCmdException raised then command was skipped") | |
2452 | + log.info(f"- {nb_unknown} unknown command(s) => skipped") | |
2453 | + | |
2454 | + | |
2455 | + # Can be overriden by subclass (AgentDevice) | |
2456 | + self.TEST_test_results_other(commands) | |
2457 | + ''' | |
2458 | + (EP) moved to AgentDevice | |
2459 | + # Now test that any "AD get_xx" following a "AD set_xx value" command has result = value | |
2460 | + for i,cmd_set in enumerate(commands): | |
2461 | + if cmd_set.name.startswith('set_'): | |
2462 | + commands_after = commands[i+1:] | |
2463 | + for cmd_get in commands_after: | |
2464 | + if cmd_get.name.startswith('get_') and cmd_get.name[4:]==cmd_set.name[4:] and cmd_get.device_type==cmd_set.device_type: | |
2465 | + log.info("cmd_get.result == cmd_set.args ?" + str(cmd_get.result) + ' ' + str(cmd_set.args)) | |
2466 | + assert cmd_get.get_result() == ','.join(cmd_set.args), "A get_xx command did not gave the expected result as set by a previous set_xx command" | |
2467 | + break | |
2468 | + ''' | |
2469 | + | |
2470 | + # Specific (detailed) test (to be overriden by subclass) | |
2471 | + nb_asserted2 = self.TEST_test_results_main(commands) | |
2472 | + self._TEST_test_results_end(nb_asserted) | |
2473 | + | |
2474 | + def _TEST_prepare(self): | |
2475 | + if not self.is_in_test_mode(): return | |
2476 | + log.debug("\n!!! In TEST mode !!! => preparing to run a scenario of test commands") | |
2477 | + log.debug("- Current test commands list scenario is:\n" + str(self.TEST_COMMANDS_LIST)) | |
2478 | + if not self.WITH_SIMULATOR: | |
2479 | + log.debug("\n!!! In TEST but no SIMULATOR mode (using REAL device) !!! => removing dangerous commands for real devices... :") | |
2480 | + TEST_COMMANDS_LIST_copy = self.TEST_COMMANDS_LIST.copy() | |
2481 | + for cmd in TEST_COMMANDS_LIST_copy: | |
2482 | + cmd_key = cmd.split()[1] | |
2483 | + if ("set_" in cmd_key) or ("do_start" in cmd_key) or cmd_key in ["do_init", "do_goto", "do_open", "do_close"]: | |
2484 | + self.TEST_COMMANDS_LIST.remove(cmd) | |
2485 | + log.debug("- NEW test commands list scenario is:\n" + self.TEST_COMMANDS_LIST, '\n') | |
2486 | + | |
2487 | + # Can be overriden by subclass (AgentDevice) | |
2488 | + def TEST_test_results_other(self, commands): | |
2489 | + pass | |
2490 | + | |
2491 | + def _TEST_test_results_start(self): | |
2492 | + print() | |
2493 | + log.info("--- Testing if the commands I SENT had the awaited result") | |
2494 | + log.info("Here are the last commands I sent:") | |
2495 | + #commands = list(Command.get_last_N_commands_for_agent(self.name, 16)) | |
2496 | + #commands = Command.get_last_N_commands_sent_to_agent(self.name, 16) | |
2497 | + nb_commands = len(self.TEST_COMMANDS_LIST) | |
2498 | + if "ad_unknown get_dec" in self.TEST_COMMANDS_LIST: nb_commands -= 1 | |
2499 | + commands = AgentCmd.get_last_N_commands_sent_by_agent(self.name, nb_commands) | |
2500 | + AgentCmd.show_commands(commands) | |
2501 | + return nb_commands, commands | |
2502 | + """ OLD SCENARIO | |
2503 | + nb_asserted = 0 | |
2504 | + for cmd in commands: | |
2505 | + if cmd.name == "specific0": | |
2506 | + assert cmd.is_skipped() | |
2507 | + nb_asserted+=1 | |
2508 | + if cmd.name == "specific1": | |
2509 | + assert cmd.result == "in step #5/5" | |
2510 | + assert cmd.is_executed() | |
2511 | + nb_asserted+=1 | |
2512 | + if cmd.name in ("specific2","specific3"): | |
2513 | + assert cmd.is_killed() | |
2514 | + nb_asserted+=1 | |
2515 | + if cmd.name in ("specific4", "specific5", "specific6", "specific7", "specific8"): | |
2516 | + assert cmd.is_pending() | |
2517 | + nb_asserted+=1 | |
2518 | + # 2 cmds abort | |
2519 | + if cmd.name in ("do_abort"): | |
2520 | + assert cmd.is_executed() | |
2521 | + nb_asserted+=1 | |
2522 | + if cmd.name in ("do_exit"): | |
2523 | + assert cmd.is_executed() | |
2524 | + nb_asserted+=1 | |
2525 | + assert nb_asserted == 12 | |
2526 | + self.printd("--- Finished testing => result is ok") | |
2527 | + """ | |
2528 | + | |
2529 | + # To be overriden by subclass | |
2530 | + def TEST_test_results_main(self, commands): | |
2531 | + return 0 | |
2532 | + ''' | |
2533 | + nb_asserted = 0 | |
2534 | + self.printd("from simulator_test_results_main", commands) | |
2535 | + for cmd in commands: | |
2536 | + assert cmd.is_executed() | |
2537 | + nb_asserted+=1 | |
2538 | + return nb_asserted | |
2539 | + ''' | |
2540 | + | |
2541 | + def _TEST_test_results_end(self, nb_asserted): | |
2542 | + #nb_commands_to_send = len(self.TEST_COMMANDS_LIST) | |
2543 | + #self.printd(nb_asserted, "vs", nb_commands_to_send) | |
2544 | + #assert nb_asserted == nb_commands_to_send | |
2545 | + #self.printd(f"************** Finished testing => result is ok ({nb_asserted} assertions) **************") | |
2546 | + printFullTerm(Colors.GREEN, f"************** Finished testing => result is ok ({nb_asserted} assertions) **************") | |
2547 | + | |
2548 | + | |
2549 | + | |
2550 | + | |
2551 | + | |
2552 | + | |
2553 | +""" | |
2554 | +================================================================= | |
2555 | + MAIN | |
2556 | +================================================================= | |
2557 | +""" | |
2558 | + | |
2559 | +def extract_parameters(): | |
2560 | + """ Usage: Agent.py [-t] [configfile] """ | |
2561 | + # arg 1 : -t | |
2562 | + # arg 2 : configfile | |
2563 | + DEBUG_MODE = False | |
2564 | + TEST_MODE = False | |
2565 | + WITH_SIM = False | |
2566 | + VERBOSE_MODE = False | |
2567 | + configfile = None | |
2568 | + log.debug("args:" + str(sys.argv)) | |
2569 | + for arg in sys.argv[1:] : | |
2570 | + if arg == "-t": TEST_MODE = True | |
2571 | + elif arg == "-s": WITH_SIM = True | |
2572 | + elif arg == "-d": DEBUG_MODE = True | |
2573 | + elif arg == "-v": VERBOSE_MODE = True | |
2574 | + #else: configfile = arg | |
2575 | + ''' | |
2576 | + if len(sys.argv) > 1: | |
2577 | + if sys.argv[1] == "-t": | |
2578 | + TEST_MODE = True | |
2579 | + if len(sys.argv) == 3: | |
2580 | + configfile = sys.argv[2] | |
2581 | + else: | |
2582 | + configfile = sys.argv[1] | |
2583 | + ''' | |
2584 | + #return DEBUG_MODE, WITH_SIM, TEST_MODE, VERBOSE_MODE, configfile | |
2585 | + return DEBUG_MODE, TEST_MODE, VERBOSE_MODE | |
2586 | + | |
2587 | +#def build_agent(Agent_type:Agent, name="GenericAgent", RUN_IN_THREAD=True): | |
2588 | +#def build_agent(Agent_type:Agent, RUN_IN_THREAD=True): | |
2589 | +def build_agent(Agent_type:Agent) -> Agent : | |
2590 | + #DEBUG_MODE, WITH_SIM, TEST_MODE, VERBOSE_MODE, configfile = extract_parameters() | |
2591 | + DEBUG_MODE, TEST_MODE, VERBOSE_MODE = extract_parameters() | |
2592 | + log.debug("debug mode is" + os.getenv("PYROS_DEBUG")) | |
2593 | + | |
2594 | + #print(logger) | |
2595 | + | |
2596 | + #agent = Agent("GenericAgent", configfile, RUN_IN_THREAD=True) | |
2597 | + #agent = Agent_type(configfile, RUN_IN_THREAD, DEBUG_MODE=DEBUG_MODE) | |
2598 | + #agent = Agent_type(RUN_IN_THREAD) | |
2599 | + agent = Agent_type() | |
2600 | + # AgentSP isn't in a config, so to avoid that WITH_SIM returns an error it's a special case | |
2601 | + if agent.name == "AgentSP": | |
2602 | + agent._set_with_simulator(False) | |
2603 | + agent._set_test_mode(TEST_MODE) | |
2604 | + return agent | |
2605 | + #agent = Agent_type(name, configfile, RUN_IN_THREAD) | |
2606 | + # Get the information of the agent name (name of class) within obsconfig and get the "is_real" attribute | |
2607 | + if agent.name in agent.get_config().get_agents(agent.unit).keys(): | |
2608 | + if agent.get_config().get_agent_information(agent.unit,agent.name).get("is_real"): | |
2609 | + WITH_SIM = not agent.get_config().get_agent_information(agent.unit,agent.name)["is_real"] | |
2610 | + else: | |
2611 | + WITH_SIM = True | |
2612 | + else: | |
2613 | + WITH_SIM = True | |
2614 | + agent._set_with_simulator(WITH_SIM) | |
2615 | + agent._set_test_mode(TEST_MODE) | |
2616 | + #print(logger) | |
2617 | + #logg.info("agent built, return it") | |
2618 | + | |
2619 | + #agent._set_debug_mode(DEBUG_MODE) | |
2620 | + return agent | |
2621 | + | |
2622 | + | |
2623 | +if __name__ == "__main__": | |
2624 | + | |
2625 | + ''' | |
2626 | + # with thread | |
2627 | + RUN_IN_THREAD=True | |
2628 | + # with process | |
2629 | + #RUN_IN_THREAD=False | |
2630 | + ''' | |
2631 | + | |
2632 | + #agent = build_agent(Agent, RUN_IN_THREAD=RUN_IN_THREAD) | |
2633 | + agent = build_agent(Agent) | |
2634 | + | |
2635 | + agent.show_config() | |
2636 | + | |
2637 | + #agent = build_agent(Agent, name="GenericAgent", RUN_IN_THREAD=RUN_IN_THREAD) | |
2638 | + ''' | |
2639 | + TEST_MODE, WITH_SIM, configfile = extract_parameters() | |
2640 | + agent = Agent("GenericAgent", configfile, RUN_IN_THREAD=True) | |
2641 | + #agent.setSimulatorMode(TEST_MODE) | |
2642 | + agent.setTestMode(TEST_MODE) | |
2643 | + agent.setWithSimulator(WITH_SIM) | |
2644 | + self.printd(agent) | |
2645 | + ''' | |
2646 | + agent.run() | ... | ... |
pyros.py
... | ... | @@ -1115,8 +1115,6 @@ def new_start(configfile: str, observatory: str, unit: str, computer_hostname: s |
1115 | 1115 | cmd += " -t" |
1116 | 1116 | if verbose_mode(): |
1117 | 1117 | cmd += " -v" |
1118 | - if configfile: | |
1119 | - cmd += f" {configfile}" | |
1120 | 1118 | if computer_hostname: |
1121 | 1119 | cmd += f" --computer {computer_hostname}" |
1122 | 1120 | ... | ... |