Commit d96e2484b27153f33776d8b6edfe001bbfd177ed

Authored by Alain Klotz
2 parents 06d938bf 1d32122b
Exists in dev

Merge branch 'dev' of https://gitlab.irap.omp.eu/epallier/pyros into dev

# Conflicts:
#	src/agent/Agent.py
README.md
... ... @@ -67,25 +67,40 @@ This software has been tested and validated with the following configurations :
67 67 --------------------------------------------------------------------------------------------
68 68 ## LAST VERSION
69 69  
70   -Date: 19/03/2019
  70 +Date: 22/03/2019
71 71  
72 72 Author: E. Pallier
73 73  
74   -VERSION: 0.20.22
75   -
76   -Comment:
77   - Multi-agents (2 agents) : AgentA (sender) sends commands to AgentB (receiver, and sender too)
78   -
79   - - Eval command
80   - - Timeout géré si commande pas exécutée en temps raisonnable : la même commande est ré-exéuctée à l'itération suivante
81   - - AgentA (et AgentB) a son propre scenario de commandes à envoyer à AgentB
82   - - Mode opératoire pour lancer un agent:
83   - - pour démarrer agentA : ./pyros.py start agentA [-c configfile]
84   - (ou encore: activer l'environnement virtuel, puis lancer "./AgentA.py configfile")
85   - - pour démarrer agentB : ouvrir un autre terminal et taper "./pyros.py start agentB"
  74 +VERSION: 0.20.29
  75 +
  76 +Comment: mode "test" (-t) géré (= mode simulateur)
  77 +
  78 + - Scenario de test :
  79 + - lancer agents A et B en mode simu (option -t): ./pyros.py -t start agentA,agentB
  80 + - attendre 1 à 2mn jusqu'à obtenir les 2 résultats suivants:
  81 + (AgentA): Finished testing => result is ok
  82 + (AgentB): Finished testing => result is ok
  83 +
  84 + - Mode opératoire pour lancer un agent (en mode normal, hors test) :
  85 + - pour lancer agentA seulement : ./pyros.py start agentA [-c configfile]
  86 + - pour lancer plusieurs agents : ./pyros.py start agentA,agentB,... [-c configfile]
  87 + (ou encore: activer l'environnement virtuel, puis lancer "cd src/agent/ ; ./AgentA.py configfile")
86 88 - pour utiliser thread ou processus : il suffit de mettre la constante RUN_IN_THREAD de AgentA (ou AgentB ou AgentX) à False ou True
87   - - GROSSE OPTIMISATION : plus besoin du script intermédiaire "start_agent.py" !!!
88   - ==> pyros.py lance directement "cd src/agent/ ; python AgentX.py"
  89 +
  90 + - Autres remarques:
  91 + - pyros.py peut lancer plusieurs agents (A et B) en même temps
  92 + - run(N) : run only N iterations
  93 + - send_command() implemented
  94 + - EVAL is now a generic command
  95 + - mode DEBUG (2 niveaux pour print)
  96 + - flush_command : nouvelle commande pour purger les commmandes en attente
  97 + - routine_process() implemented
  98 + - Eval command implemented
  99 + - Timeout géré : si commande pas exécutée en temps raisonnable => la même commande est ré-exéuctée à l'itération suivante
  100 + - self.print() pour les agents
  101 + - Chaque agent a son propre scenario de commandes à envoyer
  102 + - GROSSE OPTIMISATION : plus besoin du script intermédiaire "start_agent.py" !!!
  103 + ==> pyros.py lance directement "cd src/agent/ ; python AgentX.py"
89 104  
90 105 --------------------------------------------------------------------------------------------
91 106 - TECHNICAL DOC: tinyurl.com/pyros-doc
... ...
pyros.py
... ... @@ -123,6 +123,10 @@ def execProcess(command, from_venv=False, is_async=False):
123 123 printFullTerm(Colors.BLUE, "Executing command" + " [" + command + "]" + from_venv_str)
124 124 if from_venv: command = VENV_BIN+' ' + command
125 125 process = subprocess.Popen(command, shell=True)
  126 + if is_async:
  127 + #current_processes.append(process)
  128 + return process
  129 + # Not is_async => Wait for end of execution
126 130 process.wait()
127 131 if process.returncode == 0:
128 132 printFullTerm(Colors.GREEN, "Process executed successfully")
... ... @@ -133,11 +137,14 @@ def execProcess(command, from_venv=False, is_async=False):
133 137 #return process.returncode
134 138 return True if process.returncode==0 else False
135 139  
136   -def execProcessFromVenv(command:str):
137   - return execProcess(command, from_venv=True)
  140 +def execProcessFromVenv(command:str, is_async=False):
  141 + #return execProcess(command, from_venv=True, is_async)
  142 + return execProcess(command, True, is_async)
138 143  
139 144 #TODO: fusionner dans execProcess avec param is_async
140 145 def execProcessFromVenvAsync(command:str):
  146 + return execProcessFromVenv(command, True)
  147 + """
141 148 args = command.split()
142 149 printFullTerm(
143 150 Colors.BLUE, "Executing command from venv [" + str(" ".join(args[1:])) + "]"
... ... @@ -148,7 +155,7 @@ def execProcessFromVenvAsync(command:str):
148 155 # self.addExecuted(self.current_command, str(' '.join(args[1:])))
149 156 # p.wait()
150 157 return p
151   -
  158 + """
152 159  
153 160 def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False):
154 161 #system = platform.system()
... ... @@ -223,10 +230,28 @@ def pyros_launcher(test, verbose):
223 230 def shell():
224 231 print()
225 232 print("Launching a pyros shell")
226   - print("From this shell, type 'from common.models import *' to import all the pyros objects")
227   - print("Then, you can create any pyros object just by typing its name")
228   - print("For example, to create a AgentsSurvey object, type 'agent_survey = AgentsSurvey()'")
229   - print("See documentation, chapter '9.6 - Play with the pyros objects' for more details")
  233 + print()
  234 + print("NB1: If you want to play with an agent, type:")
  235 + print(" >>> from agent.AgentA import AgentA")
  236 + print(" >>> agent=AgentA('agent_toto')")
  237 + print(" >>> agent")
  238 + print(" >>> agent.run(2) (=> will run 2 iterations)")
  239 + print(" >>> cmd = agent.send_command('AgentB','eval 2+2')")
  240 + print(" >>> cmd")
  241 + print(" >>> cmd1.get_updated_result()")
  242 + print(" >>> ...")
  243 + print(" - See documentation, section 'Play with a pyros agent' inside the chapter 'Running pyros' for more details")
  244 + print()
  245 + print("NB2: If you want to play with the pyros objects, type:")
  246 + print(" >>> from common.models import *")
  247 + print(" - (This will import all the pyros objects)")
  248 + print(" - Then, you can create any pyros object just by typing its name.")
  249 + print(" - For example, to create an AgentSurvey object, type:")
  250 + print(" >>> agent_survey = AgentSurvey()")
  251 + print(" >>> agent_survey")
  252 + print(" >>> ...")
  253 + print(" - See documentation, section 'Play with the pyros objects' inside the chapter 'Running pyros' for more details")
  254 + print()
230 255 print("Type 'exit()' to quit")
231 256 print()
232 257 os.chdir("src/")
... ... @@ -336,23 +361,16 @@ def start(agent:str, configfile:str):
336 361 if not _check_agent(a): return
337 362 print("Agents are:", agents)
338 363 # 1 agent only
339   - elif not _check_agent(agent): return
340   - # VENV_BIN = 'private/venv_py3_pyros' + os.sep + self.b_in_dir + os.sep + self.bin_name
341   -
342   -
343   - # Start Agents
344   - """
345   - if agent=="majordome" or agent=="all":
346   - from majordome.tasks import Majordome
347   - Majordome().run()
348   - if agent=="alert_manager" or agent=="all":
349   - from alert_manager.tasks import AlertListener
350   - AlertListener().run()
351   - """
  364 + else:
  365 + agents = [agent]
  366 + if not _check_agent(agent): return
  367 +
  368 + # Start Agents (processes)
  369 + current_processes = []
352 370 for agent_name,agent_folder in AGENTS.items():
353 371  
354   - if agent in ("all", agent_name) :
355   -
  372 + #if agent in ("all", agent_name) :
  373 + if agent=="all" or agent_name in agents:
356 374 # Default case, launch agentX
357 375 #if agent_name == "agentX":
358 376  
... ... @@ -362,6 +380,8 @@ def start(agent:str, configfile:str):
362 380 #if not test_mode(): execProcess(VENV_BIN + " manage.py runserver")
363 381 #if not test_mode(): execProcessFromVenv("start_agent_" + agent_name + ".py " + configfile)
364 382  
  383 + current_dir = os.getcwd()
  384 +
365 385 # OLD format agents: majordome, monitoring, alert...
366 386 cmd = "start_agent.py " + agent_name + " " + configfile
367 387  
... ... @@ -377,41 +397,51 @@ def start(agent:str, configfile:str):
377 397 ##agentX.run(FOR_REAL=True)
378 398 os.chdir("src/agent/")
379 399 #cmd = "-m AgentX"
380   - cmd = f" Agent{agent_name[5:]}.py {configfile}"
381   -
382   - if not test_mode(): execProcessFromVenv(cmd)
  400 + #cmd = f" Agent{agent_name[5:]}.py {configfile}"
  401 + cmd = f"Agent{agent_name[5:]}.py"
  402 + if test_mode(): cmd += " -t"
  403 + if configfile: cmd += " {configfile}"
  404 + #if not test_mode(): current_processes.append( [execProcessFromVenvAsync(cmd), agent_name, -1] )
  405 + # Append this process ( [process id, agent_name, result=failure] )
  406 + # ("result" will be updated at the end of execution)
  407 + current_processes.append( [execProcessFromVenvAsync(cmd), agent_name, -1] )
383 408 # self._change_dir("..")
  409 + os.chdir(current_dir)
384 410  
385   - '''
386   - # Any other agent
387   - else:
388   - # Go into src/
389   - # self._change_dir("src")
390   - os.chdir("src")
391   - # print("Current directory : " + str(os.getcwd()))
392   - if agent_name != "webserver": os.chdir(agent_folder)
393   - # self.execProcessFromVenvAsync(self.VENV_BIN + ' start_agent_'+agent+'.py')
394   - #print(VENV_BIN)
395   - print("Launching agent", agent_name, "...")
396   - #if not test_mode(): execProcess(VENV_BIN + " start_agent_" + agent_name + ".py")
397   - #TODO:
398   - # - start_agent agent_name (1 script unique)
399   - # - start_agent -c configfile
400   - cmd = "start_agent_" + agent_name + ".py " + configfile
401   - # Django default dev web server
402   - if agent_name == "webserver": cmd = "manage.py runserver"
403   - if not test_mode(): execProcessFromVenv(cmd)
404   - # Go back to src/
405   - # self._change_dir('..')
406   - os.chdir("..")
407   - '''
408   -
409 411 # Go back to root folder (/)
410 412 # self._change_dir('..')
411   - os.chdir("..")
412   - return True
413   -
414   -
  413 + #os.chdir("..")
  414 + # Wait for end of each process execution
  415 + #for (p,agent) in current_processes:
  416 + for process in current_processes:
  417 + p,agent,_ = process
  418 + print(f"************ Waiting for end of execution of agent {agent} ************")
  419 + p.wait()
  420 + process[2] = p.returncode
  421 + print(f"************ END of execution of agent {agent} ************")
  422 + if p.returncode == 0:
  423 + printFullTerm(Colors.GREEN, f"Process {agent} executed successfully")
  424 + # self.addExecuted(self.current_command, command)
  425 + else:
  426 + printFullTerm(Colors.WARNING, f"Process {agent} execution failed")
  427 + # self.addError(self.current_command, command)
  428 +
  429 + print()
  430 + print()
  431 + print("Synthesis of the results:")
  432 + for process in current_processes:
  433 + p,agent,returncode = process
  434 + if returncode == 0:
  435 + printFullTerm(Colors.GREEN, f"Process {agent} executed successfully")
  436 + # self.addExecuted(self.current_command, command)
  437 + else:
  438 + printFullTerm(Colors.WARNING, f"Process {agent} execution failed")
  439 + # self.addError(self.current_command, command)
  440 +
  441 + #print("************ end of START() ************")
  442 + # Only according to the last process status:
  443 + #return True if p.returncode==0 else False
  444 + return True if p.returncode==0 else False
415 445  
416 446  
417 447 @pyros_launcher.command(help="Kill an agent")
... ...
src/agent/Agent.py
... ... @@ -2,6 +2,8 @@
2 2  
3 3 VERSION = "0.5"
4 4  
  5 +DEBUG=True
  6 +DEBUG=False
5 7  
6 8 """TODO:
7 9  
... ... @@ -44,7 +46,7 @@ sys.path.append("../..")
44 46 print("Starting with this sys.path", sys.path)
45 47  
46 48 # DJANGO setup
47   -# print("file is", __file__)
  49 +# self.printd("file is", __file__)
48 50 # mypath = os.getcwd()
49 51 # Go into src/
50 52 ##os.chdir("..")
... ... @@ -70,11 +72,16 @@ print()
70 72  
71 73 # --- GENERAL PURPOSE IMPORT ---
72 74 #from __future__ import absolute_import
73   -import time
74   -import threading, multiprocessing
  75 +import platform
  76 +import random
75 77 from threading import Thread
  78 +import threading, multiprocessing
  79 +import time
76 80 import utils.Logger as L
  81 +<<<<<<< HEAD
77 82 import random
  83 +=======
  84 +>>>>>>> 1d32122be4d1f56e208e7497498671de131dc8ce
78 85 import socket
79 86 #import ctypes
80 87 #import copy
... ... @@ -88,8 +95,8 @@ from django import db
88 95 #from django.conf import settings as djangosettings
89 96  
90 97 # --- SPECIFIC IMPORT ---
91   -from config.configpyros import ConfigPyros
92 98 from common.models import AgentSurvey, Command
  99 +from config.configpyros import ConfigPyros
93 100 #from dashboard.views import get_sunelev
94 101 #from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault
95 102 #from utils.JDManipulator import *
... ... @@ -97,15 +104,50 @@ from common.models import AgentSurvey, Command
97 104  
98 105 """
99 106 =================================================================
100   - GENERAL MODULE CONSTANT DEFINITIONS
  107 + GENERAL MODULE CONSTANT & FUNCTIONS DEFINITIONS
101 108 =================================================================
102 109 """
103 110  
104   -DEBUG_FILE = False
  111 +#DEBUG_FILE = False
105 112  
106 113 log = L.setupLogger("AgentLogger", "Agent")
107 114  
  115 +IS_WINDOWS = platform.system() == "Windows"
108 116  
  117 +class Colors:
  118 + HEADER = "\033[95m"
  119 + BLUE = "\033[94m"
  120 + GREEN = "\033[92m"
  121 + WARNING = "\033[93m"
  122 + FAIL = "\033[91m"
  123 + ENDC = "\033[0m"
  124 + BOLD = "\033[1m"
  125 + UNDERLINE = "\033[4m"
  126 +
  127 +def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False):
  128 + #system = platform.system()
  129 + """
  130 + if (self.disp == False and forced == False):
  131 + return 0
  132 + """
  133 + #if system == "Windows":
  134 + if IS_WINDOWS:
  135 + print(message, file=file, end=eol)
  136 + else:
  137 + print(color + message + Colors.ENDC, file=file, end=eol)
  138 + return 0
  139 +
  140 +def printFullTerm(color: Colors, string: str):
  141 + #system = platform.system()
  142 + columns = 100
  143 + row = 1000
  144 + disp = True
  145 + value = int(columns / 2 - len(string) / 2)
  146 + printColor(color, "-" * value, eol="")
  147 + printColor(color, string, eol="")
  148 + value += len(string)
  149 + printColor(color, "-" * (columns - value))
  150 + return 0
109 151  
110 152  
111 153 """
... ... @@ -152,6 +194,13 @@ class Agent:
152 194 - Then only, it goes to next iteration
153 195 """
154 196  
  197 +
  198 + # Maximum duration of this agent (only for SIMULATION mode)
  199 + # If set to None, it will never exit except if asked (or CTRL-C)
  200 + # If set to 20, it will exit after 20s
  201 + SIMULATOR_MAX_DURATION_SEC = None
  202 + #SIMULATOR_MAX_DURATION_SEC = 30
  203 +
155 204 # FOR TEST ONLY
156 205 # Run this agent in simulator mode
157 206 SIMULATOR_MODE = True
... ... @@ -161,7 +210,7 @@ class Agent:
161 210 SIMULATOR_COMMANDS_DEST = "myself"
162 211 # Default scenario to be executed
163 212 #SIMULATOR_COMMANDS = iter([
164   - SIMULATOR_COMMANDS = [
  213 + SIMULATOR_COMMANDS_LIST = [
165 214 "go_active",
166 215 "go_idle",
167 216  
... ... @@ -211,6 +260,8 @@ class Agent:
211 260 "go_active",
212 261 "specific10"
213 262 ]
  263 + #SIMULATOR_COMMANDS = iter(SIMULATOR_COMMANDS_LIST)
  264 +
214 265  
215 266  
216 267 """
... ... @@ -246,12 +297,6 @@ class Agent:
246 297 # Run for real, otherwise just print messages without really doing anything
247 298 FOR_REAL = True
248 299  
249   - # Maximum duration of this agent
250   - # If set to None, it will never exit except if asked (or CTRL-C)
251   - # If set to 20, it will exit after 20s
252   - MAX_DURATION_SEC = None
253   - #MAX_DURATION_SEC = 30
254   -
255 300 #COMMANDS_PEREMPTION_HOURS = 48
256 301 #COMMANDS_PEREMPTION_HOURS = 60/60
257 302  
... ... @@ -279,7 +324,7 @@ class Agent:
279 324 DEFAULT_CONFIG_FILE_NAME = "config_unit_simulunit1.xml"
280 325 CONFIG_DIR = "config"
281 326  
282   - # Command to send
  327 + # Current command to send
283 328 cmdts = None
284 329  
285 330 _agent_survey = None
... ... @@ -292,36 +337,43 @@ class Agent:
292 337 _computer_description = ''
293 338 _path_data = '../../config'
294 339  
  340 +<<<<<<< HEAD
295 341 _iter_num = 1
  342 +=======
  343 + _iter_num = None
  344 +
  345 +>>>>>>> 1d32122be4d1f56e208e7497498671de131dc8ce
296 346  
297 347 def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True):
298 348 self.name = name
299   - self.SIMULATOR_COMMANDS = iter(self.SIMULATOR_COMMANDS)
  349 + self.SIMULATOR_COMMANDS = iter(self.SIMULATOR_COMMANDS_LIST)
