Commit d96e2484b27153f33776d8b6edfe001bbfd177ed
Exists in
dev
Merge branch 'dev' of https://gitlab.irap.omp.eu/epallier/pyros into dev
# Conflicts: # src/agent/Agent.py
Showing
7 changed files
with
673 additions
and
300 deletions
Show diff stats
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 | ... | ... |
... | ... | @@ -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() | ... | ... |
... | ... | @@ -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__ == "__main__": |
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() | ... | ... |