Commit 31e31f3b16edcd4460619064fafdb93a311ed882

Authored by Etienne Pallier
1 parent 7d679ea0
Exists in dev

pyros.py peut lancer plusieurs agents (A et B) en même temps...

- Mode opératoire :
	- pour lancer agentA seulement : ./pyros.py start agentA [-c
configfile]
	- pour lancer plusieurs agents : ./pyros.py start agentA,agentB,... [-c
configfile]
	(ou encore: activer l'environnement virtuel, puis lancer "./AgentA.py
configfile")
	- pour utiliser thread ou processus : il suffit de mettre la constante
RUN_IN_THREAD de AgentA (ou AgentB ou AgentX) à False ou True

- Scenario de test :
	- lancer agents A et B : ./pyros.py start agentA,agentB
	- attendre 1mn et attendre les 2 résultats suivants:
		(AgentA): Finished testing => result is ok
		(AgentB): Finished testing => result is ok

- Autres remarques:
	- Nouvelle commande "flush_commands" pour purger les commmandes en
attente
	- routine_process() implemented
	- Eval command implemented
	- 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
	- Chaque agent a son propre scenario de commandes à envoyer
	- GROSSE OPTIMISATION : plus besoin du script intermédiaire
"start_agent.py" !!!
			==> pyros.py lance directement "cd src/agent/ ; python AgentX.py"
@@ -74,18 +74,28 @@ Author: E. Pallier @@ -74,18 +74,28 @@ Author: E. Pallier
74 VERSION: 0.20.22 74 VERSION: 0.20.22
75 75
76 Comment: 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] 77 + pyros.py peut lancer plusieurs agents (A et B) en même temps (+ flush_commands + test)
  78 +
  79 + - Mode opératoire :
  80 + - pour lancer agentA seulement : ./pyros.py start agentA [-c configfile]
  81 + - pour lancer plusieurs agents : ./pyros.py start agentA,agentB,... [-c configfile]
84 (ou encore: activer l'environnement virtuel, puis lancer "./AgentA.py configfile") 82 (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"  
86 - pour utiliser thread ou processus : il suffit de mettre la constante RUN_IN_THREAD de AgentA (ou AgentB ou AgentX) à False ou True 83 - 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" 84 +
  85 + - Scenario de test :
  86 + - lancer agents A et B : ./pyros.py start agentA,agentB
  87 + - attendre 1mn et attendre les 2 résultats suivants:
  88 + (AgentA): Finished testing => result is ok
  89 + (AgentB): Finished testing => result is ok
  90 +
  91 + - Autres remarques:
  92 + - Nouvelle commande "flush_commands" pour purger les commmandes en attente
  93 + - routine_process() implemented
  94 + - Eval command implemented
  95 + - 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
  96 + - Chaque agent a son propre scenario de commandes à envoyer
  97 + - GROSSE OPTIMISATION : plus besoin du script intermédiaire "start_agent.py" !!!
  98 + ==> pyros.py lance directement "cd src/agent/ ; python AgentX.py"
89 99
90 -------------------------------------------------------------------------------------------- 100 --------------------------------------------------------------------------------------------
91 - TECHNICAL DOC: tinyurl.com/pyros-doc 101 - TECHNICAL DOC: tinyurl.com/pyros-doc
@@ -123,6 +123,10 @@ def execProcess(command, from_venv=False, is_async=False): @@ -123,6 +123,10 @@ def execProcess(command, from_venv=False, is_async=False):
123 printFullTerm(Colors.BLUE, "Executing command" + " [" + command + "]" + from_venv_str) 123 printFullTerm(Colors.BLUE, "Executing command" + " [" + command + "]" + from_venv_str)
124 if from_venv: command = VENV_BIN+' ' + command 124 if from_venv: command = VENV_BIN+' ' + command
125 process = subprocess.Popen(command, shell=True) 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 process.wait() 130 process.wait()
127 if process.returncode == 0: 131 if process.returncode == 0:
128 printFullTerm(Colors.GREEN, "Process executed successfully") 132 printFullTerm(Colors.GREEN, "Process executed successfully")
@@ -133,11 +137,14 @@ def execProcess(command, from_venv=False, is_async=False): @@ -133,11 +137,14 @@ def execProcess(command, from_venv=False, is_async=False):
133 #return process.returncode 137 #return process.returncode
134 return True if process.returncode==0 else False 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 #TODO: fusionner dans execProcess avec param is_async 144 #TODO: fusionner dans execProcess avec param is_async
140 def execProcessFromVenvAsync(command:str): 145 def execProcessFromVenvAsync(command:str):
  146 + return execProcessFromVenv(command, True)
  147 + """
141 args = command.split() 148 args = command.split()
142 printFullTerm( 149 printFullTerm(
143 Colors.BLUE, "Executing command from venv [" + str(" ".join(args[1:])) + "]" 150 Colors.BLUE, "Executing command from venv [" + str(" ".join(args[1:])) + "]"
@@ -148,7 +155,7 @@ def execProcessFromVenvAsync(command:str): @@ -148,7 +155,7 @@ def execProcessFromVenvAsync(command:str):
148 # self.addExecuted(self.current_command, str(' '.join(args[1:]))) 155 # self.addExecuted(self.current_command, str(' '.join(args[1:])))
149 # p.wait() 156 # p.wait()
150 return p 157 return p
151 - 158 + """
152 159
153 def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False): 160 def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False):
154 #system = platform.system() 161 #system = platform.system()
@@ -336,23 +343,16 @@ def start(agent:str, configfile:str): @@ -336,23 +343,16 @@ def start(agent:str, configfile:str):
336 if not _check_agent(a): return 343 if not _check_agent(a): return
337 print("Agents are:", agents) 344 print("Agents are:", agents)
338 # 1 agent only 345 # 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 - """ 346 + else:
  347 + agents = [agent]
  348 + if not _check_agent(agent): return
  349 +
  350 + # Start Agents (processes)
  351 + current_processes = []
352 for agent_name,agent_folder in AGENTS.items(): 352 for agent_name,agent_folder in AGENTS.items():
353 353
354 - if agent in ("all", agent_name) :  
355 - 354 + #if agent in ("all", agent_name) :
  355 + if agent=="all" or agent_name in agents:
356 # Default case, launch agentX 356 # Default case, launch agentX
357 #if agent_name == "agentX": 357 #if agent_name == "agentX":
358 358
@@ -362,6 +362,8 @@ def start(agent:str, configfile:str): @@ -362,6 +362,8 @@ def start(agent:str, configfile:str):
362 #if not test_mode(): execProcess(VENV_BIN + " manage.py runserver") 362 #if not test_mode(): execProcess(VENV_BIN + " manage.py runserver")
363 #if not test_mode(): execProcessFromVenv("start_agent_" + agent_name + ".py " + configfile) 363 #if not test_mode(): execProcessFromVenv("start_agent_" + agent_name + ".py " + configfile)
364 364
  365 + current_dir = os.getcwd()
  366 +
365 # OLD format agents: majordome, monitoring, alert... 367 # OLD format agents: majordome, monitoring, alert...
366 cmd = "start_agent.py " + agent_name + " " + configfile 368 cmd = "start_agent.py " + agent_name + " " + configfile
367 369
@@ -379,39 +381,29 @@ def start(agent:str, configfile:str): @@ -379,39 +381,29 @@ def start(agent:str, configfile:str):
379 #cmd = "-m AgentX" 381 #cmd = "-m AgentX"
380 cmd = f" Agent{agent_name[5:]}.py {configfile}" 382 cmd = f" Agent{agent_name[5:]}.py {configfile}"
381 383
382 - if not test_mode(): execProcessFromVenv(cmd) 384 + #if not test_mode(): execProcessFromVenv(cmd)
  385 + if not test_mode(): current_processes.append( (execProcessFromVenvAsync(cmd), agent_name) )
383 # self._change_dir("..") 386 # self._change_dir("..")
  387 + os.chdir(current_dir)
384 388
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 # Go back to root folder (/) 389 # Go back to root folder (/)
410 # self._change_dir('..') 390 # self._change_dir('..')
411 - os.chdir("..")  
412 - return True  
413 -  
414 - 391 + #os.chdir("..")
  392 + # Wait for end of each process execution
  393 + for (p,agent) in current_processes:
  394 + print(f"************ Waiting for end of execution of agent {agent} ************")
  395 + p.wait()
  396 + print(f"************ END of execution of agent {agent} ************")
  397 + if p.returncode == 0:
  398 + printFullTerm(Colors.GREEN, f"Process {agent} executed successfully")
  399 + # self.addExecuted(self.current_command, command)
  400 + else:
  401 + printFullTerm(Colors.WARNING, f"Process {agent} execution failed")
  402 + # self.addError(self.current_command, command)
  403 +
  404 + #print("************ end of START() ************")
  405 + # Only according to the last process status:
  406 + return True if p.returncode==0 else False