300 350 self.RUN_IN_THREAD = RUN_IN_THREAD
301 351 self.set_status(self.STATUS_LAUNCH)
302   - self.set_mode(self.MODE_IDLE)
  352 + self.set_idle()
  353 + #self.set_mode(self.MODE_IDLE)
303 354 if not config_filename:
304 355 config_filename = self.DEFAULT_CONFIG_FILE_NAME
305   -
306   - print(f"config_filename={config_filename}")
  356 +
  357 + self.printd(f"config_filename={config_filename}")
307 358 # If config file name is RELATIVE (i.e. without path, just the file name)
308 359 # => give it an absolute path (and remove "src/agent/" from it)
309 360 if config_filename == os.path.basename(config_filename):
310 361 tmp = os.path.abspath(self.CONFIG_DIR + os.sep + config_filename)
311 362 config_filename = os.path.abspath(self.CONFIG_DIR + os.sep + config_filename).replace(os.sep+"src"+os.sep,os.sep).replace(os.sep+"agent"+os.sep,os.sep)
312   - print("Config file used is", config_filename)
313   - #print("current path", os.getcwd())
314   - #print("this file path :", __file__)
315   - #print("config file path is", config_filename)
  363 + self.printd("Config file used is", config_filename)
  364 + #self.printd("current path", os.getcwd())
  365 + #self.printd("this file path :", __file__)
  366 + #self.printd("config file path is", config_filename)
