Commit 7735aaa722d935dca4af29ebd9a06e41365c3948

Authored by Etienne Pallier
1 parent cbbd0c65
Exists in dev

updated Agent specific commands format et new get_specific_cmds method avec list…

…e arguments auto-generée
pyros.py
... ... @@ -626,8 +626,7 @@ def install_or_update(UPDATE: bool = False, with_packages: bool = True, with_dat
626 626 # self.addExecuted(self.current_command, command)
627 627 from git import Repo
628 628 else:
629   - log.error(
630   - "GitPython package (required for obsconfig class) installation failed")
  629 + log.error("GitPython package (required for obsconfig class) installation failed")
631 630 '''
632 631 print("Guitastro : Generating (updating) API documentation (using Sphinx)")
633 632 # Make html doc from RST
... ...
src/core/pyros_django/agent/Agent.py
... ... @@ -40,8 +40,10 @@ and execute them on reception at each iteration :
40 40  
41 41  
42 42 # For cmd parsing
43   -from typing import List, Tuple
  43 +from array import array
  44 +from typing import List, Tuple, Union, Any
44 45 import ast
  46 +from inspect import signature
45 47  
46 48 import os
47 49 from pathlib import Path
... ... @@ -256,8 +258,11 @@ class StoppableThreadEvenWhenSleeping(threading.Thread):
256 258 class AgentCmdException(Exception):
257 259 ''' Base class for all Agent command exceptions '''
258 260 # pass
259   - def __init__(self, cmd:AgentCmd):
  261 + def __init__( self, cmd: Union[AgentCmd,str] ):
260 262 self.cmd = cmd
  263 + print(type(cmd))
  264 + exit
  265 + self.cmd_name = cmd if isinstance(cmd,str) else cmd.name
261 266 '''
262 267 def __str__(self):
263 268 return f"The Agent command '{self.cmd.name}' is unknown to the agent"
... ... @@ -267,19 +272,19 @@ class AgentCmdException(Exception):
267 272 class UnknownCmdException(AgentCmdException):
268 273 ''' Raised when an Agent (specific) cmd is NOT known by the agent '''
269 274 def __str__(self):
270   - return f"The Agent command '{self.cmd.name}' is unknown to the agent"
  275 + return f"The Agent command '{self.cmd_name}' is unknown to the agent"
271 276 #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
272 277  
273 278 class AgentCmdUnimplementedException(AgentCmdException):
274 279 ''' Raised when an Agent Specific cmd is known by the agent but not implemented '''
275 280 def __str__(self):
276   - return f"The Agent command '{self.cmd.name}' is known by the agent but not implemented"
  281 + return f"The Agent command '{self.cmd_name}' is known by the agent but not implemented"
277 282 #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
278 283  
279 284 class AgentCmdBadArgsException(AgentCmdException):
280 285 ''' Raised when an Agent cmd has bad, missing, or too many argument(s) '''
281 286 def __str__(self):
282   - return f"The Agent command '{self.cmd.name}' has bad, missing, or too many argument(s)"
  287 + return f"The Agent command '{self.cmd_name}' has bad, missing, or too many argument(s)"
283 288 #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
284 289  
285 290  
... ... @@ -327,18 +332,19 @@ class Agent:
327 332 # Default LOG level is INFO
328 333 #PYROS_DEFAULT_GLOBAL_LOG_LEVEL = LogPyros.LOG_LEVEL_INFO # INFO
329 334  
330   - # This Agent Specific commands list, that he is able to execute
331   - # To be overriden by subclasses (here are just a few examples of commands)
332   - # Format : ("cmd",timeout) with :
333   - # - cmd : the command name
334   - # - timeout : the command timeout (in sec)
335   - AGENT_SPECIFIC_COMMANDS = [
336   - #"do_specific1",
337   - #"set_specific2",
338   - #"do_specific3",
339   - ("do_specific1", 10),
340   - ("set_specific2", 5),
341   - ("do_specific3", 3),
  335 + '''
  336 + This Agent Specific commands list, that he is able to execute
  337 + To be overriden by subclasses (here are just a few examples of commands)
  338 + Format : ("cmd", args, timeout) with :
  339 + - cmd (str) : the command name
  340 + - timeout (int) : the command timeout (in sec)
  341 + - exec_mode (int) : 0 (sequential), 1 (thread), or 2 (process)
  342 + '''
  343 + AGENT_SPECIFIC_COMMANDS: List[ Tuple[str, int, int] ] = [
  344 + # Format : (“cmd_name”, timeout, exec_mode)
  345 + ("do_specific1", 10, 0),
  346 + #("set_specific2", 5, 0),
  347 + ("do_specific3", 3, 0),
342 348 ]
343 349  
344 350 #
... ... @@ -384,7 +390,11 @@ class Agent:
384 390 # - "self set_state ATTENTIVE" => means to send the command "set_state ATTENTIVE" to MYSELF
385 391 # - "self do_restart_loop" => means to send the command "do_restart_loop" to MYSELF (no args)
386 392 #
387   - TEST_COMMANDS_LIST = [
  393 + TEST_COMMANDS_LIST: List[ Tuple[ str, int, str, Union[int,None] ] ] = [
  394 +
  395 + # Format : ("self cmd_name cmd_args", timeout, "expected_result", expected_status),
  396 + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, '15.5', None),
  397 +
388 398  
389 399 # 1) First, 3 EXCEPTION CASES (uncomment to activate exception)
390 400 # Each of these lines will stop execution with an exception
... ... @@ -416,15 +426,16 @@ class Agent:
416 426 ("self get_mode", 100, "MODE = ATTENTIVE", None),
417 427  
418 428 # => should get "7"
419   - ("self do_eval 3+5-1", 200, 7, None),
  429 + ("self do_eval 3+5-1", 200, '7', None),
420 430  
421 431 # END, will not go further
422 432 #("self do_exit", 2, "STOPPING"),
423 433  
424 434 # Agent specific commands => should be executed
425   - ("self do_specific3", 200, None, None),
  435 + ("self do_specific3", 200, '', None),
426 436 ("self do_exit", 500, "STOPPING", None),
427   - ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, 7, None),
  437 + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, '15.5', None),
  438 +
428 439  
429 440 ("self set_mode ROUTINE", 200, "MODE = ROUTINE", None),
430 441 # => should get "ROUTINE"
... ... @@ -1042,15 +1053,15 @@ class Agent:
1042 1053 log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n')
1043 1054 try :
1044 1055 cmd = self._process_next_command_if_exists()
1045   - print("after get")
1046 1056 print(cmd)
1047 1057 except (AgentCmdUnimplementedException, AgentCmdBadArgsException, UnknownCmdException) as e :
1048 1058 print(e)
1049   - log.error(f"EXCEPTION on Agent command '{e.cmd.name}'")
1050   - if type(e) is UnknownCmdException:
1051   - e.cmd.set_as_skipped("EXCEPTION: unknown command")
1052   - else :
1053   - e.cmd.set_as_processed("EXCEPTION: command known but unimplemented or bad args")
  1059 + log.error(f"EXCEPTION on Agent command '{e.cmd_name}'")
  1060 + if isinstance(e.cmd, AgentCmd) :
  1061 + if type(e) is AgentCmdUnimplementedException:
  1062 + e.cmd.set_as_unimplemented("EXCEPTION: command known but unimplemented")
  1063 + else:
  1064 + e.cmd.set_as_invalid("EXCEPTION: command unknown or bad args")
1054 1065 self._cleanup_before_exit()
1055 1066 raise
1056 1067 log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n")
... ... @@ -1421,6 +1432,22 @@ class Agent:
1421 1432 def show_mode_and_status(self):
1422 1433 log.info(f"CURRENT MODE is {self.mode} (status is {self.status})")
1423 1434  
  1435 + def get_specific_cmds(self)->str:
  1436 + specific_commands = ""
  1437 + for command_tuple in self.AGENT_SPECIFIC_COMMANDS:
  1438 + cmd_name = command_tuple[0]
  1439 + specific_commands += cmd_name
  1440 + # Exception if exists an unimplemented command
  1441 + try:
  1442 + f = getattr(self, cmd_name)
  1443 + except AttributeError:
  1444 + raise AgentCmdUnimplementedException(cmd_name) from None
  1445 + args = signature(f)
  1446 + specific_commands += str(args)
  1447 + specific_commands += ";"
  1448 + if specific_commands: specific_commands = specific_commands[0:-1]
  1449 + return specific_commands
  1450 + '''
1424 1451 def get_specifics_cmd(self):
1425 1452 specific_commands = ""
1426 1453 for index, command_tuple in enumerate(self.AGENT_SPECIFIC_COMMANDS):
... ... @@ -1428,6 +1455,7 @@ class Agent:
1428 1455 if index != len(self.AGENT_SPECIFIC_COMMANDS)-1:
1429 1456 specific_commands += ";"
1430 1457 return specific_commands
  1458 + '''
1431 1459  
1432 1460 def get_mode(self):
1433 1461 return self.mode
... ... @@ -1982,7 +2010,7 @@ class Agent:
1982 2010 # Execute method self."cmd.name"()
1983 2011 # This can raise an exception (caught by this method caller)
1984 2012 try:
1985   - res = self.exec_cmd_from_its_name(cmd)
  2013 + res = self._exec_cmd_from_its_name(cmd)
1986 2014 except (AgentCmdUnimplementedException, AgentCmdBadArgsException) as e:
1987 2015 # These exceptions are managed at higher level :
1988 2016 raise
... ... @@ -2013,11 +2041,25 @@ class Agent:
2013 2041 self.printd("Logging data...")
2014 2042 '''
2015 2043  
2016   - def exec_cmd_from_its_name(self, cmd:AgentCmd):
  2044 + def _exec_cmd_from_its_name(self, cmd:AgentCmd)->Any:
  2045 + #print(dir(self))
  2046 + '''
  2047 + for method in dir(self):
  2048 + if callable(getattr(self, method)):
  2049 + m = getattr(self, method)
  2050 + print(method, ' => ', signature(m))
  2051 + '''
  2052 + #print("***************")
  2053 + #print(self.get_specific_cmds())
  2054 + #print("***************")
  2055 +
2017 2056 methods_list = [method for method in dir(self) if callable(getattr(self, method))]
2018 2057 #print(methodsList)
2019 2058 func = cmd.name
2020 2059 if func not in methods_list: raise AgentCmdUnimplementedException(cmd)
  2060 + f = getattr(self, func)
  2061 + ###print(func, ' => ', signature(f))
  2062 +
2021 2063 #for arg in cmd.args: print(arg)
2022 2064 #print(cmd.args)
2023 2065 #print(*cmd.args)
... ... @@ -2046,9 +2088,17 @@ class Agent:
2046 2088 for arg in cmd.args:
2047 2089 #print(arg)
2048 2090 #try:
2049   - # Evaluate arg only if it is not a word (letters)
  2091 + # Evaluate arg only if it is not a word (letters) :
  2092 + # - a word like "toto" is not evaluated => stays as is (=str)
  2093 + # Are evaluated :
  2094 + # - an int or float
  2095 + # - a (tuple)
  2096 + # - a [list]
  2097 + # - ...
  2098 + #print('********', arg, " :")
2050 2099 if not arg[0].isalpha():
2051 2100 arg = ast.literal_eval(arg)
  2101 + #print("evaluated to", type(arg), arg)
2052 2102 #except ValueError as e: newarg = arg
2053 2103 args.append(arg)
2054 2104 try:
... ... @@ -2063,6 +2113,7 @@ class Agent:
2063 2113 ##raise AgentCmdUnimplementedException(cmd).with_traceback(tb)
2064 2114 ##raise AgentCmdUnimplementedException(cmd).with_traceback(None)
2065 2115  
  2116 +
2066 2117 def _is_agent_level_cmd(self, cmd:AgentCmd):
2067 2118 return self._is_agent_general_cmd(cmd) or self._is_agent_specific_cmd(cmd)
2068 2119  
... ... @@ -2092,7 +2143,7 @@ class Agent:
2092 2143 def _is_agent_specific_cmd(self, cmd:AgentCmd):
2093 2144 #return cmd.name in self.AGENT_SPECIFIC_COMMANDS
2094 2145 #return (cmd.name,) in self.AGENT_SPECIFIC_COMMANDS
2095   - for (cmd_name,timeout) in self.AGENT_SPECIFIC_COMMANDS:
  2146 + for (cmd_name,_,_) in self.AGENT_SPECIFIC_COMMANDS:
2096 2147 if cmd.name == cmd_name : return True
2097 2148  
2098 2149 '''
... ... @@ -2118,7 +2169,7 @@ class Agent:
2118 2169 def do_flush_commands(self):
2119 2170 AgentCmd.delete_pending_commands_for_agent(self.name)
2120 2171  
2121   - def do_exec_commands(self, what:str):
  2172 + def do_exec_command(self, what:str):
2122 2173 # - Temporaly stop execution of new commands (let them accumulate)
2123 2174 if what == "stop": pass
2124 2175 # - Resume execution of commands (accumulated since "do_stop_exec_cmd")
... ... @@ -2183,6 +2234,7 @@ class Agent:
2183 2234  
2184 2235 #def do_specific1(self, arg1:int, arg2:int=2, arg3:int=3) -> int:
2185 2236 #def do_specific1(self, arg1:int, arg2:int=2, arg3:float=3.1, arg4:list=[1,2,3]) -> float:
  2237 +
2186 2238 def do_specific1(self,
2187 2239 arg1:int,
2188 2240 arg2:int,
... ... @@ -2200,11 +2252,12 @@ class Agent:
2200 2252 print(arg5[1])
2201 2253 arg6 = ast.literal_eval(arg6)
2202 2254 '''
2203   - print(arg4)
  2255 + #print(arg4)
2204 2256 res = arg1 + arg2 + arg3 + arg5[0] + arg5[2]
2205 2257 if arg6: res += arg6[0]
2206 2258 return res
2207 2259  
  2260 + # Undefined specific cmd
2208 2261 #def set_specific2(self, arg1:str, arg2:int): pass
2209 2262  
2210 2263 def do_specific3(self):
... ... @@ -2303,7 +2356,7 @@ class Agent:
2303 2356 log.debug(cmd.result)
2304 2357 log.debug(self._cmdts.expected_res)
2305 2358 if cmd.is_executed() and self._cmdts.expected_res:
2306   - assert(cmd.result == self._cmdts.expected_res)
  2359 + assert(str(cmd.result) == self._cmdts.expected_res)
2307 2360  
2308 2361 log.debug(cmd.state)
2309 2362 log.debug(self._cmdts.expected_status)
... ... @@ -2374,7 +2427,6 @@ class Agent:
2374 2427 # Execution was completeted => get result
2375 2428 elif self._cmdts.is_executed():
2376 2429 cmdts_res = self._cmdts.get_result()
2377   - print("toto")
2378 2430 log.info(f"Cmd executed. Result is '{cmdts_res}'")
2379 2431 #cmdts_is_processed = True
2380 2432 ''' Optimisation possible pour gagner une iteration:
... ...
src/core/pyros_django/common/models.py
1 1 ##from __future__ import unicode_literals
2 2  
  3 +# (EP 21/9/22) To allow autoreferencing (ex: AgentCmd.create() returns a AgentCmd)
  4 +from __future__ import annotations
  5 +
3 6 # Stdlib imports
4 7 from numpy import False_
5 8 from src.device_controller.abstract_component.device_controller import DeviceCmd
... ... @@ -626,10 +629,9 @@ class AgentCmd(models.Model):
626 629 # -------------- Command CLASS (static) METHODS --------------
627 630  
628 631 # Avoid to override the Model __init__ method
  632 + # See https://docs.djangoproject.com/en/stable/ref/models/instances/#creating-objects
629 633 @classmethod
630   - # TODO: return type AgentCmd
631   - #def create(cls, agent_from:str, agent_to:str, cmd_name:str, cmd_args:str=None, cmd_validity:int=None) -> AgentCmd :
632   - def create(cls, agent_from:str, agent_to:str, cmd_name:str, cmd_args:str=None, cmd_validity:int=None) :
  634 + def create(cls, agent_from:str, agent_to:str, cmd_name:str, cmd_args:str=None, cmd_validity:int=None) -> AgentCmd :
633 635 #print(agent_to)
634 636 if '.' in agent_to:
635 637 agent_to, component_name = agent_to.split('.')
... ... @@ -654,9 +656,8 @@ class AgentCmd(models.Model):
654 656 """
655 657  
656 658 @classmethod
657   - # TODO: return type AgentCmd
658   - #def send_cmd_from_to(cls, agent_from, agent_to, cmd_name, cmd_args=None, cmd_validity:int=None)->AgentCmd:
659   - def send_cmd_from_to(cls, agent_from, agent_to, cmd_name, cmd_args=None, cmd_validity:int=None):
  659 + def send_cmd_from_to(cls, agent_from, agent_to, cmd_name, cmd_args=None, cmd_validity:int=None)->AgentCmd:
  660 + """ Create and send a command, then return it """
660 661 # def send(cls, from_agent, to_agent, cmd_name, cmd_args=None):
661 662 """
662 663 ex: send("AgentA",“AgentB”,"EVAL”,“3+4”)
... ... @@ -968,7 +969,47 @@ class AgentCmd(models.Model):
968 969 # Optimization: update only 1 field
969 970 if do_save: self.save(update_fields=["r_read_time"])
970 971  
  972 + #
  973 + # Set cmd status
  974 + #
  975 +
  976 + def set_as_pending(self):
  977 + self.set_state_to(self.CMD_STATUS_CODES.CMD_PENDING)
  978 +
  979 + # from PENDING
  980 + def set_as_unimplemented(self, result:str=None):
  981 + assert self.is_pending()
  982 + self.set_state_to(self.CMD_STATUS_CODES.CMD_UNIMPLEMENTED)
  983 +
  984 + # from PENDING
  985 + def set_as_invalid(self, result:str=None):
  986 + assert self.is_pending()
  987 + self.set_state_to(self.CMD_STATUS_CODES.CMD_INVALID)
  988 +
  989 + # from PENDING
  990 + def set_as_skipped(self, result:str=None):
  991 + assert self.is_pending()
  992 + self.set_state_to(self.CMD_STATUS_CODES.CMD_SKIPPED, result=result)
  993 +
  994 + # from PENDING
  995 + def set_as_expired(self):
  996 + assert self.is_pending()
  997 + print(f"- Set this command as expired (older than its validity duration of {self.validity_duration}s): {self}")
  998 + self.set_state_to(self.CMD_STATUS_CODES.CMD_EXPIRED)
  999 +
  1000 + # from PENDING
  1001 + def set_as_running(self):
  1002 + assert self.is_pending()
  1003 + printd(f"- Set command {self.name} as running")
  1004 + self.set_state_to(self.CMD_STATUS_CODES.CMD_RUNNING)
  1005 +
  1006 + '''
  1007 + def set_as_executed(self):
  1008 + self.set_state_to(self.CMD_STATUS_CODES.CMD_EXECUTED)
  1009 + '''
  1010 + # from RUNNING
971 1011 def set_as_processed(self, result:str=None):
  1012 + assert self.is_running()
972 1013 printd(f"- Set command {self.name} as processed")
973 1014 self.set_state_to(self.CMD_STATUS_CODES.CMD_EXECUTED, result=result)
974 1015 # print(self)
... ... @@ -979,32 +1020,30 @@ class AgentCmd(models.Model):
979 1020 """
980 1021 # Optimization: update the related fields, but does not work, why ?
981 1022 ##self.save(update_fields=["state", "r_processed_time"])
982   -
983 1023 #def set_as_outofdate(self):
984   - def set_as_expired(self):
985   - print(
986   - f"- Set this command as expired (older than its validity duration of {self.validity_duration}s): {self}")
987   - self.set_state_to(self.CMD_STATUS_CODES.CMD_EXPIRED)
988 1024  
989   - def set_as_pending(self):
990   - self.set_state_to(self.CMD_STATUS_CODES.CMD_PENDING)
  1025 + # from RUNNING
  1026 + def set_as_exec_error(self, result:str=None):
  1027 + assert self.is_running()
  1028 + self.set_state_to(self.CMD_STATUS_CODES.CMD_EXEC_ERROR, result=result)
991 1029  
992   - def set_as_skipped(self, result:str=None):
993   - self.set_state_to(self.CMD_STATUS_CODES.CMD_SKIPPED, result=result)
  1030 + # from RUNNING
  1031 + def set_as_exec_timeout(self, result:str=None):
  1032 + assert self.is_running()
  1033 + self.set_state_to(self.CMD_STATUS_CODES.CMD_EXEC_TIMEOUT, result=result)
994 1034  
  1035 + # from RUNNING
  1036 + def set_as_exec_timeout(self, result:str=None):
  1037 + assert self.is_running()
  1038 + self.set_state_to(self.CMD_STATUS_CODES.CMD_EXEC_TIMEOUT, result=result)
  1039 +
  1040 + # from RUNNING
995 1041 def set_as_killed_by(self, author_agent: str):
  1042 + assert self.is_running()
996 1043 printd(f"- Set command {self.name} as killed")
997 1044 #print(f"- Set this command as killed: {self}")
998 1045 self.set_state_to(self.CMD_STATUS_CODES.CMD_EXEC_KILLED, author_agent)
999 1046  
1000   - def set_as_running(self):
1001   - printd(f"- Set command {self.name} as running")
1002   - self.set_state_to(self.CMD_STATUS_CODES.CMD_RUNNING)
1003   - '''
1004   - def set_as_executed(self):
1005   - self.set_state_to(self.CMD_STATUS_CODES.CMD_EXECUTED)
1006   - '''
1007   -
1008 1047 def set_state_to(self, status: str, author_agent_name: str = None, result:str=None):
1009 1048 '''
1010 1049 If comment is present it will be set in the "result" field
... ...