415 407
416 408
417 @pyros_launcher.command(help="Kill an agent") 409 @pyros_launcher.command(help="Kill an agent")
src/agent/Agent.py
@@ -44,7 +44,7 @@ sys.path.append("../..") @@ -44,7 +44,7 @@ sys.path.append("../..")
44 print("Starting with this sys.path", sys.path) 44 print("Starting with this sys.path", sys.path)
45 45
46 # DJANGO setup 46 # DJANGO setup
47 -# print("file is", __file__) 47 +# self.print("file is", __file__)
48 # mypath = os.getcwd() 48 # mypath = os.getcwd()
49 # Go into src/ 49 # Go into src/
50 ##os.chdir("..") 50 ##os.chdir("..")
@@ -160,7 +160,7 @@ class Agent: @@ -160,7 +160,7 @@ class Agent:
160 SIMULATOR_COMMANDS_DEST = "myself" 160 SIMULATOR_COMMANDS_DEST = "myself"
161 # Default scenario to be executed 161 # Default scenario to be executed
162 #SIMULATOR_COMMANDS = iter([ 162 #SIMULATOR_COMMANDS = iter([
163 - SIMULATOR_COMMANDS = [ 163 + SIMULATOR_COMMANDS_LIST = [
164 "go_active", 164 "go_active",
165 "go_idle", 165 "go_idle",
166 166
@@ -210,6 +210,8 @@ class Agent: @@ -210,6 +210,8 @@ class Agent:
210 "go_active", 210 "go_active",
211 "specific10" 211 "specific10"
212 ] 212 ]
  213 + #SIMULATOR_COMMANDS = iter(SIMULATOR_COMMANDS_LIST)
  214 +
213 215
214 216
215 """ 217 """
@@ -290,25 +292,25 @@ class Agent: @@ -290,25 +292,25 @@ class Agent:
290 292
291 def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True): 293 def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True):
292 self.name = name 294 self.name = name
293 - self.SIMULATOR_COMMANDS = iter(self.SIMULATOR_COMMANDS) 295 + self.SIMULATOR_COMMANDS = iter(self.SIMULATOR_COMMANDS_LIST)
294 self.RUN_IN_THREAD = RUN_IN_THREAD 296 self.RUN_IN_THREAD = RUN_IN_THREAD
295 self.set_status(self.STATUS_LAUNCH) 297 self.set_status(self.STATUS_LAUNCH)
296 self.set_mode(self.MODE_IDLE) 298 self.set_mode(self.MODE_IDLE)
297 if not config_filename: 299 if not config_filename:
298 config_filename = self.DEFAULT_CONFIG_FILE_NAME 300 config_filename = self.DEFAULT_CONFIG_FILE_NAME
299 -  
300 - print(f"config_filename={config_filename}") 301 +
  302 + self.print(f"config_filename={config_filename}")
301 # If config file name is RELATIVE (i.e. without path, just the file name) 303 # If config file name is RELATIVE (i.e. without path, just the file name)
302 # => give it an absolute path (and remove "src/agent/" from it) 304 # => give it an absolute path (and remove "src/agent/" from it)
303 if config_filename == os.path.basename(config_filename): 305 if config_filename == os.path.basename(config_filename):
304 tmp = os.path.abspath(self.CONFIG_DIR + os.sep + config_filename) 306 tmp = os.path.abspath(self.CONFIG_DIR + os.sep + config_filename)
305 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) 307 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)
306 - print("Config file used is", config_filename)  
307 - #print("current path", os.getcwd())  
308 - #print("this file path :", __file__)  
309 - #print("config file path is", config_filename) 308 + self.print("Config file used is", config_filename)
  309 + #self.print("current path", os.getcwd())
  310 + #self.print("this file path :", __file__)
  311 + #self.print("config file path is", config_filename)
310 # Instantiate an object for configuration 312 # Instantiate an object for configuration
311 - #print("config file path is ", config_abs_filename) 313 + #self.print("config file path is ", config_abs_filename)
312 self.config = ConfigPyros(config_filename) 314 self.config = ConfigPyros(config_filename)
313 if self.config.get_last_errno() != self.config.NO_ERROR: 315 if self.config.get_last_errno() != self.config.NO_ERROR:
314 raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") 316 raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}")
@@ -324,7 +326,7 @@ class Agent: @@ -324,7 +326,7 @@ class Agent:
324 #if nb_agents == 0: 326 #if nb_agents == 0:
325 if not AgentSurvey.objects.filter(name=self.name).exists(): 327 if not AgentSurvey.objects.filter(name=self.name).exists():
326 self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status) 328 self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
327 - print("Agent survey is", self._agent_survey) 329 + self.print("Agent survey is", self._agent_survey)
328 #self._agent_survey = AgentSurvey(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status) 330 #self._agent_survey = AgentSurvey(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
329 #self._agent_survey.save() 331 #self._agent_survey.save()
330 332
@@ -332,6 +334,14 @@ class Agent: @@ -332,6 +334,14 @@ class Agent:
332 def __str__(self): 334 def __str__(self):
333 return "I am agent " + self.name 335 return "I am agent " + self.name
334 336
  337 + #def print(self, msg):
  338 + def print(self, *args, **kwargs):
  339 + #print(f"({self.name}): ", msg)
  340 + if args:
  341 + print(f"({self.name}): ", *args, **kwargs)
  342 + else:
  343 + print()
  344 +
335 def sleep(self, nbsec:float=2.0): 345 def sleep(self, nbsec:float=2.0):
336 # thread 346 # thread
337 if self._current_specific_thread and self.RUN_IN_THREAD: 347 if self._current_specific_thread and self.RUN_IN_THREAD:
@@ -355,13 +365,13 @@ class Agent: @@ -355,13 +365,13 @@ class Agent:
355 365
356 # Avoid blocking on false "running" commands 366 # Avoid blocking on false "running" commands
357 # (old commands that stayed with "running" status when agent was killed) 367 # (old commands that stayed with "running" status when agent was killed)
358 - Command.delete_commands_with_running_status_if_exists_for_agent(self.name) 368 + Command.delete_commands_with_running_status_for_agent(self.name)
359 369
360 ''' 370 '''
361 - print()  
362 - print(self)  
363 - print("FOR REAL ?", self.FOR_REAL)  
364 - print("DB3 used is:", djangosettings.DATABASES["default"]["NAME"]) 371 + self.print()
  372 + self.print(self)
  373 + self.print("FOR REAL ?", self.FOR_REAL)
  374 + self.print("DB3 used is:", djangosettings.DATABASES["default"]["NAME"])
365 375
366 # SETUP 376 # SETUP
367 try: 377 try:
@@ -373,7 +383,7 @@ class Agent: @@ -373,7 +383,7 @@ class Agent:
373 # self.config = Config.objects.get()[0] 383 # self.config = Config.objects.get()[0]
374 except Exception as e: 384 except Exception as e:
375 # except Config.ObjectDoesNotExist: 385 # except Config.ObjectDoesNotExist:
376 - print("Config read (or write) exception", str(e)) 386 + self.print("Config read (or write) exception", str(e))
377 return -1 387 return -1
378 ''' 388 '''
379 389
@@ -383,15 +393,15 @@ class Agent: @@ -383,15 +393,15 @@ class Agent:
383 # Wait a random number of sec before starting new iteration 393 # Wait a random number of sec before starting new iteration
384 # (to let another agent having the chance to send a command) 394 # (to let another agent having the chance to send a command)
385 random_waiting_sec = random.randint(0,5) 395 random_waiting_sec = random.randint(0,5)
386 - print(f"Waiting {random_waiting_sec} sec (random) before starting new iteration...") 396 + self.print(f"Waiting {random_waiting_sec} sec (random) before starting new iteration...")
387 time.sleep(random_waiting_sec) 397 time.sleep(random_waiting_sec)
388 398
389 try: 399 try:
390 400
391 - print()  
392 - print()  
393 - #print("-"*80)  
394 - print("-"*20, f"MAIN LOOP ITERATION {self._iter_num} (START)", "-"*20) 401 + self.print()
  402 + self.print()
  403 + #self.print("-"*80)
  404 + self.print("-"*20, f"MAIN LOOP ITERATION {self._iter_num} (START)", "-"*20)
