Commit 3a46366b004e92b7caef7f9ae7c0bc3a3fab0611

Authored by Etienne Pallier
1 parent ee5f79f8
Exists in dev

Ajout infos précises sur statut commande + Bugfix simulateur sur "abort"

& Nouveaux diagrammes UML pour Command (state diag) et Agent (activity
diag)
README.md
... ... @@ -67,13 +67,13 @@ This software has been tested and validated with the following configurations :
67 67 --------------------------------------------------------------------------------------------
68 68 ## LAST VERSION
69 69  
70   -Date: 29/03/2019
  70 +Date: 01/04/2019
71 71  
72 72 Author: E. Pallier
73 73  
74   -VERSION: 0.20.33
  74 +VERSION: 0.20.34
75 75  
76   -Comment: Déplacé AgentM dans src/monitoring/ (pyros start agentM)
  76 +Comment: Ajout informations plus précises sur statut commande + Bugfix simulateur sur "abort"
77 77  
78 78 - Scenario de test :
79 79 - lancer agents A et B en mode simu (option -t): ./pyros.py -t start agentA,agentB
... ... @@ -88,6 +88,8 @@ Comment: Déplacé AgentM dans src/monitoring/ (pyros start agentM)
88 88 - pour utiliser thread ou processus : il suffit de mettre la constante RUN_IN_THREAD de AgentA (ou AgentB ou AgentX) à False ou True
89 89  
90 90 - Historique des nouveautés implémentées :
  91 + - Nouveaux diagrammes UML pour Command (state diag) et Agent (activity diag)
  92 + - Déplacé AgentM dans src/monitoring/ (pyros start agentM)
91 93 - Implémenté la commande "restart_init" (voir doc "play with a pyros agent") et refactorisation de run()
92 94 - Implémenté le "abort" dans le simulateur
93 95 - Optimisation log_agent_status() (ex update_survey())
... ...
src/agent/Agent.py
... ... @@ -194,61 +194,133 @@ class Agent:
194 194 """
195 195  
196 196 """
197   - PLANTUML ACTIVITY DIAGRAM
198   - # (http://plantuml.com/fr/activity-diagram-legacy)
199   - """
  197 + # --- Agent ACTIVIY DIAGRAM (plantUML) ---
  198 + # NEW syntax => http://plantuml.com/fr/activity-diagram-beta
  199 + # OLD syntax => http://plantuml.com/fr/activity-diagram-legacy
200 200  
201   - """
202 201 @startuml
203 202 version
204 203 @enduml
205   - """
206 204  
207   - """
  205 + # Exemple de skin utilisable (celui de ChemCam) :
  206 + skinparam activity {
  207 + StartColor red
  208 + EndColor Silver
  209 + BackgroundColor Peru
  210 + BackgroundColor<< Begin >> Olive
  211 + BorderColor Peru
  212 + FontName Impact
  213 + }
  214 +
  215 + (NEW SYNTAX)
208 216 @startuml
209 217  
210 218 title
211   - __** Agent.run() function activity diagram **__
  219 + __**Agent.run() function : Activity Diagram **__
  220 +
212 221 end title
213 222  
214   - skinparam activity {
215   - StartColor red
216   - EndColor Silver
217   - BackgroundColor Peru
218   - BackgroundColor<< Begin >> Olive
219   - BorderColor Peru
220   - FontName Impact
221   - }
  223 + start
222 224  
223   - (*) --> load_config()
  225 + :DO_EXIT = False
  226 + DO_RESTART = True;