316 367 # Instantiate an object for configuration
317   - #print("config file path is ", config_abs_filename)
  368 + #self.printd("config file path is ", config_abs_filename)
318 369 self.config = ConfigPyros(config_filename)
319 370 if self.config.get_last_errno() != self.config.NO_ERROR:
320 371 raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}")
321 372  
322 373 self.set_mode_from_config(name)
323 374 # TODO: remove
324   - self.set_idle()
  375 + #self.set_idle()
  376 + self.set_active()
325 377  
326 378 # Create 1st survey if none
327 379 #tmp = AgentSurvey.objects.filter(name=self.name)
... ... @@ -330,13 +382,27 @@ class Agent:
330 382 #if nb_agents == 0:
331 383 if not AgentSurvey.objects.filter(name=self.name).exists():
332 384 self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
333   - print("Agent survey is", self._agent_survey)
  385 + self.printd("Agent survey is", self._agent_survey)
334 386 #self._agent_survey = AgentSurvey(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
335 387 #self._agent_survey.save()
336 388  
  389 + def __repr__(self):
  390 + return "I am agent " + self.name
337 391  
338 392 def __str__(self):
339   - return "I am agent " + self.name
  393 + return self.__repr__()
  394 + #return "I am agent " + self.name
  395 +
  396 + # Normal print
  397 + def print(self, *args, **kwargs):
  398 + if args:
  399 + print(f"({self.name}): ", *args, **kwargs)
  400 + else:
  401 + print()
  402 +
  403 + # DEBUG print
  404 + def printd(self, *args, **kwargs):
  405 + if DEBUG: self.print(*args, **kwargs)
340 406  
341 407 def sleep(self, nbsec:float=2.0):
342 408 # thread
... ... @@ -346,11 +412,17 @@ class Agent:
346 412 else:
347 413 time.sleep(nbsec)
348 414  
349   - def run(self, FOR_REAL: bool = True):
  415 + def run(self, nb_iter=None, FOR_REAL: bool = True):
350 416 """
351 417 FOR_REAL: set to False if you don't want Majordome to send commands to devices
352 418 """
353 419  
  420 + if self.SIMULATOR_MODE:
  421 + self.print("[IN SIMULATOR MODE]")
  422 + else:
  423 + self.print("[IN NORMAL MODE]")
  424 + self.SIMULATOR_MAX_DURATION_SEC=None
  425 +
354 426 start_time = time.time()
355 427  
356 428 self.FOR_REAL = FOR_REAL
... ... @@ -361,13 +433,18 @@ class Agent:
361 433  
362 434 # Avoid blocking on false "running" commands
363 435 # (old commands that stayed with "running" status when agent was killed)
364   - Command.delete_commands_with_running_status_if_exists_for_agent(self.name)
  436 + Command.delete_commands_with_running_status_for_agent(self.name)
  437 +
  438 + # SIMULATOR MODE ONLY : flush previous commands to be sure to restart clean
  439 + if self.SIMULATOR_MODE:
  440 + self.print("flush previous commands to be sure to start in clean state")
  441 + Command.delete_pending_commands_for_agent(self.name)
365 442  
366 443 '''
367   - print()
368   - print(self)
369   - print("FOR REAL ?", self.FOR_REAL)
370   - print("DB3 used is:", djangosettings.DATABASES["default"]["NAME"])
  444 + self.printd()
  445 + self.printd(self)
  446 + self.printd("FOR REAL ?", self.FOR_REAL)
  447 + self.printd("DB3 used is:", djangosettings.DATABASES["default"]["NAME"])
371 448  
372 449 # SETUP
373 450 try:
... ... @@ -379,76 +456,89 @@ class Agent:
379 456 # self.config = Config.objects.get()[0]
380 457 except Exception as e:
381 458 # except Config.ObjectDoesNotExist:
382   - print("Config read (or write) exception", str(e))
  459 + self.printd("Config read (or write) exception", str(e))
383 460 return -1
384 461 '''
385 462  
  463 + self._iter_num = 1
386 464 # Main loop
387 465 while True:
388 466  
389   - # Wait a random number of sec before starting new iteration
390   - # (to let another agent having the chance to send a command)
391   - random_waiting_sec = random.randint(0,5)
392   - print(f"Waiting {random_waiting_sec} sec (random) before starting new iteration...")
393   - time.sleep(random_waiting_sec)
394   -
395 467 try:
396 468  
397   - print()
398   - print()
399   - #print("-"*80)
400   - print("-"*20, f"MAIN LOOP ITERATION {self._iter_num} (START)", "-"*20)
  469 + # Bad number of iterations, so exit
  470 + if nb_iter is not None:
  471 + if nb_iter <= 0: break
  472 + # Number of iterations asked is reached, so exit
  473 + if self._iter_num > nb_iter:
  474 + print(f"Exit because number of iterations asked ({nb_iter}) has been reached")
  475 + break
  476 +
  477 + self.print()
  478 + self.print()
  479 + #self.printd("-"*80)
  480 + self.print("-"*20, f"MAIN LOOP ITERATION {self._iter_num} (START)", "-"*20)
401 481 self.set_status(self.STATUS_MAIN_LOOP)
402 482 self.show_mode_and_status()
403 483  
  484 + # Wait a random number of sec before starting iteration
  485 + # (to let another agent having the chance to send a command before me)
  486 + random_waiting_sec = random.randint(0,5)
  487 + self.print(f"Waiting {random_waiting_sec} sec (random) before starting new iteration...")
  488 + time.sleep(random_waiting_sec)
  489 +
404 490 self.load_config()
405 491  
406 492 self.update_survey()
407 493  
408 494 #if self.SIMULATOR_MODE: self.simulator_send_next_command()
409 495  
410   - # generic cmd in json format
411   - print("------START COMMMAND PROCESSING------")
  496 + self.printd("------START COMMMAND PROCESSING------")
  497 +
  498 + # Purge commandes (every N iterations, delete old commands)
  499 + N=3
  500 + if ((self._iter_num-1) % N) == 0:
  501 + self.print("Looking for old commands to purge...")
  502 + #Command.purge_old_commands_for_agent(self.name)
  503 + self.purge_old_commands_sent_to_me()
  504 +
  505 + # Get next command to execute
412 506 cmd = self.get_next_valid_command()
413 507 #if cmd: cmd = self.general_process(cmd)
  508 +
  509 + # Process this (next) command (if exists)
414 510 if cmd: cmd = self.command_process(cmd)
415   - '''
416   - # Sub-level loop (only if ACTIVE)
417   - if self.is_active():
418   - if cmd: self.specific_process(cmd)
419   - '''
  511 +
  512 + # ROUTINE process
420 513 self.routine_process()
421   - print("------END COMMMAND PROCESSING------")
422 514  
423   - # Every N iterations, delete old commands
424   - N=3
425   - if ((self._iter_num-1) % N) == 0: Command.purge_old_commands_for_agent(self.name)
  515 + self.printd("------END COMMMAND PROCESSING------")
426 516  
427 517 #self.waitfor(self.mainloop_waittime)
428 518  
429   - print("-"*20, "MAIN LOOP ITERATION (END)", "-"*20)
430   - #print("-"*80)
431   -
  519 + self.print("-"*20, "MAIN LOOP ITERATION (END)", "-"*20)
432 520 #self.do_log(LOG_DEBUG, "Ending main loop iteration")
433 521  
434   - self._iter_num += 1
  522 + # Exit if max duration is reached
  523 + if self.SIMULATOR_MAX_DURATION_SEC and (time.time()-start_time > self.SIMULATOR_MAX_DURATION_SEC):
  524 + self.print("Exit because of max duration set to ", self.SIMULATOR_MAX_DURATION_SEC, "s")
  525 + self.kill_running_specific_cmd_if_exists()
  526 + if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST: self.simulator_test_results()
  527 + break
435 528  
436   - # Exit if max duration is timed out
437   - if self.MAX_DURATION_SEC:
438   - #print ("time", time.time()-start_time)
439   - if time.time()-start_time > self.MAX_DURATION_SEC:
440   - print("Exit because of max duration set to ", self.MAX_DURATION_SEC, "s")
441   - self.kill_running_specific_cmd_if_exists()
442   - break
  529 + self._iter_num += 1
443 530  
444 531 except KeyboardInterrupt:
445 532 # In case of CTRL-C, kill the current thread (process) before dying (in error)
446   - print("CTRL-C Interrupted, I kill the current thread (process) before exiting")
  533 + self.print("CTRL-C Interrupted, I kill the current thread (process) before exiting")
447 534 self.kill_running_specific_cmd_if_exists()
448 535 exit(1)
449 536  
450 537  
451 538  
  539 + def purge_old_commands_sent_to_me(self):
  540 + Command.purge_old_commands_sent_to_agent(self.name)
  541 +