395 self.set_status(self.STATUS_MAIN_LOOP) 405 self.set_status(self.STATUS_MAIN_LOOP)
396 self.show_mode_and_status() 406 self.show_mode_and_status()
397 407
@@ -402,7 +412,7 @@ class Agent: @@ -402,7 +412,7 @@ class Agent:
402 #if self.SIMULATOR_MODE: self.simulator_send_next_command() 412 #if self.SIMULATOR_MODE: self.simulator_send_next_command()
403 413
404 # generic cmd in json format 414 # generic cmd in json format
405 - print("------START COMMMAND PROCESSING------") 415 + self.print("------START COMMMAND PROCESSING------")
406 cmd = self.get_next_valid_command() 416 cmd = self.get_next_valid_command()
407 #if cmd: cmd = self.general_process(cmd) 417 #if cmd: cmd = self.general_process(cmd)
408 if cmd: cmd = self.command_process(cmd) 418 if cmd: cmd = self.command_process(cmd)
@@ -412,32 +422,33 @@ class Agent: @@ -412,32 +422,33 @@ class Agent:
412 if cmd: self.specific_process(cmd) 422 if cmd: self.specific_process(cmd)
413 ''' 423 '''
414 self.routine_process() 424 self.routine_process()
415 - print("------END COMMMAND PROCESSING------") 425 + self.print("------END COMMMAND PROCESSING------")
416 426
417 # Every N iterations, delete old commands 427 # Every N iterations, delete old commands
418 N=3 428 N=3
419 - if ((self._iter_num-1) % N) == 0: Command.purge_old_commands_for_agent(self.name) 429 + if ((self._iter_num-1) % N) == 0:
  430 + self.print("Looking for old commands to purge...")
  431 + Command.purge_old_commands_for_agent(self.name)
420 432
421 #self.waitfor(self.mainloop_waittime) 433 #self.waitfor(self.mainloop_waittime)
422 434
423 - print("-"*20, "MAIN LOOP ITERATION (END)", "-"*20)  
424 - #print("-"*80) 435 + self.print("-"*20, "MAIN LOOP ITERATION (END)", "-"*20)
  436 + #self.print("-"*80)
425 437
426 #self.do_log(LOG_DEBUG, "Ending main loop iteration") 438 #self.do_log(LOG_DEBUG, "Ending main loop iteration")
427 439
428 self._iter_num += 1 440 self._iter_num += 1
429 441
430 # Exit if max duration is timed out 442 # Exit if max duration is timed out
431 - if self.MAX_DURATION_SEC:  
432 - #print ("time", time.time()-start_time)  
433 - if time.time()-start_time > self.MAX_DURATION_SEC:  
434 - print("Exit because of max duration set to ", self.MAX_DURATION_SEC, "s")  
435 - self.kill_running_specific_cmd_if_exists()  
436 - break 443 + if self.MAX_DURATION_SEC and (time.time()-start_time > self.MAX_DURATION_SEC):
  444 + self.print("Exit because of max duration set to ", self.MAX_DURATION_SEC, "s")
  445 + self.kill_running_specific_cmd_if_exists()
  446 + if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST: self.simulator_test_results()
  447 + break
437 448
438 except KeyboardInterrupt: 449 except KeyboardInterrupt:
439 # In case of CTRL-C, kill the current thread (process) before dying (in error) 450 # In case of CTRL-C, kill the current thread (process) before dying (in error)
440 - print("CTRL-C Interrupted, I kill the current thread (process) before exiting") 451 + self.print("CTRL-C Interrupted, I kill the current thread (process) before exiting")
441 self.kill_running_specific_cmd_if_exists() 452 self.kill_running_specific_cmd_if_exists()
442 exit(1) 453 exit(1)
443 454
@@ -467,7 +478,7 @@ class Agent: @@ -467,7 +478,7 @@ class Agent:
467 if self.cmdts is None: return 478 if self.cmdts is None: return
468 479
469 # 1) Send cmd 480 # 1) Send cmd
470 - print(f"Send command", self.cmdts) 481 + self.print(f"Send command", self.cmdts)
471 self.cmdts.send() 482 self.cmdts.send()
472 cmdts_is_processed = False 483 cmdts_is_processed = False
473 cmdts_res = None 484 cmdts_res = None
@@ -475,7 +486,7 @@ class Agent: @@ -475,7 +486,7 @@ class Agent:
475 # 2) Wait for end of cmd execution 486 # 2) Wait for end of cmd execution
476 #self.wait_for_execution_of_cmd(self.cmdts) 487 #self.wait_for_execution_of_cmd(self.cmdts)
477 while not cmdts_is_processed: 488 while not cmdts_is_processed:
478 - print(f"Waiting for end of cmd {self.cmdts.name} execution...") 489 + self.print(f"Waiting for end of cmd {self.cmdts.name} execution...")
479 self.cmdts.refresh_from_db() 490 self.cmdts.refresh_from_db()
480 # timeout ? 491 # timeout ?
481 if self.cmdts.is_expired(): break 492 if self.cmdts.is_expired(): break
@@ -489,9 +500,9 @@ class Agent: @@ -489,9 +500,9 @@ class Agent:
489 500
490 # 3) Get cmd result 501 # 3) Get cmd result
491 if cmdts_is_processed: 502 if cmdts_is_processed:
492 - print(f"Cmd executed. Result is '{cmdts_res}'") 503 + self.print(f"Cmd executed. Result is '{cmdts_res}'")
493 else: 504 else:
494 - print("Command was not completed") 505 + self.print("Command was not completed")
495 506
496 507
497 """ 508 """
@@ -503,10 +514,10 @@ class Agent: @@ -503,10 +514,10 @@ class Agent:
503 NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc) 514 NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc)
504 ### 515 ###
505 516
506 - print("Looking for old commands to purge...") 517 + self.print("Looking for old commands to purge...")
507 ### 518 ###
508 COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS) 519 COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS)
509 - #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW) 520 + #self.print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)
510 old_commands = Command.objects.filter( 521 old_commands = Command.objects.filter(
511 # only commands for me 522 # only commands for me
512 receiver = self.name, 523 receiver = self.name,
@@ -516,22 +527,22 @@ class Agent: @@ -516,22 +527,22 @@ class Agent:
516 ### 527 ###
517 old_commands = Command.get_old_commands_for_agent(self.name) 528 old_commands = Command.get_old_commands_for_agent(self.name)
518 if old_commands.exists(): 529 if old_commands.exists():
519 - print("Found old commands to delete:")  
520 - for cmd in old_commands: print(cmd) 530 + self.print("Found old commands to delete:")
  531 + for cmd in old_commands: self.print(cmd)
521 old_commands.delete() 532 old_commands.delete()
522 """ 533 """
523 534
524 def waitfor(self, nbsec): 535 def waitfor(self, nbsec):
525 - print(f"Now, waiting for {nbsec} seconds...") 536 + self.print(f"Now, waiting for {nbsec} seconds...")
526 time.sleep(nbsec) 537 time.sleep(nbsec)
527 538
528 def set_status(self, status:str): 539 def set_status(self, status:str):
529 - print(f"[NEW CURRENT STATUS: {status}] (switching from status {self.status})") 540 + self.print(f"[NEW CURRENT STATUS: {status}] (switching from status {self.status})")
530 self.status = status 541 self.status = status
531 return False 542 return False
532 543
533 def set_mode(self, mode:str): 544 def set_mode(self, mode:str):
534 - print(f"Switching from mode {self.mode} to mode {mode}") 545 + self.print(f"Switching from mode {self.mode} to mode {mode}")
535 self.mode = mode 546 self.mode = mode
536 547
537 def is_active(self): 548 def is_active(self):
@@ -544,7 +555,7 @@ class Agent: @@ -544,7 +555,7 @@ class Agent:
544 self.set_mode(self.MODE_IDLE) 555 self.set_mode(self.MODE_IDLE)
545 556
546 def show_mode_and_status(self): 557 def show_mode_and_status(self):
547 - print(f"CURRENT MODE is {self.mode} (with status {self.status})") 558 + self.print(f"CURRENT MODE is {self.mode} (with status {self.status})")
548 559
549 def die(self): 560 def die(self):
550 self.set_status(self.STATUS_EXIT) 561 self.set_status(self.STATUS_EXIT)
@@ -592,7 +603,7 @@ class Agent: @@ -592,7 +603,7 @@ class Agent:
592 """ 603 """
593 604
594 def init(self): 605 def init(self):
595 - print("Initializing...") 606 + self.print("Initializing...")
596 self.set_status(self.STATUS_INIT) 607 self.set_status(self.STATUS_INIT)
597 608
598 def load_config(self): 609 def load_config(self):
@@ -600,7 +611,7 @@ class Agent: @@ -600,7 +611,7 @@ class Agent:
600 TODO: 611 TODO:
601 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) 612 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)
602 """ 613 """
603 - print("Loading the config file...") 614 + self.print("Loading the config file...")
604 #config_filename = 'c:/srv/develop/pyros/config/config_unit_simulunit1.xml' 615 #config_filename = 'c:/srv/develop/pyros/config/config_unit_simulunit1.xml'
605 #config.set_configfile(config_filename) 616 #config.set_configfile(config_filename)
606 self.config.load() 617 self.config.load()
@@ -609,21 +620,21 @@ class Agent: @@ -609,21 +620,21 @@ class Agent:
609 # --- display informations 620 # --- display informations
610 # --- Get all the assembly of this unit[0] (mount + channels) 621 # --- Get all the assembly of this unit[0] (mount + channels)
611 if self.config.is_config_contents_changed(): 622 if self.config.is_config_contents_changed():
612 - print("--------- Components of the unit -----------")  
613 - print("Configuration file is {}".format(self.config.get_configfile())) 623 + self.print("--------- Components of the unit -----------")
  624 + self.print("Configuration file is {}".format(self.config.get_configfile()))