224 227  
225   - --> init()
  228 + while (DO_RESTART ?) is (yes)
  229 + :load_config();
  230 + :init();
  231 + :DO_MAIN_LOOP = True;
  232 + while (DO_MAIN_LOOP ?) is (yes)
  233 + partition main_loop() {
  234 + :reload_config();
  235 + note right
  236 + only if changed
  237 + end note
226 238  
227   - partition infinite_loop {
228   - ---> "reload_config()"
229   - note right
230   - only if changed
231   - end note
  239 + :log_agent_status();
  240 + note right
  241 + Log this agent status in DB
  242 + end note
232 243  
233   - --> log_agent_status()
234   - note right
235   - Log this agent status in DB
236   - end note
  244 + :routine_process();
  245 +
  246 + :cmd = get_next_valid_command();
  247 + partition command_process(cmd) {
  248 + :cmd = self.general_process(cmd);
  249 + note right
  250 + only if there is a command
  251 + end note
  252 + :specific_process(cmd);
  253 + note right
  254 + only if there is a command and I am "active"
  255 + end note
  256 + }
  257 + if (DO_RESTART or DO_EXIT ?) then (yes)
  258 + :DO_MAIN_LOOP = False;
  259 + endif
237 260  
238   - --> routine_process()
239 261  
240   - --> cmd = get_next_valid_command()
241   - --> cmd = command_process(cmd)
242   - note right
243   - only if there is a command
244   - end note
245   - }
246 262  
247   - If "NEW ITERATION ?" then
248   - --> [Yes] "reload_config()"
249   - else
250   - --> (*)
251   - Endif
  263 +
  264 + }
  265 + endwhile (no)
  266 + endwhile (no)
  267 +
  268 + stop
  269 +
  270 + @enduml
  271 +
  272 +
  273 + (OLD SYNTAX)
  274 + @startuml
  275 + title
  276 + __** Agent.run() function activity diagram **__
  277 + end title
  278 +
  279 + (*) --> DO_EXIT=False & DO_RESTART=True
  280 +
  281 + partition while-DO_RESTART {
  282 + --> load_config()
  283 + --> init()
  284 + --> DO_MAIN_LOOP = True
  285 + --> main_loop()
  286 +
  287 + partition while-DO_MAIN_LOOP {
  288 + partition main_loop() {
  289 +
  290 + ---> "reload_config()"
  291 + note right
  292 + only if changed
  293 + end note
  294 +
  295 + --> log_agent_status()
  296 + note right
  297 + Log this agent status in DB
  298 + end note
  299 +
  300 + --> routine_process()
  301 +
  302 + --> cmd = get_next_valid_command()
  303 + --> cmd = command_process(cmd)
  304 + note right
  305 + only if there is a command
  306 + end note
  307 + partition command_process(cmd) {
  308 + --> cmd = self.general_process(cmd)
  309 + --> specific_process(cmd)
  310 + note right
  311 + only if there is a command and I am "active"
  312 + end note
  313 + }
  314 + }
  315 + }
  316 +
  317 + If "NEW ITERATION ?" then
  318 + --> "reload_config()"
  319 + else
  320 + --> [No] (*)
  321 + Endif
  322 +
  323 + }