452 542 # To be overriden by subclass
453 543 def routine_process(self):
454 544 """
... ... @@ -458,7 +548,17 @@ class Agent:
458 548 at each iteration
459 549 """
460 550 self.set_status(self.STATUS_ROUTINE_PROCESS)
  551 + if not self.is_active():
  552 + self.printd("I am IDLE, so I bypass the routine_process (do not send any new command)")
  553 + return
  554 +
  555 + # TODO: normal code pour routine
  556 + if not self.SIMULATOR_MODE:
  557 + return
461 558  
  559 + """
  560 + SIMULATOR MODE ONLY
  561 + """
462 562 if self.cmdts is None:
463 563 self.cmdts = self.simulator_get_next_command_to_send()
464 564 else:
... ... @@ -466,14 +566,14 @@ class Agent:
466 566 # For this, it is enough to set primary key to None,
467 567 # then the send() command below will save a NEW command
468 568 #self.cmdts = copy.copy(self.cmdts)
469   - self.cmdts.set_as_pending()
470 569 self.cmdts.id = None
471 570  
472 571 # No more command to send (from simulator), return
473 572 if self.cmdts is None: return
474 573  
475   - # 1) Send cmd
476   - print(f"Send command", self.cmdts)
  574 + # 1) Send cmd (= set as pending and save)
  575 + self.print(f"Send command", self.cmdts)
  576 + #self.cmdts.set_as_pending()
477 577 self.cmdts.send()
478 578 cmdts_is_processed = False
479 579 cmdts_res = None
... ... @@ -481,7 +581,7 @@ class Agent:
481 581 # 2) Wait for end of cmd execution
482 582 #self.wait_for_execution_of_cmd(self.cmdts)
483 583 while not cmdts_is_processed:
484   - print(f"Waiting for end of cmd {self.cmdts.name} execution...")
  584 + self.print(f"Waiting for end of cmd '{self.cmdts.name}' execution...")
485 585 self.cmdts.refresh_from_db()
486 586 # timeout ?
487 587 if self.cmdts.is_expired(): break
... ... @@ -495,9 +595,9 @@ class Agent:
495 595  
496 596 # 3) Get cmd result
497 597 if cmdts_is_processed:
498   - print(f"Cmd executed. Result is '{cmdts_res}'")
  598 + self.print(f"Cmd executed. Result is '{cmdts_res}'")
499 599 else:
500   - print("Command was not completed")
  600 + self.printd("Command was not completed")
501 601  
502 602  
503 603 """
... ... @@ -509,10 +609,10 @@ class Agent:
509 609 NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc)
510 610 ###
511 611  
512   - print("Looking for old commands to purge...")
  612 + self.printd("Looking for old commands to purge...")
513 613 ###
514 614 COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS)
515   - #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)
  615 + #self.printd("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)
516 616 old_commands = Command.objects.filter(
517 617 # only commands for me
518 618 receiver = self.name,
... ... @@ -522,22 +622,24 @@ class Agent:
522 622 ###
523 623 old_commands = Command.get_old_commands_for_agent(self.name)
524 624 if old_commands.exists():
525   - print("Found old commands to delete:")
526   - for cmd in old_commands: print(cmd)
  625 + self.printd("Found old commands to delete:")
  626 + for cmd in old_commands: self.printd(cmd)
527 627 old_commands.delete()
528 628 """
529 629  
530 630 def waitfor(self, nbsec):
531   - print(f"Now, waiting for {nbsec} seconds...")
  631 + self.printd(f"Now, waiting for {nbsec} seconds...")
532 632 time.sleep(nbsec)
533 633  
534 634 def set_status(self, status:str):
535   - print(f"[NEW CURRENT STATUS: {status}] (switching from status {self.status})")
  635 + #self.print(f"[{status}] (switching from status {self.status})")
  636 + self.print(f"[{status}]")
536 637 self.status = status
537 638 return False
538 639  
539 640 def set_mode(self, mode:str):
540   - print(f"Switching from mode {self.mode} to mode {mode}")
  641 + #self.print(f"Switching from mode {self.mode} to mode {mode}")
  642 + self.print(f"[NEW MODE {mode}]")
541 643 self.mode = mode
542 644  
543 645 def is_active(self):
... ... @@ -550,7 +652,7 @@ class Agent:
550 652 self.set_mode(self.MODE_IDLE)
551 653  
552 654 def show_mode_and_status(self):
553   - print(f"CURRENT MODE is {self.mode} (with status {self.status})")
  655 + self.print(f"CURRENT MODE is {self.mode} (status is {self.status})")
554 656  
555 657 def die(self):
556 658 self.set_status(self.STATUS_EXIT)
... ... @@ -598,7 +700,7 @@ class Agent:
598 700 """
599 701  
600 702 def init(self):
601   - print("Initializing...")
  703 + self.printd("Initializing...")
602 704 self.set_status(self.STATUS_INIT)
603 705  
604 706 def load_config(self):
... ... @@ -606,7 +708,11 @@ class Agent:
606 708 TODO:
607 709 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)
608 710 """
  711 +<<<<<<< HEAD
609 712 print("Loading the config file...")
  713 +=======
  714 + self.printd("Loading the config file...")
  715 +>>>>>>> 1d32122be4d1f56e208e7497498671de131dc8ce
610 716 self.config.load()
611 717 if self.config.get_last_errno() != self.config.NO_ERROR:
612 718 raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}")
... ... @@ -624,7 +730,11 @@ class Agent:
624 730 if param['section']=="assembly" and param['key']=="alias":
625 731 assembled_aliases.append(param['value'])
626 732 #print(f"Unit {unit_alias} is the assembly of {assembled_aliases}")
  733 +<<<<<<< HEAD
627 734  
  735 +=======
  736 +
  737 +>>>>>>> 1d32122be4d1f56e208e7497498671de131dc8ce
628 738 print("--------- Components of the unit -----------")
629 739 print("Configuration file is {}".format(self.config.get_configfile()))
630 740 alias = self.config.get_aliases('unit')[0]
... ... @@ -642,6 +752,7 @@ class Agent:
642 752 assembled_mount_aliases.append(alias)
643 753 elif unit_subtag=="channel":
644 754 assembled_channel_aliases.append(alias)
  755 +<<<<<<< HEAD
645 756  
646 757 print("--------- Assembly of the unit -----------")
647 758 print(f"Assembled mount aliases: {assembled_mount_aliases}")
... ... @@ -651,6 +762,17 @@ class Agent:
651 762 mount_alias = assembled_mount_aliases[0]
652 763 home = self.config.get_param(mount_alias,'MountPointing','home')
653 764  
  765 +=======
  766 +
  767 + print("--------- Assembly of the unit -----------")
  768 + print(f"Assembled mount aliases: {assembled_mount_aliases}")
  769 + print(f"Assembled channel aliases: {assembled_channel_aliases}")
  770 +
  771 + # --- Get the home of the mount[0]
  772 + mount_alias = assembled_mount_aliases[0]
  773 + home = self.config.get_param(mount_alias,'MountPointing','home')
  774 +
  775 +>>>>>>> 1d32122be4d1f56e208e7497498671de131dc8ce
654 776 print("------------------------------------------")
655 777 hostname = socket.gethostname()
656 778 self._computer_alias = ''
... ... @@ -670,8 +792,8 @@ class Agent:
670 792 print("------------------------------------------")
671 793  
672 794 def update_survey(self):
673   - print("Updating the survey database table...")
674   - print("- fetching table line for agent", self.name)
  795 + self.printd("Updating the survey database table...")
  796 + #self.printd("- fetching table line for agent", self.name)
675 797 # only necessary when using process (not necessary with threads)
676 798 #with transaction.atomic():
677 799 self._agent_survey = AgentSurvey.objects.get(name=self.name)
... ... @@ -679,31 +801,18 @@ class Agent:
679 801 self._agent_survey.status = self.status
680 802 self._agent_survey.save()
681 803  
682   -
683   - def simulator_get_next_command_to_send(self)->Command:
684   - cmd_name = next(self.SIMULATOR_COMMANDS, None)
685   - if cmd_name is None: return None
686   - receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
687   - return Command(sender=self.name, receiver=receiver_agent, name=cmd_name)
688   -
689   - def simulator_send_next_command(self):
690   - #self._current_test_cmd = "go_idle" if self._current_test_cmd=="go_active" else "go_active"
691   - #if self._nb_test_cmds == 4: self._current_test_cmd = "exit"
692   - cmd_name = next(self.SIMULATOR_COMMANDS, None)
693   - #print("next cmd is ", cmd_name)
694   - if cmd_name is None: return
695   - #Command.objects.create(sender=self.name, receiver=self.name, name=cmd_name)
696   - receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
697   - Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name)
698   - #time.sleep(1)
699   - #self._simulator_current_cmd_idx += 1
700   - #self._nb_test_cmds += 1
701   -
702 804 """
703 805 def send_command(self, cmd_name):
704 806 receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
705 807 Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name)
706 808 """
  809 + #def send_command(self, to_agent, cmd_type, cmd_name, cmd_args=None):
  810 + def send_command(self, to_agent, cmd_name, cmd_args=None):
  811 + """
  812 + #ex: send_command(“AgentX”,”GENERIC”,”EVAL”,“3+4”)
  813 + ex: send_command(“AgentX”,"EVAL”,“3+4”)
  814 + """
  815 + return Command.send_command(self.name, to_agent, cmd_name, cmd_args)
707 816  
708 817 def get_next_valid_command(self)->Command:
709 818 """
... ... @@ -712,26 +821,28 @@ class Agent:
712 821 Commands are read in chronological order
713 822 """
714 823 self.set_status(self.STATUS_GET_NEXT_COMMAND)
715   - print("Looking for new commands from the database ...")
  824 + #self.printd("Looking for new commands from the database ...")
716 825  
717 826 # 1) Get all pending commands for me (return if None)
718   - # Not sure this is necessary, but there might be a risk
  827 + # Not sure this is necessary to do it in a transaction,
  828 + # but there might be a risk