614 alias = self.config.get_aliases('unit')[0] 625 alias = self.config.get_aliases('unit')[0]
615 namevalue = self.config.get_paramvalue(alias,'unit','name') 626 namevalue = self.config.get_paramvalue(alias,'unit','name')
616 - print("Unit alias is {}. Name is {}".format(alias,namevalue), ":") 627 + self.print("Unit alias is {}. Name is {}".format(alias,namevalue), ":")
617 unit_subtags = self.config.get_unit_subtags() 628 unit_subtags = self.config.get_unit_subtags()
618 for unit_subtag in unit_subtags: 629 for unit_subtag in unit_subtags:
619 aliases = self.config.get_aliases(unit_subtag) 630 aliases = self.config.get_aliases(unit_subtag)
620 for alias in aliases: 631 for alias in aliases:
621 namevalue = self.config.get_paramvalue(alias,unit_subtag,'name') 632 namevalue = self.config.get_paramvalue(alias,unit_subtag,'name')
622 - print(f"- {unit_subtag} alias is {alias}. Name is {namevalue}")  
623 - print("------------------------------------------") 633 + self.print(f"- {unit_subtag} alias is {alias}. Name is {namevalue}")
  634 + self.print("------------------------------------------")
624 #params = self.config.get_params(unit_alias) 635 #params = self.config.get_params(unit_alias)
625 #for param in params: 636 #for param in params:
626 - # print("Unit component is {}".format(param)) 637 + # self.print("Unit component is {}".format(param))
627 638
628 """ 639 """
629 # self.config = Config.objects.get(pk=1) 640 # self.config = Config.objects.get(pk=1)
@@ -637,15 +648,15 @@ class Agent: @@ -637,15 +648,15 @@ class Agent:
637 except Exception as e: 648 except Exception as e:
638 # except Config.ObjectDoesNotExist: 649 # except Config.ObjectDoesNotExist:
639 # except Config.DoesNotExist: 650 # except Config.DoesNotExist:
640 - print("Config read (or write) exception", str(e)) 651 + self.print("Config read (or write) exception", str(e))
641 # return self.config 652 # return self.config
642 # return -1 653 # return -1
643 return False 654 return False
644 """ 655 """
645 656
646 def update_survey(self): 657 def update_survey(self):
647 - print("Updating the survey database table...")  
648 - print("- fetching table line for agent", self.name) 658 + self.print("Updating the survey database table...")
  659 + #self.print("- fetching table line for agent", self.name)
649 # only necessary when using process (not necessary with threads) 660 # only necessary when using process (not necessary with threads)
650 #with transaction.atomic(): 661 #with transaction.atomic():
651 self._agent_survey = AgentSurvey.objects.get(name=self.name) 662 self._agent_survey = AgentSurvey.objects.get(name=self.name)
@@ -653,26 +664,6 @@ class Agent: @@ -653,26 +664,6 @@ class Agent:
653 self._agent_survey.status = self.status 664 self._agent_survey.status = self.status
654 self._agent_survey.save() 665 self._agent_survey.save()
655 666
656 -  
657 - def simulator_get_next_command_to_send(self)->Command:  
658 - cmd_name = next(self.SIMULATOR_COMMANDS, None)  
659 - if cmd_name is None: return None  
660 - receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST  
661 - return Command(sender=self.name, receiver=receiver_agent, name=cmd_name)  
662 -  
663 - def simulator_send_next_command(self):  
664 - #self._current_test_cmd = "go_idle" if self._current_test_cmd=="go_active" else "go_active"  
665 - #if self._nb_test_cmds == 4: self._current_test_cmd = "exit"  
666 - cmd_name = next(self.SIMULATOR_COMMANDS, None)  
667 - #print("next cmd is ", cmd_name)  
668 - if cmd_name is None: return  
669 - #Command.objects.create(sender=self.name, receiver=self.name, name=cmd_name)  
670 - receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST  
671 - Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name)  
672 - #time.sleep(1)  
673 - #self._simulator_current_cmd_idx += 1  
674 - #self._nb_test_cmds += 1  
675 -  
676 """ 667 """
677 def send_command(self, cmd_name): 668 def send_command(self, cmd_name):
678 receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST 669 receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
@@ -686,26 +677,28 @@ class Agent: @@ -686,26 +677,28 @@ class Agent:
686 Commands are read in chronological order 677 Commands are read in chronological order
687 """ 678 """
688 self.set_status(self.STATUS_GET_NEXT_COMMAND) 679 self.set_status(self.STATUS_GET_NEXT_COMMAND)
689 - print("Looking for new commands from the database ...") 680 + self.print("Looking for new commands from the database ...")
690 681
691 # 1) Get all pending commands for me (return if None) 682 # 1) Get all pending commands for me (return if None)
692 - # Not sure this is necessary, but there might be a risk 683 + # Not sure this is necessary to do it in a transaction,
  684 + # but there might be a risk
693 # that a command status is modified while we are reading... 685 # that a command status is modified while we are reading...
694 with transaction.atomic(): 686 with transaction.atomic():
695 self._pending_commands = Command.get_pending_commands_for_agent(self.name) 687 self._pending_commands = Command.get_pending_commands_for_agent(self.name)
696 commands = self._pending_commands 688 commands = self._pending_commands
697 if not commands.exists(): 689 if not commands.exists():
698 - print("No new command to process") 690 + self.print("No new command to process")
699 return None 691 return None
700 - print("Current pending commands are (time ordered) :") 692 + self.print("Current pending commands are (time ordered) :")
701 Command.show_commands(commands) 693 Command.show_commands(commands)
702 694
703 # 2) If there is a "exit" or "abort" command pending (even at the end of the list), 695 # 2) If there is a "exit" or "abort" command pending (even at the end of the list),
704 # which is VALID (not expired), 696 # which is VALID (not expired),
705 # then pass it straight away to general_process() for execution 697 # then pass it straight away to general_process() for execution
706 - for cmd in commands:  
707 - if cmd.name in ("exit", "abort"): break  
708 - if cmd.name in ("exit", "abort") and not cmd.is_expired(): return cmd 698 + for cmd in commands:
  699 + if cmd.name in ("exit", "abort", "flush_commands"): break
  700 + if cmd.name in ("exit", "abort", "flush_commands") and not cmd.is_expired():
  701 + return cmd
709 702
710 # 3) If first (oldest) command is currently running 703 # 3) If first (oldest) command is currently running
711 # (status CMD_RUNNING), then do nothing and return 704 # (status CMD_RUNNING), then do nothing and return
@@ -721,16 +714,16 @@ class Agent: @@ -721,16 +714,16 @@ class Agent:
721 """ 714 """
722 cmd = commands[0] 715 cmd = commands[0]
723 if cmd.is_running(): 716 if cmd.is_running():
724 - #print(f"There is currently a running command ({cmd_executing.first().name}), so I do nothing (wait for end of execution)")  
725 - print(f"There is currently a running command ({cmd.name})") 717 + #self.print(f"There is currently a running command ({cmd_executing.first().name}), so I do nothing (wait for end of execution)")
  718 + self.print(f"There is currently a running command ({cmd.name})")