252 324  
253 325 @enduml
254 326 """
... ... @@ -399,7 +471,7 @@ class Agent:
399 471 _path_data = '../../config'
400 472  
401 473 _iter_num = None
402   -
  474 +
403 475 # Log object
404 476 _log = None
405 477  
... ... @@ -595,10 +667,10 @@ class Agent:
595 667 cmd = self.get_next_valid_command()
596 668 #if cmd: cmd = self.general_process(cmd)
597 669 # Process this (next) command (if exists)
598   - if cmd: cmd = self.command_process(cmd)
  670 + if cmd: self.command_process(cmd)
599 671 # if restart, exit this loop to restart from beginning
600 672 if self.DO_RESTART or self.DO_EXIT:
601   - self.DO_MAIN_LOOP = False
  673 + self.DO_MAIN_LOOP = False
602 674 return
603 675  
604 676 self.printd("------END COMMMAND PROCESSING------")
... ... @@ -611,7 +683,7 @@ class Agent:
611 683 # (simulator only) Exit if max duration is reached
612 684 if self.SIMULATOR_MAX_DURATION_SEC and (time.time()-self.start_time > self.SIMULATOR_MAX_DURATION_SEC):
613 685 self.print("Exit because of max duration set to ", self.SIMULATOR_MAX_DURATION_SEC, "s")
614   - self.kill_running_specific_cmd_if_exists()
  686 + self.kill_running_specific_cmd_if_exists(self.name)
615 687 if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST: self.simulator_test_results()
616 688 self.DO_MAIN_LOOP = False
617 689 return
... ... @@ -911,9 +983,6 @@ class Agent:
911 983 # 6) Current cmd must now be a valid (not expired) and PENDING one,
912 984 # so pass it to general_process() for execution
913 985 #self.printd(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
914   - self.print("***")
915   - self.print("*** Got", cmd)
916   - self.print("***")
917 986 return cmd
918 987  
919 988  
... ... @@ -929,7 +998,10 @@ class Agent:
929 998 assert cmd is not None
930 999  
931 1000 self.set_status(self.STATUS_GENERAL_PROCESS)
932   - self.print(f"Starting processing of {cmd}")
  1001 + self.print("***")
  1002 + self.print("*** RECEIVED", cmd)
  1003 + self.print("***")
  1004 + self.printd(f"Starting general processing of this command")
933 1005  
934 1006 # Update read time to say that the command has been READ
935 1007 cmd.set_read_time()
... ... @@ -999,7 +1071,7 @@ class Agent:
999 1071 #self.printd("Current pending commands are:")
1000 1072 #Command.show_commands(self._pending_commands)
1001 1073 self.print("Aborting current executing command if exists:")
1002   - self.kill_running_specific_cmd_if_exists()
  1074 + self.kill_running_specific_cmd_if_exists(cmd.sender)
1003 1075 if cmd.name == "restart_init":
1004 1076 self.print("restart_init received: Restarting from init()")
1005 1077 self.DO_RESTART=True
... ... @@ -1072,7 +1144,7 @@ class Agent:
1072 1144 self.printd("Ending specific process (thread has been launched)")
1073 1145  
1074 1146  
1075   - def kill_running_specific_cmd_if_exists(self):
  1147 + def kill_running_specific_cmd_if_exists(self, abort_sender):
1076 1148 if (self._current_specific_thread is None) or not self._current_specific_thread.is_alive():
1077 1149 self.printd("...No current specific command thread to abort...")
1078 1150 else:
... ... @@ -1083,7 +1155,7 @@ class Agent:
1083 1155 #self._current_specific_thread.shutdown()
1084 1156 #threading._shutdown()
1085 1157 #self._current_specific_thread.raise_exception()
1086   - self._current_specific_cmd.set_as_killed()
  1158 + self._current_specific_cmd.set_as_killed_by(abort_sender)
1087 1159 self._current_specific_thread.terminate()
1088 1160 # Now, wait for the end of the thread
1089 1161 if self.RUN_IN_THREAD:
... ... @@ -1129,7 +1201,9 @@ class Agent:
1129 1201 SIMULATOR MODE ONLY
1130 1202 """
1131 1203  
1132   - # There is a current command being processed => check its status to see if finished
  1204 + # There is a current command being processed
  1205 + # => check if next command is "abort"
  1206 + # => if so, instantly send an "abort" to abort previous command
1133 1207 if self.cmdts is not None:
1134 1208 self.print(f"Waiting for end of cmd '{self.cmdts.name}' execution...")
1135 1209 # Update cmdts fields from DB
... ... @@ -1139,8 +1213,14 @@ class Agent:
1139 1213 # If next command is "abort" then abort becomes the new current command (to be sent)
1140 1214 self.next_cmdts = self.simulator_get_next_command_to_send()
1141 1215 if self.next_cmdts and self.next_cmdts.name == "abort":
  1216 + # Wait a little to give a chance to agentB to start execution of current command,
  1217 + # so that we can abort it then (otherwise it won't be aborted!!)
  1218 + time.sleep(2)
1142 1219 self.cmdts = self.next_cmdts
1143 1220 self.next_cmdts = None
  1221 + self.print("***")
  1222 + self.print(f"*** SEND command", self.cmdts)
  1223 + self.print("***")
1144 1224 self.cmdts.send()
1145 1225  
1146 1226 # Current cmd is no more running
... ... @@ -1176,7 +1256,7 @@ class Agent:
1176 1256 # Set cmdts to None so that a new command will be sent at next iteration
1177 1257 self.cmdts = None
1178 1258  
1179   - # No currently running command => get a new command to send
  1259 + # No currently running command => get a new command and SEND it