719 829 # that a command status is modified while we are reading...
720 830 with transaction.atomic():
721   - self._pending_commands = Command.get_pending_commands_for_agent(self.name)
  831 + self._pending_commands = Command.get_pending_and_running_commands_for_agent(self.name)
722 832 commands = self._pending_commands
723 833 if not commands.exists():
724   - print("No new command to process")
  834 + self.printd("No new command to process")
725 835 return None
726   - print("Current pending commands are (time ordered) :")
  836 + self.print("Current pending commands are (time ordered) :")
727 837 Command.show_commands(commands)
728 838  
729 839 # 2) If there is a "exit" or "abort" command pending (even at the end of the list),
730 840 # which is VALID (not expired),
731 841 # then pass it straight away to general_process() for execution
732   - for cmd in commands:
733   - if cmd.name in ("exit", "abort"): break
734   - if cmd.name in ("exit", "abort") and not cmd.is_expired(): return cmd
  842 + for cmd in commands:
  843 + if cmd.name in ("exit", "abort", "flush_commands"): break
  844 + if cmd.name in ("exit", "abort", "flush_commands") and not cmd.is_expired():
  845 + return cmd
735 846  
736 847 # 3) If first (oldest) command is currently running
737 848 # (status CMD_RUNNING), then do nothing and return
... ... @@ -747,16 +858,16 @@ class Agent:
747 858 """
748 859 cmd = commands[0]
749 860 if cmd.is_running():
750   - #print(f"There is currently a running command ({cmd_executing.first().name}), so I do nothing (wait for end of execution)")
751   - print(f"There is currently a running command ({cmd.name})")
  861 + #self.printd(f"There is currently a running command ({cmd_executing.first().name}), so I do nothing (wait for end of execution)")
  862 + self.printd(f"There is currently a running command ({cmd.name})")
752 863 """
753 864 # Check that this command is not expired
754 865 if cmd.is_expired():
755   - print("But this command is expired, so set its status to OUTOFDATE, and go on")
  866 + self.printd("But this command is expired, so set its status to OUTOFDATE, and go on")
756 867 cmd_executing.set_as_outofdate()
757 868 else:
758 869 """
759   - print(f"Thus, I will do nothing until this command execution is finished")
  870 + self.printd(f"Thus, I will do nothing until this command execution is finished")
760 871 # TODO: kill si superieur a MAX_EXEC_TIME
761 872 return None
762 873  
... ... @@ -771,51 +882,54 @@ class Agent:
771 882  
772 883 # 6) Current cmd must now be a valid (not expired) and PENDING one,
773 884 # so pass it to general_process() for execution
774   - #print(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
775   - print("***")
776   - print("*** Got", cmd)
777   - print("***")
  885 + #self.printd(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
  886 + self.print("***")
  887 + self.print("*** Got", cmd)
  888 + self.print("***")
778 889 return cmd
779 890  
780 891  
781 892 def command_process(self, cmd:Command)->Command:
782   - cmd = self.general_process(cmd)
783   - # Sub-level loop (only if ACTIVE)
784   - if cmd and self.is_active():
785   - self.specific_process(cmd)
  893 + assert cmd is not None
  894 + cmd = self.general_process(cmd)
  895 + # Sub-level loop (only if ACTIVE)
  896 + if cmd and self.is_active():
  897 + self.specific_process(cmd)
786 898  
787 899  
788 900 def general_process(self, cmd:Command)->Command:
  901 + assert cmd is not None
789 902  
790 903 self.set_status(self.STATUS_GENERAL_PROCESS)
791   - print(f"Starting general processing of {cmd}")
  904 + self.print(f"Starting processing of {cmd}")
792 905  
793 906 # Update read time to say that the command has been READ
794 907 cmd.set_read_time()
795 908 # Precondition: command cmd is valid (not expired), has already been read, is pending
796 909 assert (not cmd.is_expired()) and cmd.is_pending() and cmd.is_read()
797 910  
798   - #print(f"Starting general processing of command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
  911 + #self.printd(f"Starting general processing of command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
799 912  
800 913 """
801 914 # 2) If expired command, change its status to expired and return
802 915 if cmd.is_expired():
803   - print("This command is expired, so mark it as such, and ignore it")
  916 + self.printd("This command is expired, so mark it as such, and ignore it")
804 917 cmd.set_as_outofdate()
805 918 return None
806 919 """
807 920  
808 921 # If cmd is generic, execute it, change its status to executed, and return
809 922 if cmd.is_generic():
810   - print("This command is generic, execute it...")
  923 + self.print("This command is generic, execute it...")
811 924 self.exec_generic_cmd(cmd)
812 925 # If cmd is "exit", kill myself (without any question, this is an order soldier !)
813 926 # This "exit" should normally kill any current thread (to be checked...)
814 927 if cmd.name == "exit":
815   - print("(before exiting) Here are the current (still) pending commands (time ordered) :")
816   - commands = Command.get_pending_commands_for_agent(self.name)
  928 + self.printd("(before exiting) Here are the current (still) pending commands (time ordered) :")
  929 + commands = Command.get_pending_and_running_commands_for_agent(self.name)
817 930 Command.show_commands(commands)
818   - if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST and self.SIMULATOR_COMMANDS_DEST == "myself": self.simulator_test_results()
  931 + #if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST and self.SIMULATOR_COMMANDS_DEST == "myself": self.simulator_test_results()
  932 + if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST: self.simulator_test_results()
819 933 exit(0)
820 934 # Command is executed, so return None
821 935 return None
... ... @@ -825,19 +939,19 @@ class Agent:
825 939 # cmd is not generic but, as I am idle, change its status to SKIPPED, ignore it, and return
826 940 #if self.mode == self.MODE_IDLE:
827 941 if not self.is_active():
828   - print("This command is not generic but, as I am IDLE, I mark it SKIPPED and ignore it")
  942 + self.printd("This command is not generic but, as I am IDLE, I mark it SKIPPED and ignore it")
829 943 cmd.set_as_skipped()
830 944 return None
831 945  
832 946 # Je suis pas idle et cde pas générique: je la traite pas, elle sera traitée par core_process :
833 947 # attendre que cette commande soit exécutée avant de passer à la commande suivante (situation “bloquante” normale)
834   - print("This command is not generic and, as I am not IDLE, I pass it to the specific processing")
835   - print("(then I will not execute any other new command until this command is EXECUTED)")
  948 + self.printd("This command is not generic and, as I am not IDLE, I pass it to the specific processing")
  949 + self.printd("(then I will not execute any other new command until this command is EXECUTED)")
836 950 return cmd
837 951  
838 952  
839 953 def exec_generic_cmd(self, cmd:Command):
840   - print("Starting execution of a Generic cmd...")
  954 + self.printd("Starting execution of a Generic cmd...")
841 955 cmd.set_as_running()
842 956  
843 957 # Executing command
... ... @@ -845,19 +959,30 @@ class Agent:
845 959 self.set_active()
846 960 cmd.set_result("I am now active")
847 961 time.sleep(1)
848   - if cmd.name == "go_idle":
  962 + elif cmd.name == "go_idle":
849 963 self.set_idle()
850 964 cmd.set_result("I am now idle")
851 965 time.sleep(1)
852   - # If cmd is "abort", kill any currently running thread
853   - if cmd.name in ("abort", "exit"):
854   - #print("Current pending commands are:")
  966 + elif cmd.name in ("flush_commands"):
  967 + self.print("flush_commands received: Delete all pending commands")
  968 + Command.delete_pending_commands_for_agent(self.name)
  969 + # If cmd is "abort" or "exit", kill any currently running thread
  970 + elif cmd.name in ("abort", "exit"):
  971 + #self.printd("Current pending commands are:")
855 972 #Command.show_commands(self._pending_commands)
856   - print("Aborting current executing command if exists:")
  973 + self.print("Aborting current executing command if exists:")
857 974 self.kill_running_specific_cmd_if_exists()
  975 + else:
  976 + name = cmd.name
  977 + args = None
  978 + if " " in name: name,args = name.split()
  979 + if name == "eval":
  980 + if args==None: raise(ValueError)
  981 + cmd.set_result(eval(args))
  982 +
858 983  
859 984 cmd.set_as_processed()
860   - print("...Generic cmd has been executed")
  985 + self.printd("...Generic cmd has been executed")
861 986  
862 987  
863 988  
... ... @@ -868,7 +993,7 @@ class Agent:
868 993 - in file
869 994 - in db
870 995 """
871   - print("Logging data...")
  996 + self.printd("Logging data...")