726 """ 719 """
727 # Check that this command is not expired 720 # Check that this command is not expired
728 if cmd.is_expired(): 721 if cmd.is_expired():
729 - print("But this command is expired, so set its status to OUTOFDATE, and go on") 722 + self.print("But this command is expired, so set its status to OUTOFDATE, and go on")
730 cmd_executing.set_as_outofdate() 723 cmd_executing.set_as_outofdate()
731 else: 724 else:
732 """ 725 """
733 - print(f"Thus, I will do nothing until this command execution is finished") 726 + self.print(f"Thus, I will do nothing until this command execution is finished")
734 # TODO: kill si superieur a MAX_EXEC_TIME 727 # TODO: kill si superieur a MAX_EXEC_TIME
735 return None 728 return None
736 729
@@ -745,10 +738,10 @@ class Agent: @@ -745,10 +738,10 @@ class Agent:
745 738
746 # 6) Current cmd must now be a valid (not expired) and PENDING one, 739 # 6) Current cmd must now be a valid (not expired) and PENDING one,
747 # so pass it to general_process() for execution 740 # so pass it to general_process() for execution
748 - #print(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")  
749 - print("***")  
750 - print("*** Got", cmd)  
751 - print("***") 741 + #self.print(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
  742 + self.print("***")
  743 + self.print("*** Got", cmd)
  744 + self.print("***")
752 return cmd 745 return cmd
753 746
754 747
@@ -762,34 +755,35 @@ class Agent: @@ -762,34 +755,35 @@ class Agent:
762 def general_process(self, cmd:Command)->Command: 755 def general_process(self, cmd:Command)->Command:
763 756
764 self.set_status(self.STATUS_GENERAL_PROCESS) 757 self.set_status(self.STATUS_GENERAL_PROCESS)
765 - print(f"Starting general processing of {cmd}") 758 + self.print(f"Starting general processing of {cmd}")
766 759
767 # Update read time to say that the command has been READ 760 # Update read time to say that the command has been READ
768 cmd.set_read_time() 761 cmd.set_read_time()
769 # Precondition: command cmd is valid (not expired), has already been read, is pending 762 # Precondition: command cmd is valid (not expired), has already been read, is pending
770 assert (not cmd.is_expired()) and cmd.is_pending() and cmd.is_read() 763 assert (not cmd.is_expired()) and cmd.is_pending() and cmd.is_read()
771 764
772 - #print(f"Starting general processing of command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}") 765 + #self.print(f"Starting general processing of command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
773 766
774 """ 767 """
775 # 2) If expired command, change its status to expired and return 768 # 2) If expired command, change its status to expired and return
776 if cmd.is_expired(): 769 if cmd.is_expired():
777 - print("This command is expired, so mark it as such, and ignore it") 770 + self.print("This command is expired, so mark it as such, and ignore it")
778 cmd.set_as_outofdate() 771 cmd.set_as_outofdate()
779 return None 772 return None
780 """ 773 """
781 774
782 # If cmd is generic, execute it, change its status to executed, and return 775 # If cmd is generic, execute it, change its status to executed, and return
783 if cmd.is_generic(): 776 if cmd.is_generic():
784 - print("This command is generic, execute it...") 777 + self.print("This command is generic, execute it...")
785 self.exec_generic_cmd(cmd) 778 self.exec_generic_cmd(cmd)
786 # If cmd is "exit", kill myself (without any question, this is an order soldier !) 779 # If cmd is "exit", kill myself (without any question, this is an order soldier !)
787 # This "exit" should normally kill any current thread (to be checked...) 780 # This "exit" should normally kill any current thread (to be checked...)
788 if cmd.name == "exit": 781 if cmd.name == "exit":
789 - print("(before exiting) Here are the current (still) pending commands (time ordered) :") 782 + self.print("(before exiting) Here are the current (still) pending commands (time ordered) :")
790 commands = Command.get_pending_commands_for_agent(self.name) 783 commands = Command.get_pending_commands_for_agent(self.name)
791 Command.show_commands(commands) 784 Command.show_commands(commands)
792 - if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST and self.SIMULATOR_COMMANDS_DEST == "myself": self.simulator_test_results() 785 + #if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST and self.SIMULATOR_COMMANDS_DEST == "myself": self.simulator_test_results()
  786 + if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST: self.simulator_test_results()
793 exit(0) 787 exit(0)
794 # Command is executed, so return None 788 # Command is executed, so return None
795 return None 789 return None
@@ -799,19 +793,19 @@ class Agent: @@ -799,19 +793,19 @@ class Agent:
799 # cmd is not generic but, as I am idle, change its status to SKIPPED, ignore it, and return 793 # cmd is not generic but, as I am idle, change its status to SKIPPED, ignore it, and return
800 #if self.mode == self.MODE_IDLE: 794 #if self.mode == self.MODE_IDLE:
801 if not self.is_active(): 795 if not self.is_active():
802 - print("This command is not generic but, as I am IDLE, I mark it SKIPPED and ignore it") 796 + self.print("This command is not generic but, as I am IDLE, I mark it SKIPPED and ignore it")
803 cmd.set_as_skipped() 797 cmd.set_as_skipped()
804 return None 798 return None
805 799
806 # Je suis pas idle et cde pas générique: je la traite pas, elle sera traitée par core_process : 800 # Je suis pas idle et cde pas générique: je la traite pas, elle sera traitée par core_process :
807 # attendre que cette commande soit exécutée avant de passer à la commande suivante (situation “bloquante” normale) 801 # attendre que cette commande soit exécutée avant de passer à la commande suivante (situation “bloquante” normale)
808 - print("This command is not generic and, as I am not IDLE, I pass it to the specific processing")  
809 - print("(then I will not execute any other new command until this command is EXECUTED)") 802 + self.print("This command is not generic and, as I am not IDLE, I pass it to the specific processing")
  803 + self.print("(then I will not execute any other new command until this command is EXECUTED)")
810 return cmd 804 return cmd
811 805
812 806
813 def exec_generic_cmd(self, cmd:Command): 807 def exec_generic_cmd(self, cmd:Command):
814 - print("Starting execution of a Generic cmd...") 808 + self.print("Starting execution of a Generic cmd...")
815 cmd.set_as_running() 809 cmd.set_as_running()
816 810
817 # Executing command 811 # Executing command
@@ -819,19 +813,22 @@ class Agent: @@ -819,19 +813,22 @@ class Agent:
819 self.set_active() 813 self.set_active()
820 cmd.set_result("I am now active") 814 cmd.set_result("I am now active")
821 time.sleep(1) 815 time.sleep(1)
822 - if cmd.name == "go_idle": 816 + elif cmd.name == "go_idle":
823 self.set_idle() 817 self.set_idle()
824 cmd.set_result("I am now idle") 818 cmd.set_result("I am now idle")
825 time.sleep(1) 819 time.sleep(1)
826 - # If cmd is "abort", kill any currently running thread  
827 - if cmd.name in ("abort", "exit"):  
828 - #print("Current pending commands are:") 820 + elif cmd.name in ("flush_commands"):
  821 + self.print("flush_commands received: Delete all pending commands")
  822 + Command.delete_pending_commands_for_agent(self.name)
  823 + # If cmd is "abort" or "exit", kill any currently running thread
  824 + elif cmd.name in ("abort", "exit"):
  825 + #self.print("Current pending commands are:")