1180 1260 if self.cmdts is None:
1181 1261 if self.next_cmdts is not None:
1182 1262 self.cmdts = self.next_cmdts
... ... @@ -1186,8 +1266,11 @@ class Agent:
1186 1266 # No more command to send (from simulator) => return
1187 1267 if self.cmdts is None: return
1188 1268 # Send cmd (= set as pending and save)
1189   - self.print(f"Send command", self.cmdts)
  1269 + self.print("***")
  1270 + self.print(f"*** SEND command", self.cmdts)
  1271 + self.print("***")
1190 1272 #self.cmdts.set_as_pending()
  1273 + # SEND
1191 1274 self.cmdts.send()
1192 1275 #cmdts_is_processed = False
1193 1276 #cmdts_res = None
... ...
src/agent/AgentB.py
... ... @@ -29,8 +29,8 @@ class AgentB(Agent):
29 29 "flush_commands",
30 30 "go_active",
31 31  
32   - # Because of this command, the receiver agent
33   - # will no more send any new command
  32 + # Because of this command, the recipient agent
  33 + # should no more send any new specific command
34 34 "go_idle",
35 35 "go_idle",
36 36 "go_idle",
... ...
src/agent/Agent_run_activitydiag.png

39.4 KB | W: | H:

64.3 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
src/common/models.py
... ... @@ -323,6 +323,45 @@ class Command(models.Model):
323 323 | id | sender | recipient | name | validity_duration (default=60) | s_deposit_time | r_read_time
324 324 """
325 325  
  326 + """
  327 + # --- Command STATE DIAGRAM ---
  328 + # http://plantuml.com/fr/state-diagram
  329 + @startuml
  330 +
  331 + title
  332 + __**models.Command class : State Diagram**__
  333 +
  334 + end title
  335 +
  336 + [*] --> PENDING : **created by sender**
  337 +
  338 + ' --- (1) PENDING ---
  339 + PENDING: + s_deposit_time \n+ r_read_time
  340 + PENDING --> RUNNING : **launched**
  341 + PENDING --> SKIPPED: **recipient is IDLE**
  342 + PENDING --> EXPIRED: **command is too old**
  343 +
  344 + ' --- (2) RUNNING, SKIPPED, or EXPIRED ---
  345 + RUNNING: + r_launched_time
  346 + RUNNING --> PROCESSED : **finished**
  347 + RUNNING --> KILLED: \l**aborted** \n(by sender or other allowed agent, \nwith command "abort" or "exit")
  348 +
  349 + SKIPPED: + r_skipped_time
  350 + SKIPPED --> [*]
  351 +
  352 + EXPIRED --> [*]
  353 + EXPIRED: + expired_time \n+ author_agent_name
  354 +
  355 + ' --- (3) PROCESSED or KILLED ---
  356 + PROCESSED: + finished_time
  357 + PROCESSED --> [*]
  358 +
  359 + KILLED --> [*]
  360 + KILLED: + finished_time \n+ author_agent_name
  361 +
  362 + @enduml
  363 + """
  364 +
326 365 # -------------- Command CONSTANTS --------------
327 366  
328 367 # Receiver status codes
... ... @@ -361,17 +400,29 @@ class Command(models.Model):
361 400 # -------------- Command FIELDS --------------
362 401  
363 402 #sender = models.CharField(max_length=50, blank=True, null=True, unique=True)
364   - sender = models.CharField(max_length=50, help_text='sender agent name')
365   - recipient = models.CharField(max_length=50, help_text='recipient agent name')
366   - name = models.CharField(max_length=400, help_text='command name')
  403 + # Sender and Recipient agents
  404 + sender = models.CharField(max_length=50, help_text='sender agent name', null=False)
  405 + recipient = models.CharField(max_length=50, help_text='recipient agent name', null=False)
  406 + # Other agent (than sender or recipient) that set this command to KILLED or EXPIRED
  407 + author_agent_name = models.CharField(max_length=50, help_text='sender agent name', null=True)
  408 + name = models.CharField(max_length=400, help_text='command name', null=False)
367 409 validity_duration = models.PositiveIntegerField('(in sec)', default=COMMANDS_VALIDITY_DURATION_SEC_DEFAULT)
368   - # Automatically set at table line creation (line created by the sender)
  410 +
  411 + # (AUTO on create) Automatically set at table line creation (line created by the sender)
369 412 s_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True)
370 413 # Set by the recipient :
371   - # - at reading time
  414 + # - on reading:
372 415 r_read_time = models.DateTimeField(null=True)
373   - # - after execution
374   - r_processed_time = models.DateTimeField(null=True)
  416 + # - on skipping (because idle):
  417 + r_skipped_time = models.DateTimeField(null=True)
  418 + # - on expiration (because too old):
  419 + r_skipped_time = models.DateTimeField(null=True)
  420 + # - on launching:
  421 + r_launched_time = models.DateTimeField(null=True)
  422 + # - after kill or execution:
  423 + #r_processed_time = models.DateTimeField(null=True)
  424 + finished_time = models.DateTimeField(null=True)
  425 +
375 426 r_status_code = models.CharField(choices = CMD_STATUS_CODES, default=CMD_STATUS_CODES.CMD_PENDING, max_length=20)
376 427 #r_status_code = models.IntegerField(choices=CMD_STATUS_CODES, default=RSCODE_PENDING)
377 428 # TODO: maybe à mettre au format json (key:value)
... ... @@ -595,10 +646,13 @@ class Command(models.Model):
595 646  
596 647 def set_as_processed(self):
597 648 print(f"- Set command {self.name} as processed")
  649 + self.set_status_to(self.CMD_STATUS_CODES.CMD_EXECUTED)
598 650 #print(self)
  651 + """