872 997  
873 998  
874 999  
... ... @@ -883,14 +1008,15 @@ class Agent:
883 1008 temps (max 20 min par exemple) mais dans la méthode
884 1009 specific_process il y a une boucle sur la prise des images.
885 1010 """
886   - self.set_status(self.STATUS_SPECIFIC_PROCESS)
  1011 + assert cmd is not None
887 1012 assert self.is_active()
  1013 + self.set_status(self.STATUS_SPECIFIC_PROCESS)
888 1014 self._current_specific_cmd = cmd
889   - print("Starting specific process...")
  1015 + self.printd("Starting specific process...")
890 1016 #self._current_thread = threading.Thread(target=self.exec_command)
891 1017 # Run in a thread
892 1018 if self.RUN_IN_THREAD:
893   - print("(run cmd in a thread)")
  1019 + self.printd("(run cmd in a thread)")
894 1020 self._current_specific_thread = StoppableThreadEvenWhenSleeping(target=self.thread_exec_specific_cmd)
895 1021 #self._current_specific_thread = StoppableThreadEvenWhenSleeping(target=self.exec_specific_cmd, args=(cmd,))
896 1022 #self._current_thread = threading.Thread(target=self.exec_command)
... ... @@ -899,7 +1025,7 @@ class Agent:
899 1025 #self._current_specific_thread = thread_with_exception('thread test')
900 1026 # Run in a process
901 1027 else:
902   - print("(run cmd in a process)")
  1028 + self.printd("(run cmd in a process)")
903 1029 # close the database connection first, it will be re-opened in each process
904 1030 db.connections.close_all()
905 1031 self._current_specific_thread = multiprocessing.Process(target=self.thread_exec_specific_cmd)
... ... @@ -909,14 +1035,14 @@ class Agent:
909 1035 self._current_specific_thread.start()
910 1036 #my_thread.join()
911 1037 #self.waitfor(self.subloop_waittime)
912   - print("Ending specific process (thread has been launched)")
  1038 + self.printd("Ending specific process (thread has been launched)")
913 1039  
914 1040  
915 1041 def kill_running_specific_cmd_if_exists(self):
916 1042 if (self._current_specific_thread is None) or not self._current_specific_thread.is_alive():
917   - print("...No current specific command thread to abort...")
  1043 + self.printd("...No current specific command thread to abort...")
918 1044 else:
919   - print(f"Killing command {self._current_specific_cmd.name}")
  1045 + self.printd(f"Killing command {self._current_specific_cmd.name}")
920 1046 # Ask the thread to stop itself
921 1047 #self._current_specific_thread.stop()
922 1048 #self._current_specific_thread._stop()
... ... @@ -933,13 +1059,53 @@ class Agent:
933 1059 self._current_specific_cmd = None
934 1060  
935 1061  
  1062 + """
  1063 + =================================================================
  1064 + SIMULATOR DEDICATED FUNCTIONS
  1065 + =================================================================
  1066 + """
  1067 +
  1068 + def setSimulatorMode(self, mode):
  1069 + self.SIMULATOR_MODE=mode
  1070 +
  1071 + def simulator_get_next_command_to_send(self)->Command:
  1072 + cmd_name = next(self.SIMULATOR_COMMANDS, None)
  1073 + #return cmd_name
  1074 + if cmd_name is None: return None
  1075 + receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
  1076 + return Command(sender=self.name, receiver=receiver_agent, name=cmd_name)
  1077 +
  1078 + """
  1079 + def simulator_send_next_command(self):
  1080 + #self._current_test_cmd = "go_idle" if self._current_test_cmd=="go_active" else "go_active"
  1081 + #if self._nb_test_cmds == 4: self._current_test_cmd = "exit"
  1082 + cmd_name = next(self.SIMULATOR_COMMANDS, None)
  1083 + #self.printd("next cmd is ", cmd_name)
  1084 + if cmd_name is None: return
  1085 + #Command.objects.create(sender=self.name, receiver=self.name, name=cmd_name)
  1086 + receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
  1087 + Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name)
  1088 + #time.sleep(1)
  1089 + #self._simulator_current_cmd_idx += 1
  1090 + #self._nb_test_cmds += 1
  1091 + """
  1092 +
936 1093 def simulator_test_results(self):
937   - print("\n--- Starting testing if result is ok")
938   - print("Here are the 14 last commands:")
  1094 + commands = self.simulator_test_results_start()
  1095 + nb_asserted = self.simulator_test_results_main(commands)
  1096 + self.simulator_test_results_end(nb_asserted)
  1097 +
  1098 + def simulator_test_results_start(self):
  1099 + self.print("\n--- Testing if the commands I SENT had the awaited result")
  1100 + self.print("Here are the last commands I sent:")
939 1101 #commands = list(Command.get_last_N_commands_for_agent(self.name, 16))
940   - commands = Command.get_last_N_commands_for_agent(self.name, 16)
941   - Command.show_commands(commands)
  1102 + #commands = Command.get_last_N_commands_sent_to_agent(self.name, 16)
  1103 + commands = Command.get_last_N_commands_sent_by_agent(self.name, len(self.SIMULATOR_COMMANDS_LIST))
942 1104 Command.show_commands(commands)
  1105 + assert commands[0].name == self.SIMULATOR_COMMANDS_LIST[0]
  1106 + assert commands[-1].name == self.SIMULATOR_COMMANDS_LIST[-1]
  1107 + return commands
  1108 + """ OLD SCENARIO
943 1109 nb_asserted = 0
944 1110 for cmd in commands:
945 1111 if cmd.name == "specific0":
... ... @@ -963,7 +1129,23 @@ class Agent:
963 1129 assert cmd.is_executed()
964 1130 nb_asserted+=1
965 1131 assert nb_asserted == 12
966   - print("--- Finished testing => result is ok")
  1132 + self.printd("--- Finished testing => result is ok")
  1133 + """
  1134 +
  1135 + # To be overriden by subclass
  1136 + def simulator_test_results_main(self, commands):
  1137 + nb_asserted = 0
  1138 + for cmd in commands:
  1139 + assert cmd.is_executed()
  1140 + nb_asserted+=1
  1141 + return nb_asserted
  1142 +
  1143 + def simulator_test_results_end(self, nb_asserted):
  1144 + nb_commands_to_send = len(self.SIMULATOR_COMMANDS_LIST)
  1145 + assert nb_asserted == nb_commands_to_send
  1146 + #self.print(f"************** Finished testing => result is ok ({nb_asserted} assertions) **************")
  1147 + printFullTerm(Colors.GREEN, f"************** Finished testing => result is ok ({nb_asserted} assertions) **************")
  1148 +
967 1149  
968 1150  
969 1151  
... ... @@ -991,8 +1173,8 @@ class Agent:
991 1173 cmd = self._current_specific_cmd
992 1174 """ specific command execution setting up """
993 1175 #cmd = self.get_current_specific_cmd()
994   - print(">>>>> Thread: starting execution of command", cmd.name)
995   - print(">>>>> Thread: PID: %s, Process Name: %s, Thread Name: %s" % (
  1176 + self.printd(">>>>> Thread: starting execution of command", cmd.name)
  1177 + self.printd(">>>>> Thread: PID: %s, Process Name: %s, Thread Name: %s" % (
996 1178 os.getpid(),
997 1179 multiprocessing.current_process().name,
998 1180 threading.current_thread().name)
... ... @@ -1030,7 +1212,7 @@ class Agent:
1030 1212 def thread_exec_specific_cmd_main(self):
1031 1213 """
1032 1214 cmd = self._current_specific_cmd
1033   - print("Doing nothing, just sleeping...")
  1215 + self.printd("Doing nothing, just sleeping...")
1034 1216 self.sleep(3)
1035 1217 """
1036 1218  
... ... @@ -1073,7 +1255,7 @@ class Agent:
1073 1255 with transaction.atomic():
1074 1256 cmd.set_as_processed()
1075 1257 """
1076   - print(">>>>> Thread: ended execution of command", cmd.name)
  1258 + self.printd(">>>>> Thread: ended execution of command", cmd.name)
1077 1259 cmd = None
1078 1260 # No more current thread
1079 1261 #self._current_specific_thread = None
... ... @@ -1084,9 +1266,9 @@ class Agent:
1084 1266 # Exit if I was asked to stop
1085 1267 cmd = self._current_specific_cmd
1086 1268 if self.RUN_IN_THREAD and threading.current_thread().stopped():
1087   - print(f">>>>> Thread (cmd {cmd.name}): I received the stop signal, so I stop (in error)")
  1269 + self.printd(f">>>>> Thread (cmd {cmd.name}): I received the stop signal, so I stop (in error)")
1088 1270 exit(1)
1089   - print(f">>>>> Thread (cmd {cmd.name}): step #{step}/{self._thread_total_steps_number}")
  1271 + self.printd(f">>>>> Thread (cmd {cmd.name}): step #{step}/{self._thread_total_steps_number}")
1090 1272 # call a specific function to be defined by subclass
1091 1273 cmd_step_function(step)
1092 1274 # Wait for a specific time (interruptible)
... ... @@ -1095,7 +1277,7 @@ class Agent:
1095 1277 def thread_stop_if_asked(self):
1096 1278 assert self._current_specific_thread is not None
1097 1279 if self.RUN_IN_THREAD and threading.current_thread().stopped():
1098   - print("(Thread) I received the stop signal, so I stop (in error)")
  1280 + self.printd("(Thread) I received the stop signal, so I stop (in error)")