829 #Command.show_commands(self._pending_commands) 826 #Command.show_commands(self._pending_commands)
830 - print("Aborting current executing command if exists:") 827 + self.print("Aborting current executing command if exists:")
831 self.kill_running_specific_cmd_if_exists() 828 self.kill_running_specific_cmd_if_exists()
832 829
833 cmd.set_as_processed() 830 cmd.set_as_processed()
834 - print("...Generic cmd has been executed") 831 + self.print("...Generic cmd has been executed")
835 832
836 833
837 834
@@ -842,7 +839,7 @@ class Agent: @@ -842,7 +839,7 @@ class Agent:
842 - in file 839 - in file
843 - in db 840 - in db
844 """ 841 """
845 - print("Logging data...") 842 + self.print("Logging data...")
846 843
847 844
848 845
@@ -860,11 +857,11 @@ class Agent: @@ -860,11 +857,11 @@ class Agent:
860 self.set_status(self.STATUS_SPECIFIC_PROCESS) 857 self.set_status(self.STATUS_SPECIFIC_PROCESS)
861 assert self.is_active() 858 assert self.is_active()
862 self._current_specific_cmd = cmd 859 self._current_specific_cmd = cmd
863 - print("Starting specific process...") 860 + self.print("Starting specific process...")
864 #self._current_thread = threading.Thread(target=self.exec_command) 861 #self._current_thread = threading.Thread(target=self.exec_command)
865 # Run in a thread 862 # Run in a thread
866 if self.RUN_IN_THREAD: 863 if self.RUN_IN_THREAD:
867 - print("(run cmd in a thread)") 864 + self.print("(run cmd in a thread)")
868 self._current_specific_thread = StoppableThreadEvenWhenSleeping(target=self.thread_exec_specific_cmd) 865 self._current_specific_thread = StoppableThreadEvenWhenSleeping(target=self.thread_exec_specific_cmd)
869 #self._current_specific_thread = StoppableThreadEvenWhenSleeping(target=self.exec_specific_cmd, args=(cmd,)) 866 #self._current_specific_thread = StoppableThreadEvenWhenSleeping(target=self.exec_specific_cmd, args=(cmd,))
870 #self._current_thread = threading.Thread(target=self.exec_command) 867 #self._current_thread = threading.Thread(target=self.exec_command)
@@ -873,7 +870,7 @@ class Agent: @@ -873,7 +870,7 @@ class Agent:
873 #self._current_specific_thread = thread_with_exception('thread test') 870 #self._current_specific_thread = thread_with_exception('thread test')
874 # Run in a process 871 # Run in a process
875 else: 872 else:
876 - print("(run cmd in a process)") 873 + self.print("(run cmd in a process)")
877 # close the database connection first, it will be re-opened in each process 874 # close the database connection first, it will be re-opened in each process
878 db.connections.close_all() 875 db.connections.close_all()
879 self._current_specific_thread = multiprocessing.Process(target=self.thread_exec_specific_cmd) 876 self._current_specific_thread = multiprocessing.Process(target=self.thread_exec_specific_cmd)
@@ -883,14 +880,14 @@ class Agent: @@ -883,14 +880,14 @@ class Agent:
883 self._current_specific_thread.start() 880 self._current_specific_thread.start()
884 #my_thread.join() 881 #my_thread.join()
885 #self.waitfor(self.subloop_waittime) 882 #self.waitfor(self.subloop_waittime)
886 - print("Ending specific process (thread has been launched)") 883 + self.print("Ending specific process (thread has been launched)")
887 884
888 885
889 def kill_running_specific_cmd_if_exists(self): 886 def kill_running_specific_cmd_if_exists(self):
890 if (self._current_specific_thread is None) or not self._current_specific_thread.is_alive(): 887 if (self._current_specific_thread is None) or not self._current_specific_thread.is_alive():
891 - print("...No current specific command thread to abort...") 888 + self.print("...No current specific command thread to abort...")
892 else: 889 else:
893 - print(f"Killing command {self._current_specific_cmd.name}") 890 + self.print(f"Killing command {self._current_specific_cmd.name}")
894 # Ask the thread to stop itself 891 # Ask the thread to stop itself
895 #self._current_specific_thread.stop() 892 #self._current_specific_thread.stop()
896 #self._current_specific_thread._stop() 893 #self._current_specific_thread._stop()
@@ -907,13 +904,42 @@ class Agent: @@ -907,13 +904,42 @@ class Agent:
907 self._current_specific_cmd = None 904 self._current_specific_cmd = None
908 905
909 906
  907 + """
  908 + =================================================================
  909 + SIMULATOR DEDICATED FUNCTIONS
  910 + =================================================================
  911 + """
  912 +
  913 + def simulator_get_next_command_to_send(self)->Command:
  914 + cmd_name = next(self.SIMULATOR_COMMANDS, None)
  915 + if cmd_name is None: return None
  916 + receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
  917 + return Command(sender=self.name, receiver=receiver_agent, name=cmd_name)
  918 +
  919 + """
  920 + def simulator_send_next_command(self):
  921 + #self._current_test_cmd = "go_idle" if self._current_test_cmd=="go_active" else "go_active"
  922 + #if self._nb_test_cmds == 4: self._current_test_cmd = "exit"
  923 + cmd_name = next(self.SIMULATOR_COMMANDS, None)
  924 + #self.print("next cmd is ", cmd_name)
  925 + if cmd_name is None: return
  926 + #Command.objects.create(sender=self.name, receiver=self.name, name=cmd_name)
  927 + receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST
  928 + Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name)
  929 + #time.sleep(1)
  930 + #self._simulator_current_cmd_idx += 1
  931 + #self._nb_test_cmds += 1
  932 + """
  933 +
910 def simulator_test_results(self): 934 def simulator_test_results(self):
911 - print("\n--- Starting testing if result is ok")  
912 - print("Here are the 14 last commands:") 935 + self.print("\n--- Testing if the commands I SENT had the awaited result")
  936 + self.print("Here are the last commands I sent:")
913 #commands = list(Command.get_last_N_commands_for_agent(self.name, 16)) 937 #commands = list(Command.get_last_N_commands_for_agent(self.name, 16))
914 - commands = Command.get_last_N_commands_for_agent(self.name, 16)  
915 - Command.show_commands(commands) 938 + #commands = Command.get_last_N_commands_sent_to_agent(self.name, 16)
  939 + commands = Command.get_last_N_commands_sent_by_agent(self.name, len(self.SIMULATOR_COMMANDS_LIST))
916 Command.show_commands(commands) 940 Command.show_commands(commands)
  941 + return commands
  942 + """ OLD SCENARIO
917 nb_asserted = 0 943 nb_asserted = 0
918 for cmd in commands: 944 for cmd in commands:
919 if cmd.name == "specific0": 945 if cmd.name == "specific0":
@@ -937,7 +963,8 @@ class Agent: @@ -937,7 +963,8 @@ class Agent:
937 assert cmd.is_executed() 963 assert cmd.is_executed()
938 nb_asserted+=1 964 nb_asserted+=1
939 assert nb_asserted == 12 965 assert nb_asserted == 12
940 - print("--- Finished testing => result is ok") 966 + self.print("--- Finished testing => result is ok")
  967 + """