599 652 self.r_status_code = self.CMD_STATUS_CODES.CMD_EXECUTED
600 653 self.r_processed_time = datetime.utcnow().astimezone()
601 654 self.save()
  655 + """
602 656 # Optimization: update the related fields, but does not work, why ?
603 657 ##self.save(update_fields=["r_status_code", "r_processed_time"])
604 658  
... ... @@ -612,10 +666,10 @@ class Command(models.Model):
612 666 def set_as_skipped(self):
613 667 self.set_status_to(self.CMD_STATUS_CODES.CMD_SKIPPED)
614 668  
615   - def set_as_killed(self):
  669 + def set_as_killed_by(self, author_agent:str):
616 670 print(f"- Set command {self.name} as killed")
617 671 #print(f"- Set this command as killed: {self}")
618   - self.set_status_to(self.CMD_STATUS_CODES.CMD_KILLED)
  672 + self.set_status_to(self.CMD_STATUS_CODES.CMD_KILLED, author_agent)
619 673  
620 674 def set_as_running(self):
621 675 print(f"- Set command {self.name} as running")
... ... @@ -625,7 +679,24 @@ class Command(models.Model):
625 679 self.set_status_to(self.CMD_STATUS_CODES.CMD_EXECUTED)
626 680 '''
627 681  
628   - def set_status_to(self, status:str):
  682 + def set_status_to(self, status:str, author_agent_name:str=None):
  683 + now_time = datetime.utcnow().astimezone()
  684 + if status in (self.CMD_STATUS_CODES.CMD_RUNNING, self.CMD_STATUS_CODES.CMD_SKIPPED, self.CMD_STATUS_CODES.CMD_OUTOFDATE):
  685 + assert self.is_pending()
  686 + if status == self.CMD_STATUS_CODES.CMD_RUNNING:
  687 + self.r_launched_time = now_time
  688 + elif status == self.CMD_STATUS_CODES.CMD_SKIPPED:
  689 + self.r_skipped_time = now_time
  690 + # EXPIRED
  691 + elif status == self.CMD_STATUS_CODES.CMD_OUTOFDATE:
  692 + self.expired_time = now_time
  693 + self.author_agent_name = author_agent_name
  694 + elif status in (self.CMD_STATUS_CODES.CMD_EXECUTED, self.CMD_STATUS_CODES.CMD_KILLED):
  695 + assert self.is_running()
  696 + self.finished_time = now_time
  697 + if status == self.CMD_STATUS_CODES.CMD_KILLED:
  698 + self.author_agent_name = author_agent_name
  699 + # Update command status
629 700 self.r_status_code = status
630 701 self.save()
631 702 # Optimization, but does not work, why ?...
... ...
src/common/models_Command_statediag.png 0 → 100644

43.5 KB