1099 1281 exit(1)
1100 1282  
1101 1283 def thread_set_total_steps_number(self, nbsteps):
... ... @@ -1129,14 +1311,34 @@ class Agent:
1129 1311 """
1130 1312  
1131 1313  
1132   -if __name__ == "__main__":
1133 1314  
  1315 +"""
  1316 +=================================================================
  1317 + MAIN
  1318 +=================================================================
  1319 +"""
  1320 +
  1321 +def extract_parameters():
  1322 + """ Usage: Agent.py [-t] [configfile] """
  1323 + # arg 1 : -t
  1324 + # arg 2 : configfile
  1325 + TEST_MODE = False
1134 1326 configfile = None
  1327 + if len(sys.argv) > 1:
  1328 + if sys.argv[1] == "-t":
  1329 + TEST_MODE = True
  1330 + if len(sys.argv) == 3:
  1331 + configfile = sys.argv[2]
  1332 + else:
  1333 + configfile = sys.argv[1]
  1334 + return TEST_MODE, configfile
1135 1335  
1136   - # arg 1 : config file
1137   - if len(sys.argv) == 2:
1138   - configfile = sys.argv[1]
1139 1336  
  1337 +if __name__ == "__main__":
  1338 +
  1339 + TEST_MODE, configfile = extract_parameters()
1140 1340 agent = Agent("GenericAgent", configfile, RUN_IN_THREAD=True)
  1341 + agent.setSimulatorMode(TEST_MODE)
1141 1342 print(agent)
1142 1343 agent.run()
  1344 +
1143 1345 \ No newline at end of file
... ...
src/agent/AgentA.py 100644 → 100755
... ... @@ -5,7 +5,8 @@ import sys
5 5 ##import utils.Logger as L
6 6  
7 7 ##from .Agent import Agent
8   -from Agent import Agent
  8 +sys.path.append("..")
  9 +from agent.Agent import Agent, extract_parameters
9 10  
10 11  
11 12 ##log = L.setupLogger("AgentXTaskLogger", "AgentX")
... ... @@ -14,28 +15,42 @@ from Agent import Agent
14 15  
15 16 class AgentA(Agent):
16 17  
17   - #MAX_DURATION_SEC = None
18   - MAX_DURATION_SEC = 85
19   -
20 18 # FOR TEST ONLY
21 19 # Run this agent in simulator mode
22   - SIMULATOR_MODE = True
  20 + SIMULATOR_MODE = False
23 21 # Run the assertion tests at the end
24   - SIMULATOR_WITH_TEST = False
  22 + SIMULATOR_WITH_TEST = True
  23 + #SIMULATOR_MAX_DURATION_SEC = None
  24 + SIMULATOR_MAX_DURATION_SEC = 120
25 25 # Who should I send commands to ?
26 26 #SIMULATOR_COMMANDS_DEST = "myself"
27 27 SIMULATOR_COMMANDS_DEST = "AgentB"
28 28 # Scenario to be executed
29   - SIMULATOR_COMMANDS = [
  29 + SIMULATOR_COMMANDS_LIST = [
  30 + # Ask receiver to delete all its previous commands
  31 + "flush_commands",
  32 +
30 33 "go_active",
31 34  
  35 + # Because of this command, the receiver agent :
  36 + # - will no more send any new command
  37 + # - will only execute "generic" commands (and not the "specific" ones)
32 38 "go_idle",
33   - # Not executed because receiver agent is now "idle"
  39 +
  40 + # Not executed (skipped) because receiver agent is now "idle"
34 41 #"specific0",
35 42  
36   - # Executed because receiver agent is now "active"
  43 + # Because of this command, the receiver agent
  44 + # will now be able to send new commands
37 45 "go_active",
38   - #"specific1",
  46 +
  47 + # Executed because receiver agent is now "active"
  48 + "specific1",
  49 +
  50 + # should abort previous command, but does not because specific1 is already executed
  51 + "abort",
  52 +
  53 + # fully executed, result is 7
39 54 "eval 4+3",
40 55  
41 56 "go_idle",
... ... @@ -161,22 +176,64 @@ class AgentA(Agent):
161 176 super().exec_specific_cmd_end(cmd, from_thread)
162 177 '''
163 178  
164   -
165   -
  179 + # @override
  180 + def simulator_test_results_main(self, commands):
  181 + nb_asserted = 0
  182 + for cmd in commands:
  183 + if cmd.name == "flush_commands":
  184 + assert cmd.is_executed()
  185 + nb_asserted+=1
  186 + # 2 times
  187 + if cmd.name == "go_active":
  188 + assert cmd.is_executed()
  189 + nb_asserted+=1
  190 + # 2 times
  191 + if cmd.name == "go_idle":
  192 + assert cmd.is_executed()
  193 + nb_asserted+=1
  194 + """
  195 + if cmd.name == "specific0":
  196 + assert cmd.is_skipped()
  197 + assert cmd.result == "in step #5/5"
  198 + nb_asserted+=1
  199 + """
  200 + if cmd.name == "specific1":
  201 + assert cmd.is_executed()
  202 + assert cmd.result == "in step #5/5"
  203 + nb_asserted+=1
  204 + if cmd.name == "eval 4+3":
  205 + assert cmd.is_executed()
  206 + assert cmd.get_result() == "7"
  207 + nb_asserted+=1
  208 + if cmd.name in ("abort"):
  209 + assert cmd.is_executed()
  210 + nb_asserted+=1
  211 + if cmd.name in ("exit"):
  212 + assert cmd.is_executed()
  213 + nb_asserted+=1
  214 + return nb_asserted
  215 +
  216 +
  217 +"""
  218 +=================================================================
  219 + MAIN FUNCTION
  220 +=================================================================
  221 +"""
166 222 if __name__ == "__main__":
167   -
168 223 # with thread
169 224 RUN_IN_THREAD=True
170 225 # with process
171   - RUN_IN_THREAD=False
  226 + #RUN_IN_THREAD=False
172 227  
  228 + TEST_MODE, configfile = extract_parameters()
  229 + """
173 230 configfile = None
174   -
175 231 # arg 1 : config file
176 232 if len(sys.argv) == 2:
177 233 configfile = sys.argv[1]
178   -
  234 + """
179 235 #agent = AgentX()
180 236 agent = AgentA("AgentA", configfile, RUN_IN_THREAD)
  237 + agent.setSimulatorMode(TEST_MODE)
181 238 print(agent)
182 239 agent.run()
... ...
src/agent/AgentB.py 100644 → 100755
... ... @@ -4,8 +4,8 @@
4 4 import sys
5 5 ##import utils.Logger as L
6 6  
7   -##from .Agent import Agent
8   -from Agent import Agent
  7 +sys.path.append("..")
  8 +from agent.Agent import Agent, extract_parameters
9 9  
10 10  
11 11 ##log = L.setupLogger("AgentXTaskLogger", "AgentX")
... ... @@ -16,16 +16,28 @@ class AgentB(Agent):
16 16  
17 17 # FOR TEST ONLY
18 18 # Run this agent in simulator mode
19   - SIMULATOR_MODE = True
  19 + SIMULATOR_MODE = False
20 20 # Run the assertion tests at the end
21   - SIMULATOR_WITH_TEST = False
  21 + SIMULATOR_WITH_TEST = True
  22 + #SIMULATOR_MAX_DURATION_SEC = None
  23 + SIMULATOR_MAX_DURATION_SEC = 120
22 24 # Who should I send commands to ?
23 25 #SIMULATOR_COMMANDS_DEST = "myself"
24 26 SIMULATOR_COMMANDS_DEST = "AgentA"
25 27 # Scenario to be executed
26   - SIMULATOR_COMMANDS = [
  28 + SIMULATOR_COMMANDS_LIST = [
  29 + "flush_commands",
27 30 "go_active",
  31 +
  32 + # Because of this command, the receiver agent
  33 + # will no more send any new command
  34 + "go_idle",
  35 + "go_idle",
28 36 "go_idle",
  37 +
  38 + # Because of this command, the receiver agent
  39 + # will now be able to send new commands
  40 + "go_active",
29 41 ]
30 42 """
31 43 "go_active",
... ... @@ -150,29 +162,37 @@ class AgentB(Agent):
150 162 self.thread_exec_specific_cmd_step(5, self.cmd_step1, 3)
151 163 ###
152 164 """
153   -
  165 +
154 166 '''
155 167 # @override
156 168 def exec_specific_cmd_end(self, cmd:Command, from_thread=True):
157 169 super().exec_specific_cmd_end(cmd, from_thread)
158 170 '''
159 171  
  172 + """
  173 + # @override
  174 + def simulator_test_results_main(self, commands):
  175 + nb_asserted = 0
  176 + for cmd in commands:
  177 + assert cmd.is_executed()
  178 + nb_asserted+=1
  179 + return nb_asserted
  180 + """
160 181  
161   -
  182 +"""
  183 +=================================================================
  184 + MAIN FUNCTION
  185 +=================================================================
  186 +"""
162 187 if __name__ == "__main__":
163 188  
164 189 # with thread
165   - #RUN_IN_THREAD=True
  190 + RUN_IN_THREAD=True
166 191 # with process
167   - RUN_IN_THREAD=False
168   -
169   - configfile = None
170   -
171   - # arg 1 : config file
172   - if len(sys.argv) == 2:
173   - configfile = sys.argv[1]
  192 + #RUN_IN_THREAD=False
174 193  
175   - #agent = AgentX()
  194 + TEST_MODE, configfile = extract_parameters()
176 195 agent = AgentB("AgentB", configfile, RUN_IN_THREAD)
  196 + agent.setSimulatorMode(TEST_MODE)
177 197 print(agent)
178 198 agent.run()
... ...
src/agent/AgentX.py
... ... @@ -8,8 +8,8 @@ import sys
8 8 #from django.db import transaction
9 9 #from common.models import Command
10 10  
11   -##from .Agent import Agent
12   -from Agent import Agent
  11 +sys.path.append("..")
  12 +from agent.Agent import Agent, extract_parameters
13 13  
14 14  
15 15  
... ... @@ -22,15 +22,17 @@ class AgentX(Agent):
22 22  
23 23 # FOR TEST ONLY
24 24 # Run this agent in simulator mode
25   - SIMULATOR_MODE = True
  25 + SIMULATOR_MODE = False
26 26 # Run the assertion tests at the end
27 27 SIMULATOR_WITH_TEST = True
  28 + #SIMULATOR_MAX_DURATION_SEC = None
  29 + SIMULATOR_MAX_DURATION_SEC = 120
28 30 '''
29 31 # Who should I send commands to ?
30 32 #SIMULATOR_COMMANDS_DEST = "myself"
31 33 SIMULATOR_COMMANDS_DEST = "AgentA"
32 34 # Scenario to be executed
33   - SIMULATOR_COMMANDS = [
  35 + SIMULATOR_COMMANDS_LIST = [
34 36 "go_active",
35 37 "go_idle",
36 38 "go_active",
... ... @@ -164,15 +166,11 @@ if __name__ == &quot;__main__&quot;:
164 166 # with thread
165 167 RUN_IN_THREAD=True
166 168 # with process
167   - RUN_IN_THREAD=False
168   -
169   - configfile = None
170   -
171   - # arg 1 : config file
172   - if len(sys.argv) == 2:
173   - configfile = sys.argv[1]
  169 + #RUN_IN_THREAD=False
174 170  
  171 + TEST_MODE, configfile = extract_parameters()
175 172 #agent = AgentX()
176 173 agent = AgentX("AgentX", configfile, RUN_IN_THREAD)
  174 + agent.setSimulatorMode(TEST_MODE)
177 175 print(agent)
178 176 agent.run()
... ...
src/common/models.py
... ... @@ -244,7 +244,7 @@ class Command(models.Model):
244 244 "CMD_KILLED", # cde ignorée (je suis idle… et j’ai ignoré cette commande, et je passe à la cde suivante)
245 245 "CMD_OUTOFDATE" # cde périmée
246 246 )
247   - GENERIC_COMMANDS = ["go_idle", "go_active", "abort", "exit"]
  247 + GENERIC_COMMANDS = ["eval", "go_idle", "go_active", "flush_commands", "abort", "exit", "restart_init"]
248 248 #COMMANDS_PEREMPTION_HOURS = 48
249 249 COMMANDS_PEREMPTION_HOURS = 60/60
250 250 COMMANDS_VALIDITY_DURATION_SEC_DEFAULT = 30
... ... @@ -254,8 +254,8 @@ class Command(models.Model):
254 254  
255 255 #sender = models.CharField(max_length=50, blank=True, null=True, unique=True)
256 256 sender = models.CharField(max_length=50, help_text='sender agent name')
257   - receiver = models.CharField(max_length=50)
258   - name = models.CharField(max_length=400)
  257 + receiver = models.CharField(max_length=50, help_text='receiver agent name')
  258 + name = models.CharField(max_length=400, help_text='command name')
259 259 validity_duration_sec = models.PositiveIntegerField(default=COMMANDS_VALIDITY_DURATION_SEC_DEFAULT)
260 260 # Automatically set at table line creation (line created by the sender)
261 261 sender_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True)
... ... @@ -283,13 +283,26 @@ class Command(models.Model):
283 283 def send(cls, cmd:Command):
284 284 cls.objects.create(sender=cmd.name, receiver=cmd.receiver, name=cmd.name)
285 285 """
  286 + @classmethod
  287 + def send_command(cls, from_agent, to_agent, cmd_name, cmd_args=None):
  288 + """
  289 + ex: send("AgentA",“AgentB”,"EVAL”,“3+4”)
  290 + """
  291 + if cmd_args: cmd_name += ' '+cmd_args
  292 + #Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name)
  293 + cmd = cls(sender=from_agent, receiver=to_agent, name=cmd_name)
  294 + cmd.send()
  295 + #cmd.set_as_pending()
  296 + #cmd.save()
  297 + return cmd