941 968
942 969
943 970
@@ -965,8 +992,8 @@ class Agent: @@ -965,8 +992,8 @@ class Agent:
965 cmd = self._current_specific_cmd 992 cmd = self._current_specific_cmd
966 """ specific command execution setting up """ 993 """ specific command execution setting up """
967 #cmd = self.get_current_specific_cmd() 994 #cmd = self.get_current_specific_cmd()
968 - print(">>>>> Thread: starting execution of command", cmd.name)  
969 - print(">>>>> Thread: PID: %s, Process Name: %s, Thread Name: %s" % ( 995 + self.print(">>>>> Thread: starting execution of command", cmd.name)
  996 + self.print(">>>>> Thread: PID: %s, Process Name: %s, Thread Name: %s" % (
970 os.getpid(), 997 os.getpid(),
971 multiprocessing.current_process().name, 998 multiprocessing.current_process().name,
972 threading.current_thread().name) 999 threading.current_thread().name)
@@ -1004,7 +1031,7 @@ class Agent: @@ -1004,7 +1031,7 @@ class Agent:
1004 def thread_exec_specific_cmd_main(self): 1031 def thread_exec_specific_cmd_main(self):
1005 """ 1032 """
1006 cmd = self._current_specific_cmd 1033 cmd = self._current_specific_cmd
1007 - print("Doing nothing, just sleeping...") 1034 + self.print("Doing nothing, just sleeping...")
1008 self.sleep(3) 1035 self.sleep(3)
1009 """ 1036 """
1010 1037
@@ -1047,7 +1074,7 @@ class Agent: @@ -1047,7 +1074,7 @@ class Agent:
1047 with transaction.atomic(): 1074 with transaction.atomic():
1048 cmd.set_as_processed() 1075 cmd.set_as_processed()
1049 """ 1076 """
1050 - print(">>>>> Thread: ended execution of command", cmd.name) 1077 + self.print(">>>>> Thread: ended execution of command", cmd.name)
1051 cmd = None 1078 cmd = None
1052 # No more current thread 1079 # No more current thread
1053 #self._current_specific_thread = None 1080 #self._current_specific_thread = None
@@ -1058,9 +1085,9 @@ class Agent: @@ -1058,9 +1085,9 @@ class Agent:
1058 # Exit if I was asked to stop 1085 # Exit if I was asked to stop
1059 cmd = self._current_specific_cmd 1086 cmd = self._current_specific_cmd
1060 if self.RUN_IN_THREAD and threading.current_thread().stopped(): 1087 if self.RUN_IN_THREAD and threading.current_thread().stopped():
1061 - print(f">>>>> Thread (cmd {cmd.name}): I received the stop signal, so I stop (in error)") 1088 + self.print(f">>>>> Thread (cmd {cmd.name}): I received the stop signal, so I stop (in error)")
1062 exit(1) 1089 exit(1)
1063 - print(f">>>>> Thread (cmd {cmd.name}): step #{step}/{self._thread_total_steps_number}") 1090 + self.print(f">>>>> Thread (cmd {cmd.name}): step #{step}/{self._thread_total_steps_number}")
1064 # call a specific function to be defined by subclass 1091 # call a specific function to be defined by subclass
1065 cmd_step_function(step) 1092 cmd_step_function(step)
1066 # Wait for a specific time (interruptible) 1093 # Wait for a specific time (interruptible)
@@ -1069,7 +1096,7 @@ class Agent: @@ -1069,7 +1096,7 @@ class Agent:
1069 def thread_stop_if_asked(self): 1096 def thread_stop_if_asked(self):
1070 assert self._current_specific_thread is not None 1097 assert self._current_specific_thread is not None
1071 if self.RUN_IN_THREAD and threading.current_thread().stopped(): 1098 if self.RUN_IN_THREAD and threading.current_thread().stopped():
1072 - print("(Thread) I received the stop signal, so I stop (in error)") 1099 + self.print("(Thread) I received the stop signal, so I stop (in error)")
1073 exit(1) 1100 exit(1)
1074 1101
1075 def thread_set_total_steps_number(self, nbsteps): 1102 def thread_set_total_steps_number(self, nbsteps):
@@ -1103,6 +1130,11 @@ class Agent: @@ -1103,6 +1130,11 @@ class Agent:
1103 """ 1130 """
1104 1131
1105 1132
  1133 +"""
  1134 +=================================================================
  1135 + MAIN FUNCTION
  1136 +=================================================================
  1137 +"""
1106 if __name__ == "__main__": 1138 if __name__ == "__main__":
1107 1139
1108 configfile = None 1140 configfile = None
src/agent/AgentA.py
@@ -15,27 +15,36 @@ from Agent import Agent @@ -15,27 +15,36 @@ from Agent import Agent
15 class AgentA(Agent): 15 class AgentA(Agent):
16 16
17 #MAX_DURATION_SEC = None 17 #MAX_DURATION_SEC = None
18 - MAX_DURATION_SEC = 85 18 + MAX_DURATION_SEC = 90
19 19
20 # FOR TEST ONLY 20 # FOR TEST ONLY
21 # Run this agent in simulator mode 21 # Run this agent in simulator mode
22 SIMULATOR_MODE = True 22 SIMULATOR_MODE = True
23 # Run the assertion tests at the end 23 # Run the assertion tests at the end
24 - SIMULATOR_WITH_TEST = False 24 + SIMULATOR_WITH_TEST = True
25 # Who should I send commands to ? 25 # Who should I send commands to ?
26 #SIMULATOR_COMMANDS_DEST = "myself" 26 #SIMULATOR_COMMANDS_DEST = "myself"
27 SIMULATOR_COMMANDS_DEST = "AgentB" 27 SIMULATOR_COMMANDS_DEST = "AgentB"
28 # Scenario to be executed 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 "go_active", 33 "go_active",
31 34
32 "go_idle", 35 "go_idle",
33 # Not executed because receiver agent is now "idle" 36 # Not executed because receiver agent is now "idle"
34 #"specific0", 37 #"specific0",
35 38
36 - # Executed because receiver agent is now "active"  
37 "go_active", 39 "go_active",
38 - #"specific1", 40 +
  41 + # Executed because receiver agent is now "active"
  42 + "specific1",
  43 +
  44 + # should abort previous command, but does not because specific1 is already executed
  45 + "abort",
  46 +
  47 + # fully executed, result is 7
39 "eval 4+3", 48 "eval 4+3",
40 49
41 "go_idle", 50 "go_idle",
@@ -161,14 +170,68 @@ class AgentA(Agent): @@ -161,14 +170,68 @@ class AgentA(Agent):
161 super().exec_specific_cmd_end(cmd, from_thread) 170 super().exec_specific_cmd_end(cmd, from_thread)
162 ''' 171 '''
163 172
  173 + # @override
  174 + def simulator_test_results(self):
  175 + commands = super().simulator_test_results()
  176 + #self.print(commands)
  177 + """
  178 + "go_active",
164 179
  180 + "go_idle",
  181 + # Not executed because receiver agent is now "idle"
  182 + #"specific0",
  183 +
  184 + # Executed because receiver agent is now "active"
  185 + "go_active",
  186 + #"specific1",
  187 + "eval 4+3",
165 188
  189 + "go_idle",
  190 + "exit",
  191 + """
  192 + nb_asserted = 0
  193 + for cmd in commands:
  194 + if cmd.name == "flush_commands":
  195 + assert cmd.is_executed()
  196 + nb_asserted+=1
  197 + # 2 times
  198 + if cmd.name == "go_active":
  199 + assert cmd.is_executed()
  200 + nb_asserted+=1
  201 + # 2 times
  202 + if cmd.name == "go_idle":
  203 + assert cmd.is_executed()
  204 + nb_asserted+=1
  205 + if cmd.name == "specific1":
  206 + assert cmd.is_executed()
  207 + assert cmd.result == "in step #5/5"
  208 + nb_asserted+=1
  209 + if cmd.name == "eval 4+3":
  210 + assert cmd.is_executed()
  211 + assert cmd.get_result() == "7"
  212 + nb_asserted+=1
  213 + if cmd.name in ("abort"):
  214 + assert cmd.is_executed()
  215 + nb_asserted+=1
  216 + if cmd.name in ("exit"):
  217 + assert cmd.is_executed()
  218 + nb_asserted+=1
  219 + #assert nb_asserted == 6
  220 + assert nb_asserted == len(self.SIMULATOR_COMMANDS_LIST)
  221 + self.print("************** Finished testing => result is ok **************")
  222 +
  223 +
  224 +"""
  225 +=================================================================
  226 + MAIN FUNCTION
  227 +=================================================================
  228 +"""
166 if __name__ == "__main__": 229 if __name__ == "__main__":
167 230
168 # with thread 231 # with thread
169 RUN_IN_THREAD=True 232 RUN_IN_THREAD=True
170 # with process 233 # with process
171 - RUN_IN_THREAD=False 234 + #RUN_IN_THREAD=False
172 235
173 configfile = None 236 configfile = None
174 237
src/agent/AgentB.py
@@ -14,16 +14,19 @@ from Agent import Agent @@ -14,16 +14,19 @@ from Agent import Agent
14 14
15 class AgentB(Agent): 15 class AgentB(Agent):
16 16
  17 + #MAX_DURATION_SEC = None
  18 + MAX_DURATION_SEC = 120
  19 +
17 # FOR TEST ONLY 20 # FOR TEST ONLY
18 # Run this agent in simulator mode 21 # Run this agent in simulator mode
19 SIMULATOR_MODE = True 22 SIMULATOR_MODE = True
20 # Run the assertion tests at the end 23 # Run the assertion tests at the end
21 - SIMULATOR_WITH_TEST = False 24 + SIMULATOR_WITH_TEST = True
22 # Who should I send commands to ? 25 # Who should I send commands to ?
23 #SIMULATOR_COMMANDS_DEST = "myself" 26 #SIMULATOR_COMMANDS_DEST = "myself"
24 SIMULATOR_COMMANDS_DEST = "AgentA" 27 SIMULATOR_COMMANDS_DEST = "AgentA"
25 # Scenario to be executed 28 # Scenario to be executed
26 - SIMULATOR_COMMANDS = [ 29 + SIMULATOR_COMMANDS_LIST = [
27 "go_active", 30 "go_active",
28 "go_idle", 31 "go_idle",
29 ] 32 ]
@@ -150,15 +153,30 @@ class AgentB(Agent): @@ -150,15 +153,30 @@ class AgentB(Agent):
150 self.thread_exec_specific_cmd_step(5, self.cmd_step1, 3) 153 self.thread_exec_specific_cmd_step(5, self.cmd_step1, 3)
151 ### 154 ###
152 """ 155 """
153 - 156 +
154 ''' 157 '''
155 # @override 158 # @override
156 def exec_specific_cmd_end(self, cmd:Command, from_thread=True): 159 def exec_specific_cmd_end(self, cmd:Command, from_thread=True):
157 super().exec_specific_cmd_end(cmd, from_thread) 160 super().exec_specific_cmd_end(cmd, from_thread)
158 ''' 161 '''
159 162
160 -  
161 - 163 + # @override
  164 + def simulator_test_results(self):
  165 + commands = super().simulator_test_results()
  166 + nb_asserted = 0
  167 + for cmd in commands:
  168 + assert cmd.is_executed()
  169 + nb_asserted+=1
  170 + #assert nb_asserted == 2
  171 + assert nb_asserted == len(self.SIMULATOR_COMMANDS_LIST)
  172 + self.print("************** Finished testing => result is ok **************")
  173 +
  174 +
  175 +"""
  176 +=================================================================
  177 + MAIN FUNCTION
  178 +=================================================================
  179 +"""
162 if __name__ == "__main__": 180 if __name__ == "__main__":
163 181
164 # with thread 182 # with thread
src/agent/AgentX.py
@@ -30,7 +30,7 @@ class AgentX(Agent): @@ -30,7 +30,7 @@ class AgentX(Agent):
30 #SIMULATOR_COMMANDS_DEST = "myself" 30 #SIMULATOR_COMMANDS_DEST = "myself"
31 SIMULATOR_COMMANDS_DEST = "AgentA" 31 SIMULATOR_COMMANDS_DEST = "AgentA"
32 # Scenario to be executed 32 # Scenario to be executed
33 - SIMULATOR_COMMANDS = [ 33 + SIMULATOR_COMMANDS_LIST = [
34 "go_active", 34 "go_active",
35 "go_idle", 35 "go_idle",
36 "go_active", 36 "go_active",
src/common/models.py
@@ -244,7 +244,7 @@ class Command(models.Model): @@ -244,7 +244,7 @@ class Command(models.Model):
244 "CMD_KILLED", # cde ignorée (je suis idle… et j’ai ignoré cette commande, et je passe à la cde suivante) 244 "CMD_KILLED", # cde ignorée (je suis idle… et j’ai ignoré cette commande, et je passe à la cde suivante)
245 "CMD_OUTOFDATE" # cde périmée 245 "CMD_OUTOFDATE" # cde périmée
246 ) 246 )
247 - GENERIC_COMMANDS = ["go_idle", "go_active", "abort", "exit"] 247 + GENERIC_COMMANDS = ["go_idle", "go_active", "flush_commands", "abort", "exit"]
248 #COMMANDS_PEREMPTION_HOURS = 48 248 #COMMANDS_PEREMPTION_HOURS = 48
249 COMMANDS_PEREMPTION_HOURS = 60/60 249 COMMANDS_PEREMPTION_HOURS = 60/60
250 COMMANDS_VALIDITY_DURATION_SEC_DEFAULT = 30 250 COMMANDS_VALIDITY_DURATION_SEC_DEFAULT = 30
@@ -254,8 +254,8 @@ class Command(models.Model): @@ -254,8 +254,8 @@ class Command(models.Model):
254 254
255 #sender = models.CharField(max_length=50, blank=True, null=True, unique=True) 255 #sender = models.CharField(max_length=50, blank=True, null=True, unique=True)
256 sender = models.CharField(max_length=50, help_text='sender agent name') 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 validity_duration_sec = models.PositiveIntegerField(default=COMMANDS_VALIDITY_DURATION_SEC_DEFAULT) 259 validity_duration_sec = models.PositiveIntegerField(default=COMMANDS_VALIDITY_DURATION_SEC_DEFAULT)
260 # Automatically set at table line creation (line created by the sender) 260 # Automatically set at table line creation (line created by the sender)
261 sender_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True) 261 sender_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True)
@@ -289,7 +289,7 @@ class Command(models.Model): @@ -289,7 +289,7 @@ class Command(models.Model):
289 return datetime.utcnow().astimezone() - timedelta(hours = cls.COMMANDS_PEREMPTION_HOURS) 289 return datetime.utcnow().astimezone() - timedelta(hours = cls.COMMANDS_PEREMPTION_HOURS)
290 290
291 @classmethod 291 @classmethod
292 - def delete_commands_with_running_status_if_exists_for_agent(cls, agent_name): 292 + def delete_commands_with_running_status_for_agent(cls, agent_name):
293 running_commands = cls.objects.filter( 293 running_commands = cls.objects.filter(
294 # only commands for agent agent_name 294 # only commands for agent agent_name
295 receiver = agent_name, 295 receiver = agent_name,
@@ -302,6 +302,23 @@ class Command(models.Model): @@ -302,6 +302,23 @@ class Command(models.Model):
302 print("Delete (false) 'running' command:") 302 print("Delete (false) 'running' command:")
303 Command.show_commands(running_commands) 303 Command.show_commands(running_commands)
304 running_commands.delete() 304 running_commands.delete()
  305 + else: print("<None>")
  306 +
  307 + @classmethod
  308 + def delete_pending_commands_for_agent(cls, agent_name):
  309 + pending_commands = cls.objects.filter(
  310 + # only commands for agent agent_name
  311 + receiver = agent_name,
  312 + # only running commands
  313 + receiver_status_code = cls.CMD_STATUS_CODES.CMD_PENDING,
  314 + # only not expired commands
  315 + #sender_deposit_time__gte = cls.get_peremption_date_from_now(),
  316 + )
  317 + if pending_commands:
  318 + print("Delete these pending command(s):")
  319 + Command.show_commands(pending_commands)
  320 + pending_commands.delete()
  321 + else: print("<None>")
305 322
306 @classmethod 323 @classmethod
307 def get_pending_commands_for_agent(cls, agent_name): 324 def get_pending_commands_for_agent(cls, agent_name):
@@ -317,15 +334,27 @@ class Command(models.Model): @@ -317,15 +334,27 @@ class Command(models.Model):
317 ).order_by("sender_deposit_time") 334 ).order_by("sender_deposit_time")
318 335
319 @classmethod 336 @classmethod
320 - def get_commands_for_agent(cls, agent_name):  
321 - return cls.objects.filter(receiver = agent_name) 337 + def get_commands_sent_to_agent(cls, agent_name):
  338 + return cls.objects.filter(receiver=agent_name)
  339 +
  340 + @classmethod
  341 + def get_commands_sent_by_agent(cls, agent_name):
  342 + return cls.objects.filter(sender=agent_name)
  343 +
  344 + @classmethod
  345 + def get_last_N_commands_sent_to_agent(cls, agent_name, N):
  346 + #filter(since=since)
  347 + #return cls.objects.all()[:nb_cmds]
  348 + #commands = cls.objects.filter(receiver = agent_name).order_by('-id')[:N]
  349 + commands = cls.get_commands_sent_to_agent(agent_name).order_by('-id')[:N]
  350 + return list(reversed(commands))
322 351
323 @classmethod 352 @classmethod
324 - def get_last_N_commands_for_agent(cls, agent_name, N): 353 + def get_last_N_commands_sent_by_agent(cls, agent_name, N):
325 #filter(since=since) 354 #filter(since=since)
326 #return cls.objects.all()[:nb_cmds] 355 #return cls.objects.all()[:nb_cmds]
327 #commands = cls.objects.filter(receiver = agent_name).order_by('-id')[:N] 356 #commands = cls.objects.filter(receiver = agent_name).order_by('-id')[:N]
328 - commands = cls.get_commands_for_agent(agent_name).order_by('-id')[:N] 357 + commands = cls.get_commands_sent_by_agent(agent_name).order_by('-id')[:N]
329 return list(reversed(commands)) 358 return list(reversed(commands))
330 359
331 @classmethod 360 @classmethod
@@ -336,7 +365,7 @@ class Command(models.Model): @@ -336,7 +365,7 @@ class Command(models.Model):
336 NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc) 365 NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc)
337 """ 366 """
338 367
339 - print(f"Looking for old commands to purge... (commands that are not executing and older than {cls.COMMANDS_PEREMPTION_HOURS} hour(s))") 368 + print(f"(Looking for commands that are not executing and older than {cls.COMMANDS_PEREMPTION_HOURS} hour(s))")
340 """ 369 """
341 COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS) 370 COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS)
342 #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW) 371 #print("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)