286 298  
287 299 @classmethod
288 300 def get_peremption_date_from_now(cls):
289 301 return datetime.utcnow().astimezone() - timedelta(hours = cls.COMMANDS_PEREMPTION_HOURS)
290 302  
291 303 @classmethod
292   - def delete_commands_with_running_status_if_exists_for_agent(cls, agent_name):
  304 + def delete_commands_with_running_status_for_agent(cls, agent_name):
  305 + print("Delete (false) 'running' command if exists:")
293 306 running_commands = cls.objects.filter(
294 307 # only commands for agent agent_name
295 308 receiver = agent_name,
... ... @@ -299,12 +312,38 @@ class Command(models.Model):
299 312 #sender_deposit_time__gte = cls.get_peremption_date_from_now(),
300 313 )
301 314 if running_commands:
302   - print("Delete (false) 'running' command:")
303 315 Command.show_commands(running_commands)
304 316 running_commands.delete()
  317 + else: print("<None>")
  318 +
  319 + @classmethod
  320 + def delete_pending_commands_for_agent(cls, agent_name):
  321 + """
  322 + Delete all pending commands sent to agent_name,
  323 + except very recent commands.
  324 + This (exception) is to avoid a "data race" where for example agentB is executing a "flush" command
  325 + while agentA is sending command to agentB... :
  326 + - agentB will then delete the command just sent by agentA
  327 + - agentA will check regularly the status of its sent command, and this will crash as this command exists no more !!
  328 + """
  329 + print("Delete all pending command(s) if exists (except very recent ones):")
  330 + now_minus_2sec = datetime.utcnow().astimezone() - timedelta(seconds = 2)
  331 + #print("now_minus_2sec", now_minus_2sec)
  332 + pending_commands = cls.objects.filter(
  333 + # only commands for agent agent_name
  334 + receiver = agent_name,
  335 + # only running commands
  336 + receiver_status_code = cls.CMD_STATUS_CODES.CMD_PENDING,
  337 + # except very recent commands : take only commands that are more than 2 sec old
  338 + sender_deposit_time__lt = now_minus_2sec
  339 + )
  340 + if pending_commands:
  341 + Command.show_commands(pending_commands)
  342 + pending_commands.delete()
  343 + else: print("<None>")
305 344  
306 345 @classmethod
307   - def get_pending_commands_for_agent(cls, agent_name):
  346 + def get_pending_and_running_commands_for_agent(cls, agent_name):
308 347 #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)
309 348 return cls.objects.filter(
310 349 # only pending commands
... ... @@ -317,36 +356,38 @@ class Command(models.Model):
317 356 ).order_by("sender_deposit_time")
318 357  
319 358 @classmethod
320   - def get_commands_for_agent(cls, agent_name):
321   - return cls.objects.filter(receiver = agent_name)
  359 + def get_commands_sent_to_agent(cls, agent_name):
  360 + return cls.objects.filter(receiver=agent_name)
322 361  
323 362 @classmethod
324   - def get_last_N_commands_for_agent(cls, agent_name, N):
  363 + def get_commands_sent_by_agent(cls, agent_name):
  364 + return cls.objects.filter(sender=agent_name)
  365 +
  366 + @classmethod
  367 + def get_last_N_commands_sent_to_agent(cls, agent_name, N):
325 368 #filter(since=since)
326 369 #return cls.objects.all()[:nb_cmds]
327 370 #commands = cls.objects.filter(receiver = agent_name).order_by('-id')[:N]
328   - commands = cls.get_commands_for_agent(agent_name).order_by('-id')[:N]
  371 + commands = cls.get_commands_sent_to_agent(agent_name).order_by('-id')[:N]
329 372 return list(reversed(commands))
330 373  
331 374 @classmethod
332   - def purge_old_commands_for_agent(cls, agent_name):
  375 + def get_last_N_commands_sent_by_agent(cls, agent_name, N):
  376 + #filter(since=since)
  377 + #return cls.objects.all()[:nb_cmds]
  378 + #commands = cls.objects.filter(receiver = agent_name).order_by('-id')[:N]
  379 + commands = cls.get_commands_sent_by_agent(agent_name).order_by('-id')[:N]
  380 + return list(reversed(commands))
  381 +
  382 + @classmethod
  383 + def purge_old_commands_sent_to_agent(cls, agent_name):
333 384 """
334 385 Delete commands (which agent_name is recipient of) older than COMMANDS_PEREMPTION_HOURS (like 48h)
335 386 ATTENTION !!! EXCEPT the RUNNING command !!!
336 387 NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc)
337 388 """
338 389  
339   - print(f"Looking for old commands to purge... (commands that are not executing and older than {cls.COMMANDS_PEREMPTION_HOURS} hour(s))")
340   - """
341   - COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS)
342   - #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)
343   - old_commands = Command.objects.filter(
344   - # only commands for me
345   - receiver = self.name,
346   - # only pending commands
347   - sender_deposit_time__lt = COMMAND_PEREMPTION_DATE_FROM_NOW,
348   - )
349   - """
  390 + print(f"(Looking for commands sent to me that are not executing and older than {cls.COMMANDS_PEREMPTION_HOURS} hour(s))")
350 391 #COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = cls.COMMANDS_PEREMPTION_HOURS)
351 392 #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)
352 393 old_commands = cls.objects.filter(
... ... @@ -362,6 +403,8 @@ class Command(models.Model):
362 403 #for cmd in old_commands: print(cmd)
363 404 cls.show_commands(old_commands)
364 405 old_commands.delete()
  406 + else:
  407 + print("<None>")
365 408  
366 409 @classmethod
367 410 #def show_commands(cls, commands:models.query):
... ... @@ -380,14 +423,18 @@ class Command(models.Model):
380 423  
381 424 # --- BOOLEAN (test) functions ---
382 425  
383   - def send(self): self.save()
  426 + def send(self):
  427 + #self.save()
  428 + self.set_as_pending()
384 429  
385 430 def is_generic(self):
386 431 """
387 432 Is this a generic command ?
388 433 It is the case if command is of style "go_idle" or "go_active" or "stop"...
389 434 """
390   - return self.name in self.GENERIC_COMMANDS
  435 + name = self.name
  436 + if " " in name: name,args = name.split()
  437 + return name in self.GENERIC_COMMANDS
391 438  
392 439 "CMD_OUTOFDATE" # cde périmée
393 440  
... ... @@ -425,6 +472,10 @@ class Command(models.Model):
425 472 def get_result(self):
426 473 return self.result
427 474  
  475 + def get_updated_result(self):
  476 + self.refresh_from_db()
  477 + return self.result
  478 +
428 479 def set_result(self, result:str):
429 480 self.result = result
430 481 self.save()
... ... @@ -436,7 +487,7 @@ class Command(models.Model):
436 487  
437 488 def set_as_processed(self):
438 489 print(f"- Set command {self.name} as processed")
439   - print(self)
  490 + #print(self)
440 491 self.receiver_status_code = self.CMD_STATUS_CODES.CMD_EXECUTED
441 492 self.receiver_processed_time = datetime.utcnow().astimezone()
442 493 self.save()
... ...