Commit a661768659302a6196665aa6ff53594c84e212fe
1 parent
4f7187af
Exists in
dev
new wrapper script PYROS and new Agent2 (replace Agent)
Showing
5 changed files
with
1816 additions
and
62 deletions
Show diff stats
1 | 1 | #!/usr/bin/env bash |
2 | 2 | |
3 | +DEBUG=true | |
4 | +DEBUG=false | |
5 | + | |
3 | 6 | # test if user passed a command as parameter |
4 | -if test $# -lt 1 | |
5 | -then | |
6 | - echo "Missing command, use one of the commands below" | |
7 | - python3 pyros.py --help | |
8 | - exit 1 | |
9 | -fi | |
7 | +#if test $# -lt 1 ; then | |
8 | + #echo "Missing command, use one of the commands below" | |
9 | + #python3 pyros.py --help | |
10 | + #exit | |
11 | +#fi | |
12 | + | |
13 | +# Guess Context : | |
14 | +# - NO DOCKER, | |
15 | +# or | |
16 | +# - DOCKER OUT of container, | |
17 | +# or | |
18 | +# - or DOCKER IN container | |
19 | + | |
20 | +WITH_DOCKER_IS_SET=true | |
21 | +[ -z $WITH_DOCKER ] && WITH_DOCKER_IS_SET=false | |
22 | + | |
23 | +APP_FOLDER=false | |
24 | +[ -d ../app/ ] && APP_FOLDER=true | |
25 | + | |
26 | +VENV=false | |
27 | +[ -d ./venv/ ] && VENV=true | |
10 | 28 | |
11 | 29 | # test if docker is installed |
12 | -if [ -x "$(command -v docker)" ]; | |
13 | -then | |
14 | - docker=true | |
15 | -else | |
16 | - docker=false | |
17 | -fi | |
30 | +DOCKER_CMD=false | |
31 | +[ -x "$(command -v docker)" ] && DOCKER_CMD=true | |
32 | + | |
33 | + | |
34 | +# test if container is running | |
35 | +container=false | |
36 | +$DOCKER_CMD && [ $(docker ps | grep 'pyros' | wc -l) -eq 2 ] && container=true | |
18 | 37 | |
19 | -if $docker | |
20 | -then | |
21 | - # test if container is running | |
22 | - if ! [ $(docker ps | grep 'pyros' | wc -l) -eq 2 ]; | |
23 | - then | |
24 | - container=true; | |
25 | - else | |
26 | - container=false; | |
27 | - fi | |
28 | - if $container; | |
29 | - then | |
30 | - docker exec -it pyros python3 pyros.py $1 | |
31 | - else | |
32 | - # starting container first and then launch pyros.py | |
33 | - cd docker; docker-compose up -d | |
34 | - docker exec -it pyros python3 pyros.py $1 | |
35 | - fi | |
36 | -else | |
37 | - python3 pyros.py $1 | |
38 | +# | |
39 | +# SYNTHESIS | |
40 | +# | |
41 | + | |
42 | +DOCKER=false | |
43 | +[[ $VENV == false && $DOCKER_CMD == true ]] && DOCKER=true | |
44 | + | |
45 | +DOCKER_OUT_CONTAINER=false | |
46 | +[[ $DOCKER == true && $WITH_DOCKER_IS_SET == false ]] && DOCKER_OUT_CONTAINER=true | |
47 | +#[[ $DOCKER == false && $WITH_DOCKER_IS_SET == true ]] && DOCKER_IN_CONTAINER=true | |
48 | + | |
49 | +if $DEBUG ; then | |
50 | + echo $APP_FOLDER | |
51 | + echo $VENV | |
52 | + echo $DOCKER_CMD | |
53 | + echo $container | |
54 | + | |
55 | + # Synthesis | |
56 | + echo $DOCKER | |
57 | + echo $DOCKER_OUT_CONTAINER | |
58 | + | |
59 | + exit | |
38 | 60 | fi |
39 | 61 | |
62 | + | |
63 | +# no container ? => start container first | |
64 | +#[ $container == false ] && cd docker/ && docker-compose up -d | |
65 | + | |
66 | +# DOCKER_OUT_CONTAINER true ? docker exec | |
67 | +PREFIX='' | |
68 | +#docker exec -it pyros python3 pyros.py $* | |
69 | +[ $DOCKER_OUT_CONTAINER == true ] && PREFIX='docker exec -it pyros ' | |
70 | + | |
71 | +echo $PREFIX | |
72 | +$PREFIX python3 pyros.py $* | |
73 | + | ... | ... |
pyros.py
... | ... | @@ -34,9 +34,11 @@ _previous_dir = None |
34 | 34 | AGENTS = { |
35 | 35 | #"agentX" : "agent", |
36 | 36 | "agent" : "Agent", |
37 | + "agent2" : "Agent", | |
37 | 38 | "agentX" : "AgentX", |
38 | 39 | "agentA" : "AgentA", |
39 | 40 | "agentB" : "AgentB", |
41 | + "agentC" : "AgentC", | |
40 | 42 | "agentM" : "AgentM", |
41 | 43 | "agentSP" : "AgentSP", |
42 | 44 | #"agentDevice" : "AgentDevice", |
... | ... | @@ -289,7 +291,7 @@ def printFullTerm(color: Colors, string: str): |
289 | 291 | printColor(color, "-" * (columns - value)) |
290 | 292 | return 0 |
291 | 293 | |
292 | -def set_environment_variables_if_not_configured(env_path: str,env_sample_path: str)->None: | |
294 | +def set_environment_variables_if_not_configured(env_path:str, env_sample_path:str)->None: | |
293 | 295 | """ |
294 | 296 | Set environment variables if they aren't defined in the current environment. |
295 | 297 | Get the variables from .env file if it exists or create this file using a copy of the env_sample |
... | ... | @@ -632,7 +634,7 @@ def initdb(): |
632 | 634 | #@click.option('--format', '-f', type=click.Choice(['html', 'xml', 'text']), default='html', show_default=True) |
633 | 635 | #@click.option('--port', default=8000) |
634 | 636 | #def start(agent:str, configfile:str, test, verbosity): |
635 | -def start(agent:str, configfile:str,observatory:str,unit:str): | |
637 | +def start(agent:str, configfile:str, observatory:str, unit:str): | |
636 | 638 | printd("Running start command") |
637 | 639 | if configfile: |
638 | 640 | printd("With config file", configfile) |
... | ... | @@ -659,10 +661,14 @@ def start(agent:str, configfile:str,observatory:str,unit:str): |
659 | 661 | obs_config_file_path = os.path.join(path_to_obs_config_folder,obs_config_file_name) |
660 | 662 | os.environ["PATH_TO_OBSCONF_FILE"] = obs_config_file_path |
661 | 663 | os.environ["PATH_TO_OBSCONF_FOLDER"] = path_to_obs_config_folder |
664 | + os.environ["unit_name"] = unit if unit else '' | |
665 | + ''' | |
662 | 666 | if unit: |
663 | 667 | os.environ["unit_name"] = unit |
664 | 668 | else: |
665 | 669 | os.environ["unit_name"] = "" |
670 | + ''' | |
671 | + | |
666 | 672 | # add path to pyros_django folder as the config class is supposed to work within this folder |
667 | 673 | #cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.configpyros import ConfigPyros\nConfigPyros('{os.path.join(PYROS_DJANGO_BASE_DIR,os.environ.get('PATH_TO_OBSCONF_FILE'))}')\"" |
668 | 674 | cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.configpyros import ConfigPyros\nConfigPyros('{obs_config_file_path}')\"" |
... | ... | @@ -702,7 +708,6 @@ def start(agent:str, configfile:str,observatory:str,unit:str): |
702 | 708 | printd("Launching agent", agent_name, "...") |
703 | 709 | #if not test_mode(): execProcess(VENV_PYTHON + " manage.py runserver") |
704 | 710 | #if not test_mode(): execProcessFromVenv("start_agent_" + agent_name + ".py " + configfile) |
705 | - | |
706 | 711 | current_dir = os.getcwd() |
707 | 712 | |
708 | 713 | # OLD format agents: majordome, monitoring, alert... | ... | ... |
... | ... | @@ -0,0 +1,1717 @@ |
1 | +#!/usr/bin/env python3 | |
2 | + | |
3 | +VERSION = "0.5" | |
4 | + | |
5 | +#DEBUG=True | |
6 | +#DEBUG=False | |
7 | + | |
8 | +"""TODO: | |
9 | + | |
10 | +- 1 log par agent + lastlog (30 dernières lignes) | |
11 | +- table agents_log avec log minimum pour affichage dans dashboard, et ordre chrono intéressant pour suivi activité : nom agent, timestamp, message | |
12 | +- 1 table par agent: agent_<agent-name>_vars : nom variable, value, desc | |
13 | + | |
14 | +""" | |
15 | + | |
16 | + | |
17 | +""" | |
18 | +================================================================= | |
19 | + SETUP FOR DJANGO | |
20 | + | |
21 | + (see https://docs.djangoproject.com/en/dev/topics/settings) | |
22 | + (see also https://docs.djangoproject.com/en/dev/ref/settings) | |
23 | +================================================================= | |
24 | +""" | |
25 | + | |
26 | +import os | |
27 | +from pathlib import Path | |
28 | +import sys | |
29 | + | |
30 | +from django.conf import settings as djangosettings | |
31 | + | |
32 | +# Conseil sur le net: | |
33 | +#https://stackoverflow.com/questions/16853649/how-to-execute-a-python-script-from-the-django-shell | |
34 | +#"" | |
35 | +#import sys, os | |
36 | +#sys.path.append('/path/to/your/django/app') | |
37 | +#os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' | |
38 | +#from django.conf import settings | |
39 | +#"" | |
40 | + | |
41 | +# To avoid a "ModuleNotFoundError: No module named 'dashboard'"... (not even 1 app found) : | |
42 | +##sys.path.insert(0, os.path.abspath("..")) | |
43 | +##sys.path.insert(0, os.path.abspath("src")) | |
44 | +##sys.path.insert(0, "../src") | |
45 | +##sys.path.insert(0, "src") | |
46 | +# To avoid a "ModuleNotFoundError: No module named 'dashboard'" | |
47 | +## sys.path.append("..") | |
48 | +py_pwd = os.path.normpath(os.getcwd() + "/..") | |
49 | +if (py_pwd not in os.sys.path): | |
50 | + (os.sys.path).append(py_pwd) | |
51 | +# To avoid a "ModuleNotFoundError: No module named 'src'" | |
52 | +## sys.path.append("../../../..") | |
53 | +py_pwd = os.path.normpath(os.getcwd() + "/../../../..") | |
54 | +if (py_pwd not in os.sys.path): | |
55 | + (os.sys.path).append(py_pwd) | |
56 | +##sys.path.append("src") | |
57 | + | |
58 | + | |
59 | +def printd(*args, **kwargs): | |
60 | + if os.environ.get('PYROS_DEBUG', '0')=='1': print(*args, **kwargs) | |
61 | + | |
62 | + | |
63 | +printd("Starting with this sys.path", sys.path) | |
64 | + | |
65 | +# DJANGO setup | |
66 | +# self.printd("file is", __file__) | |
67 | +# mypath = os.getcwd() | |
68 | +# Go into src/ | |
69 | +##os.chdir("..") | |
70 | +##os.chdir("src") | |
71 | +printd("Current directory : " + str(os.getcwd())) | |
72 | + | |
73 | +#os.environ.setdefault("DJANGO_SETTINGS_MODULE", "src.core.pyros_django.pyros.settings") | |
74 | +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "src.core.pyros_django.pyros.settings") | |
75 | +# os.environ['SECRET_KEY'] = 'abc' | |
76 | +# os.environ['ENVIRONMENT'] = 'production' | |
77 | +import django | |
78 | + | |
79 | +django.setup() | |
80 | + | |
81 | +printd("DB2 used is:", djangosettings.DATABASES["default"]["NAME"]) | |
82 | +printd() | |
83 | + | |
84 | + | |
85 | +""" | |
86 | +================================================================= | |
87 | + IMPORT PYTHON PACKAGES | |
88 | +================================================================= | |
89 | +""" | |
90 | + | |
91 | +# --- GENERAL PURPOSE IMPORT --- | |
92 | +#from __future__ import absolute_import | |
93 | +##import utils.Logger as L | |
94 | +import platform | |
95 | +import random | |
96 | +import threading | |
97 | +#import multiprocessing | |
98 | +from threading import Thread | |
99 | +import time | |
100 | +import socket | |
101 | +#import ctypes | |
102 | +#import copy | |
103 | + | |
104 | +# --- DJANGO IMPORT --- | |
105 | +from django.db import transaction | |
106 | +from django import db | |
107 | +# from django.core.exceptions import ObjectDoesNotExist | |
108 | +# from django.db.models import Q | |
109 | +#from django.shortcuts import get_object_or_404 | |
110 | +#from django.conf import settings as djangosettings | |
111 | + | |
112 | +# --- SPECIFIC IMPORT --- | |
113 | +# Many ways to import configuration settings: | |
114 | +##import config | |
115 | +import config.old_config as config_old | |
116 | +#from config import PYROS_ENV, ROOT_DIR, DOC_DIR | |
117 | +#from config import * | |
118 | + | |
119 | +from common.models import AgentSurvey, AgentCmd, AgentLogs | |
120 | +#from config.configpyros import ConfigPyros | |
121 | +from config.old_config.configpyros import ConfigPyros | |
122 | +#from dashboard.views import get_sunelev | |
123 | +#from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault | |
124 | +#from utils.JDManipulator import * | |
125 | + | |
126 | +##from agent.logpyros import LogPyros | |
127 | +from src.logpyros import LogPyros | |
128 | + | |
129 | +from device_controller.abstract_component.device_controller import ( | |
130 | + DCCNotFoundException, UnknownGenericCmdException, UnimplementedGenericCmdException, UnknownNativeCmdException | |
131 | +) | |
132 | + | |
133 | + | |
134 | +""" | |
135 | +================================================================= | |
136 | + GENERAL MODULE CONSTANT & FUNCTIONS DEFINITIONS | |
137 | +================================================================= | |
138 | +""" | |
139 | + | |
140 | +#DEBUG_FILE = False | |
141 | + | |
142 | +##log = L.setupLogger("AgentLogger", "Agent") | |
143 | + | |
144 | +IS_WINDOWS = platform.system() == "Windows" | |
145 | + | |
146 | + | |
147 | +class Colors: | |
148 | + HEADER = "\033[95m" | |
149 | + BLUE = "\033[94m" | |
150 | + GREEN = "\033[92m" | |
151 | + WARNING = "\033[93m" | |
152 | + FAIL = "\033[91m" | |
153 | + ENDC = "\033[0m" | |
154 | + BOLD = "\033[1m" | |
155 | + UNDERLINE = "\033[4m" | |
156 | + | |
157 | +def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False): | |
158 | + #system = platform.system() | |
159 | + """ | |
160 | + if (self.disp == False and forced == False): | |
161 | + return 0 | |
162 | + """ | |
163 | + #if system == "Windows": | |
164 | + if IS_WINDOWS: | |
165 | + print(message, file=file, end=eol) | |
166 | + else: | |
167 | + print(color + message + Colors.ENDC, file=file, end=eol) | |
168 | + return 0 | |
169 | + | |
170 | +def printFullTerm(color: Colors, string: str): | |
171 | + #system = platform.system() | |
172 | + columns = 100 | |
173 | + row = 1000 | |
174 | + disp = True | |
175 | + value = int(columns / 2 - len(string) / 2) | |
176 | + printColor(color, "-" * value, eol="") | |
177 | + printColor(color, string, eol="") | |
178 | + value += len(string) | |
179 | + printColor(color, "-" * (columns - value)) | |
180 | + return 0 | |
181 | + | |
182 | + | |
183 | +""" | |
184 | +================================================================= | |
185 | + class StoppableThread | |
186 | +================================================================= | |
187 | +""" | |
188 | + | |
189 | +class StoppableThreadEvenWhenSleeping(threading.Thread): | |
190 | + # Thread class with a stop() method. The thread itself has to check | |
191 | + # regularly for the stopped() condition. | |
192 | + # It stops even if sleeping | |
193 | + # See https://python.developpez.com/faq/?page=Thread#ThreadKill | |
194 | + # See also https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s03.html | |
195 | + | |
196 | + def __init__(self, *args, **kwargs): | |
197 | + #super(StoppableThreadSimple, self).__init__(*args, **kwargs) | |
198 | + super().__init__(*args, **kwargs) | |
199 | + self._stop_event = threading.Event() | |
200 | + | |
201 | + #def stop(self): | |
202 | + def terminate(self): | |
203 | + self._stop_event.set() | |
204 | + | |
205 | + def stopped(self): | |
206 | + return self._stop_event.is_set() | |
207 | + | |
208 | + def wait(self, nbsec:float=2.0): | |
209 | + self._stop_event.wait(nbsec) | |
210 | + | |
211 | + | |
212 | + | |
213 | +""" | |
214 | +================================================================= | |
215 | + class Agent | |
216 | +================================================================= | |
217 | +""" | |
218 | + | |
219 | +class Agent2: | |
220 | + """ | |
221 | + See Agent_activity_diag.pu for PlantUML activity diagram | |
222 | + | |
223 | + Behavior of an agent: | |
224 | + - If idle : | |
225 | + - still does routine_process() and general_process() | |
226 | + - does not do specific_process() | |
227 | + - Once a command has been sent to another agent : | |
228 | + - It waits (non blocking) for the end of execution of the command and get its result | |
229 | + - If command is timed out or has been skipped or killed, then it is NOT re-executed at next iteration (except if needed explicitely) | |
230 | + """ | |
231 | + | |
232 | + # --- | |
233 | + # --- CLASS (STATIC) attributes (CONSTANTS) | |
234 | + # --- If agent is instance of Agent: | |
235 | + # --- - CLASS attributes are accessible via agent.__class__.__dict__ | |
236 | + # --- - INSTANCE attributes are accessible via agent.__dict__ | |
237 | + # --- | |
238 | + | |
239 | + # Default modes | |
240 | + DEBUG_MODE = False | |
241 | + WITH_SIMULATOR = False | |
242 | + TEST_MODE = False | |
243 | + | |
244 | + # Default LOG level is INFO | |
245 | + #PYROS_DEFAULT_GLOBAL_LOG_LEVEL = LogPyros.LOG_LEVEL_INFO # INFO | |
246 | + | |
247 | + # To be overriden by subclasses (empty by default, no agent specific command) | |
248 | + AGENT_SPECIFIC_COMMANDS = [ | |
249 | + #"do_eval", | |
250 | + #"set_state", | |
251 | + ] | |
252 | + | |
253 | + # Maximum duration of this agent (only for SIMULATION mode) | |
254 | + # If set to None, it will never exit except if asked (or CTRL-C) | |
255 | + # If set to 20, it will exit after 20s | |
256 | + TEST_MAX_DURATION_SEC = None | |
257 | + #TEST_MAX_DURATION_SEC = 30 | |
258 | + | |
259 | + # FOR TEST ONLY | |
260 | + # Run this agent in simulator mode | |
261 | + TEST_MODE = True | |
262 | + WITH_SIMULATOR = False | |
263 | + # Run the assertion tests at the end | |
264 | + TEST_WITH_FINAL_TEST = False | |
265 | + # Who should I send commands to ? | |
266 | + TEST_COMMANDS_DEST = "myself" | |
267 | + # Default scenario to be executed | |
268 | + #TEST_COMMANDS = iter([ | |
269 | + TEST_COMMANDS_LIST = [ | |
270 | + "set_state:active", | |
271 | + "set_state:idle", | |
272 | + | |
273 | + # specific0 not_executed_because_idle | |
274 | + "specific0", | |
275 | + | |
276 | + "set_state:active", | |
277 | + | |
278 | + # specific1 executed_because_not_idle, should complete ok | |
279 | + "specific1", | |
280 | + | |
281 | + # specific2 will be executed only when specific1 is finished, | |
282 | + # and should be aborted before end of execution, | |
283 | + # because of the 1st coming "do_abort" command below | |
284 | + "specific2", | |
285 | + | |
286 | + # specific3 should be executed only when specific2 is finished (in fact, aborted), | |
287 | + # and should be aborted before end of execution, | |
288 | + # because of the 2nd coming "do_abort" command below | |
289 | + "specific3", | |
290 | + | |
291 | + # These commands should not have the time to be processed | |
292 | + # because the "do_exit" command below should be executed before | |
293 | + "specific4", | |
294 | + "specific5", | |
295 | + "specific6", | |
296 | + "specific7", | |
297 | + "specific8", | |
298 | + | |
299 | + # Should abort the current running command (which should normally be specific2) | |
300 | + # even if commands above (specific3, ..., specific8) are already pending | |
301 | + "do_abort", | |
302 | + | |
303 | + # These commands (except abort) won't be executed | |
304 | + # because too many commands are already pending (above) | |
305 | + "specific9", | |
306 | + "do_abort", | |
307 | + "set_state:active", | |
308 | + "set_state:idle", | |
309 | + | |
310 | + # Should stop the agent even before the previous pending commands are executed | |
311 | + "do_exit", | |
312 | + | |
313 | + # Because of the previous "do_exit" command, | |
314 | + # these following commands should not be executed, | |
315 | + # and not even be added to the database command table | |
316 | + "set_state:active", | |
317 | + "specific10" | |
318 | + ] | |
319 | + #TEST_COMMANDS = iter(TEST_COMMANDS_LIST) | |
320 | + | |
321 | + | |
322 | + | |
323 | + """ | |
324 | + How to run this agent exec_specific_cmd() method ? | |
325 | + - True = inside a Thread (cannot be killed, must be asked to stop, and inadequate for computation) | |
326 | + - False = inside a Process | |
327 | + If thread, displays : | |
328 | + >>>>> Thread: starting execution of command specific1 | |
329 | + >>>>> Thread: PID: 2695, Process Name: MainProcess, Thread Name: Thread-1 | |
330 | + ... | |
331 | + >>>>> Thread: starting execution of command specific2 | |
332 | + >>>>> Thread: PID: 2695, Process Name: MainProcess, Thread Name: Thread-2 | |
333 | + ... | |
334 | + >>>>> Thread: starting execution of command specific3 | |
335 | + >>>>> Thread: PID: 2695, Process Name: MainProcess, Thread Name: Thread-3 | |
336 | + If process, displays : | |
337 | + >>>>> Thread: starting execution of command specific1 | |
338 | + >>>>> Thread: PID: 2687, Process Name: Process-1, Thread Name: MainThread | |
339 | + ... | |
340 | + >>>>> Thread: starting execution of command specific2 | |
341 | + >>>>> Thread: PID: 2689, Process Name: Process-2, Thread Name: MainThread | |
342 | + ... | |
343 | + >>>>> Thread: starting execution of command specific3 | |
344 | + >>>>> Thread: PID: 2690, Process Name: Process-3, Thread Name: MainThread | |
345 | + """ | |
346 | + # with thread | |
347 | + RUN_IN_THREAD = True | |
348 | + # with process | |
349 | + #RUN_IN_THREAD = False | |
350 | + | |
351 | + _thread_total_steps_number = 1 | |
352 | + | |
353 | + #COMMANDS_PEREMPTION_HOURS = 48 | |
354 | + #COMMANDS_PEREMPTION_HOURS = 60/60 | |
355 | + | |
356 | + name = "Generic Agent" | |
357 | + mainloop_waittime = 3 | |
358 | + subloop_waittime = 2 | |
359 | + status = None | |
360 | + mode = None | |
361 | + config = None | |
362 | + | |
363 | + # Statuses | |
364 | + STATUS_LAUNCH = "LAUNCHED" | |
365 | + STATUS_INIT = "INITIALIZING" | |
366 | + STATUS_MAIN_LOOP = "IN_MAIN_LOOP" | |
367 | + STATUS_GET_NEXT_COMMAND = "IN_GET_NEXT_COMMAND" | |
368 | + STATUS_GENERAL_PROCESS = "IN_GENERAL_PROCESS" | |
369 | + STATUS_ROUTINE_PROCESS = "IN_ROUTINE_PROCESS" | |
370 | + STATUS_SPECIFIC_PROCESS = "IN_SPECIFIC_PROCESS" | |
371 | + STATUS_EXIT = "EXITING" | |
372 | + | |
373 | + # Modes | |
374 | + MODE_ACTIVE = "ACTIVE" | |
375 | + MODE_IDLE = "IDLE" | |
376 | + | |
377 | + ''' Moved to more central file : config.config_base | |
378 | + PYROS_DJANGO_BASE_DIR = Path("src/core/pyros_django") # pathlib | |
379 | + DEFAULT_CONFIG_FILE_NAME = "config_unit_simulunit1.xml" | |
380 | + CONFIG_DIR_NAME = "config" | |
381 | + ''' | |
382 | + | |
383 | + # Parameters from config file | |
384 | + # for /src/ | |
385 | + #_path_data = '../../config' | |
386 | + # for /src/core/pyros_django/ | |
387 | + #_path_data = '../../../../config' | |
388 | + # Path to config | |
389 | + _path_data = '' | |
390 | + _computer_alias = '' | |
391 | + _computer_description = '' | |
392 | + | |
393 | + # Current and next command to send | |
394 | + _cmdts:AgentCmd = None | |
395 | + _next_cmdts = None | |
396 | + | |
397 | + _agent_survey = None | |
398 | + _pending_commands = [] | |
399 | + | |
400 | + ''' | |
401 | + _current_device_cmd = None | |
402 | + _current_device_cmd_thread = None | |
403 | + ''' | |
404 | + | |
405 | + # List of agents I will send commands to | |
406 | + _my_client_agents_aliases = [] | |
407 | + _my_client_agents = {} | |
408 | + | |
409 | + _iter_num = None | |
410 | + | |
411 | + # Log object | |
412 | + _log = None | |
413 | + | |
414 | + #def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True): | |
415 | + #def __init__(self, config_filename:str=None, RUN_IN_THREAD=True, DEBUG_MODE=False): | |
416 | + def __init__(self, config_filename:str=None, RUN_IN_THREAD=True): | |
417 | + | |
418 | + ''' | |
419 | + print('PYROS_ENV', PYROS_ENV) | |
420 | + print('ROOT_DIR', ROOT_DIR) | |
421 | + print('DOC_DIR', DOC_DIR) | |
422 | + ''' | |
423 | + print('config file is', config_filename) | |
424 | + ##print('PYROS_ENV', config.PYROS_ENV) | |
425 | + print('PYROS_ENV', config_old.PYROS_ENV) | |
426 | + print('ROOT_DIR', config_old.ROOT_DIR) | |
427 | + print('DOC_DIR', config_old.DOC_DIR) | |
428 | + ##if config.is_dev_env(): print("DEV ENV") | |
429 | + if config_old.is_dev_env(): print("DEV ENV") | |
430 | + if config_old.is_prod_env(): print("PROD ENV") | |
431 | + if config_old.is_debug(): print("IN DEBUG MODE") | |
432 | + | |
433 | + #self.name = name | |
434 | + self.name = self.__class__.__name__ | |
435 | + ''' | |
436 | + printd("*** ENVIRONMENT VARIABLE PYROS_DEBUG is:", os.environ.get('PYROS_DEBUG'), '***') | |
437 | + ##self.DEBUG_MODE = DEBUG_MODE | |
438 | + self.DEBUG_MODE = os.environ.get('PYROS_DEBUG', '0')=='1' | |
439 | + ''' | |
440 | + #self.DEBUG_MODE = config.PYROS_ENV | |
441 | + self.log = LogPyros(self.name, AgentLogs) | |
442 | + ##self.DEBUG_MODE = config.is_debug() | |
443 | + self.DEBUG_MODE = config_old.is_debug() | |
444 | + ##self.log.debug_level = DEBUG_MODE | |
445 | + ''' | |
446 | + # Default LOG level is INFO | |
447 | + log_level = LogPyros.LOG_LEVEL_INFO # INFO | |
448 | + self.log.set_global_log_level(LogPyros.LOG_LEVEL_DEBUG) if self.DEBUG_MODE else self.log.set_global_log_level(log_level) | |
449 | + ''' | |
450 | + #global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else self.PYROS_DEFAULT_GLOBAL_LOG_LEVEL | |
451 | + ##global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else config.PYROS_DEFAULT_GLOBAL_LOG_LEVEL | |
452 | + global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else config_old.PYROS_DEFAULT_GLOBAL_LOG_LEVEL | |
453 | + self.log.set_global_log_level(global_log_level) | |
454 | + ##self.printd("LOG LEVEL IS:", self.log.debug_level) | |
455 | + self.print("LOG LEVEL IS:", self.log.get_global_log_level()) | |
456 | + | |
457 | + # Est-ce bien utile ??? | |
458 | + # New way with PathLib | |
459 | + my_parent_abs_dir = Path(__file__).resolve().parent | |
460 | + #TODO: on doit pouvoir faire mieux avec pathlib (sans utiliser str()) | |
461 | + ##self._path_data = str( Path( str(my_parent_abs_dir).split(str(self.PYROS_DJANGO_BASE_DIR))[0] ) / self.CONFIG_DIR_NAME ) | |
462 | + ##self._path_data = config.CONFIG_DIR | |
463 | + self._path_data = config_old.CONFIG_DIR | |
464 | + | |
465 | + #self._set_mode(self.MODE_IDLE) | |
466 | + config_filename = self.get_config_filename(config_filename) | |
467 | + self.printd(f"*** Config file used is={config_filename}") | |
468 | + self.config = ConfigPyros(config_filename) | |
469 | + if self.config.get_last_errno() != self.config.NO_ERROR: | |
470 | + raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") | |
471 | + | |
472 | + #TODO: : à mettre dans la config | |
473 | + ''' | |
474 | + 'AgentDeviceTelescope1': 'AgentDeviceTelescopeGemini', | |
475 | + 'AgentDeviceFilterSelector1': 'AgentDeviceSBIG', | |
476 | + 'AgentDeviceShutter1': 'AgentDeviceSBIG', | |
477 | + 'AgentDeviceSensor1': 'AgentDeviceSBIG', | |
478 | + ''' | |
479 | + #self._my_client_agents = {} | |
480 | + | |
481 | + ### self._agent_logs = AgentLogs.objects.create(name=self.name, message="Step __init__") | |
482 | + self.printdb("Step __init__") | |
483 | + | |
484 | + self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST) | |
485 | + self.RUN_IN_THREAD = RUN_IN_THREAD | |
486 | + self._set_status(self.STATUS_LAUNCH) | |
487 | + self._set_idle() | |
488 | + | |
489 | + self._set_agent_device_aliases_from_config(self.name) | |
490 | + self._set_mode_from_config(self.name) | |
491 | + # TODO: remove | |
492 | + #self._set_idle() | |
493 | + self._set_active() | |
494 | + | |
495 | + # Create 1st survey if none | |
496 | + #tmp = AgentSurvey.objects.filter(name=self.name) | |
497 | + #if len(tmp) == 0: | |
498 | + #nb_agents = AgentSurvey.objects.filter(name=self.name).count() | |
499 | + #if nb_agents == 0: | |
500 | + if AgentSurvey.objects.filter(name=self.name).exists(): | |
501 | + self._agent_survey = AgentSurvey.objects.get(name=self.name) | |
502 | + else: | |
503 | + self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration=60, mode=self.mode, status=self.status, iteration=-1) | |
504 | + self.printd("Agent survey is", self._agent_survey) | |
505 | + | |
506 | + | |
507 | + def get_config_filename(self, config_filename: str): | |
508 | + if not config_filename: | |
509 | + #config_filename = self.DEFAULT_CONFIG_FILE_NAME | |
510 | + ##config_filename = config.DEFAULT_CONFIG_FILE_NAME | |
511 | + config_filename = config_old.DEFAULT_CONFIG_FILE_NAME | |
512 | + # If config file name is RELATIVE (i.e. without path, just the file name) | |
513 | + # => give it an absolute path (and remove "src/agent/" from it) | |
514 | + if config_filename == os.path.basename(config_filename): | |
515 | + ##config_filename = os.path.join(config.CONFIG_DIR, config_filename) | |
516 | + config_filename = os.path.join(config_old.CONFIG_DIR, config_filename) | |
517 | + ''' | |
518 | + # Build abs path including current agent dir | |
519 | + config_filename = os.path.abspath(self.CONFIG_DIR_NAME + os.sep + config_filename) | |
520 | + # Remove "src/agent_name/" from abs dir : | |
521 | + # (1) Remove "src/core/pyros_django/" | |
522 | + config_filename = config_filename.replace(str(self.PYROS_DJANGO_BASE_DIR)+os.sep, os.sep) | |
523 | + # (2) Remove "agent_name/" | |
524 | + #TODO: bidouille, faire plus propre | |
525 | + config_filename = config_filename.replace(os.sep+"agent"+os.sep, os.sep) | |
526 | + config_filename = config_filename.replace(os.sep+"monitoring"+os.sep, os.sep) | |
527 | + ''' | |
528 | + return os.path.normpath(config_filename) | |
529 | + | |
530 | + def __repr__(self): | |
531 | + return "I am agent " + self.name | |
532 | + | |
533 | + def __str__(self): | |
534 | + return self.__repr__() | |
535 | + #return "I am agent " + self.name | |
536 | + | |
537 | + # Normal print | |
538 | + def print(self, *args, **kwargs): self.log.print(*args, **kwargs) | |
539 | + """ | |
540 | + if args: | |
541 | + self.printd(f"({self.name}): ", *args, **kwargs) | |
542 | + else: | |
543 | + self.printd() | |
544 | + """ | |
545 | + # DEBUG print shortcut | |
546 | + def printd(self, *args, **kwargs): self.log.printd(*args, **kwargs) | |
547 | + """ | |
548 | + if DEBUG: self.printd(d(*args, **kwargs) | |
549 | + """ | |
550 | + def log_d(self, *args, **kwargs): self.log.log_d(*args, **kwargs) | |
551 | + def log_i(self, *args, **kwargs): self.log.log_i(*args, **kwargs) | |
552 | + def log_w(self, *args, **kwargs): self.log.log_w(*args, **kwargs) | |
553 | + def log_e(self, *args, **kwargs): self.log.log_e(*args, **kwargs) | |
554 | + def log_c(self, *args, **kwargs): self.log.log_c(*args, **kwargs) | |
555 | + def printdb(self, *args, **kwargs): self.log.db( *args, **kwargs) | |
556 | + | |
557 | + def sleep(self, nbsec:float=2.0): | |
558 | + ''' | |
559 | + # thread | |
560 | + if self._current_device_cmd_thread and self.RUN_IN_THREAD: | |
561 | + self._current_device_cmd_thread.wait(nbsec) | |
562 | + # process (or main thread) | |
563 | + else: | |
564 | + time.sleep(nbsec) | |
565 | + ''' | |
566 | + time.sleep(nbsec) | |
567 | + | |
568 | + def _get_real_agent_name(self, agent_alias_name:str)->str: | |
569 | + #self.printd("key is", agent_alias_name) | |
570 | + ''' | |
571 | + if not self._my_client_agents: return agent_alias_name | |
572 | + return self._my_client_agents[agent_alias_name] | |
573 | + ''' | |
574 | + return self._my_client_agents.get(agent_alias_name) | |
575 | + | |
576 | + | |
577 | + | |
578 | + def run(self, nb_iter:int=None, FOR_REAL:bool=True): | |
579 | + """ | |
580 | + FOR_REAL: set to False if you don't want Agent to send commands to devices but just print messages without really doing anything | |
581 | + """ | |
582 | + | |
583 | + # TEST MODE ONLY | |
584 | + # IF in test mode but with REAL devices (no SIMULATOR), delete all dangerous commands from the test commands list scenario: | |
585 | + if self.TEST_MODE: | |
586 | + self.printd("\n!!! In TEST mode !!! => preparing to run a scenario of test commands") | |
587 | + self.printd("- Current test commands list scenario is:\n", self.TEST_COMMANDS_LIST) | |
588 | + if not self.WITH_SIMULATOR: | |
589 | + self.printd("\n!!! In TEST but no SIMULATOR mode (using REAL device) !!! => removing dangerous commands for real devices... :") | |
590 | + TEST_COMMANDS_LIST_copy = self.TEST_COMMANDS_LIST.copy() | |
591 | + for cmd in TEST_COMMANDS_LIST_copy: | |
592 | + cmd_key = cmd.split()[1] | |
593 | + if ("set_" in cmd_key) or ("do_start" in cmd_key) or cmd_key in ["do_init", "do_goto", "do_open", "do_close"]: | |
594 | + self.TEST_COMMANDS_LIST.remove(cmd) | |
595 | + self.printd("- NEW test commands list scenario is:\n", self.TEST_COMMANDS_LIST, '\n') | |
596 | + | |
597 | + self._DO_EXIT = False | |
598 | + self._DO_RESTART = True | |
599 | + | |
600 | + # Will loop again only if _DO_RESTART is True | |
601 | + while self._DO_RESTART: | |
602 | + self._DO_RESTART = False | |
603 | + self.start_time = time.time() | |
604 | + self.printd("on est ici: ", os.getcwd()) | |
605 | + | |
606 | + self._load_config() | |
607 | + | |
608 | + self.print_TEST_MODE() | |
609 | + | |
610 | + self.init() | |
611 | + ''' testing log: | |
612 | + self.log_e("ERROR") | |
613 | + self.log_c("FATAL critical ERROR") | |
614 | + ''' | |
615 | + self.log_w("WARNING", "watch your step !") | |
616 | + | |
617 | + # Avoid blocking on false "running" commands | |
618 | + # (old commands that stayed with "running" status when agent was killed) | |
619 | + AgentCmd.delete_commands_with_running_status_for_agent(self.name) | |
620 | + | |
621 | + self._iter_num = 1 | |
622 | + self._DO_MAIN_LOOP = True | |
623 | + # Main loop | |
624 | + while self._DO_MAIN_LOOP: | |
625 | + try: | |
626 | + self._main_loop(nb_iter,FOR_REAL) | |
627 | + if not self._DO_MAIN_LOOP: break | |
628 | + except KeyboardInterrupt: # CTRL-C | |
629 | + # In case of CTRL-C, kill the current thread (process) before dying (in error) | |
630 | + self.print("CTRL-C Interrupted, I kill the current thread (process) before exiting (if exists)") | |
631 | + self._kill_running_device_cmd_if_exists("USER_CTRLC") | |
632 | + exit(1) | |
633 | + | |
634 | + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: self._TEST_test_results() | |
635 | + #if self._DO_EXIT: exit(0) | |
636 | + | |
637 | + | |
638 | + | |
639 | + # DEVICE level | |
640 | + def _kill_running_device_cmd_if_exists(self, abort_cmd_sender): | |
641 | + pass | |
642 | + | |
643 | + | |
644 | + def _main_loop(self, nb_iter:int=None, FOR_REAL:bool=True): | |
645 | + | |
646 | + self._main_loop_start(nb_iter) | |
647 | + if not self._DO_MAIN_LOOP: return | |
648 | + | |
649 | + self._load_config() # only if changed | |
650 | + | |
651 | + # Log this agent status (update my current mode and status in DB) | |
652 | + self._log_agent_status() | |
653 | + | |
654 | + #self.printd("====== START COMMMANDS PROCESSING ======") | |
655 | + | |
656 | + # ROUTINE process | |
657 | + # To be overriden to be specified by subclass | |
658 | + self._routine_process() | |
659 | + #self.printd("I am IDLE, so I bypass the routine_process (do not send any new command)") | |
660 | + | |
661 | + # Processing the next pending command if exists | |
662 | + self._command_process_if_exists() | |
663 | + | |
664 | + # if restart, exit this loop to restart from beginning | |
665 | + if self._DO_RESTART or self._DO_EXIT: | |
666 | + self._DO_MAIN_LOOP = False | |
667 | + return | |
668 | + | |
669 | + #self.printd("====== END COMMMANDS PROCESSING ======") | |
670 | + | |
671 | + #self.waitfor(self.mainloop_waittime) | |
672 | + | |
673 | + self.print("*"*20, "MAIN LOOP ITERATION (END)", "*"*20) | |
674 | + #self.do_log(LOG_DEBUG, "Ending main loop iteration") | |
675 | + | |
676 | + self._iter_num += 1 | |
677 | + | |
678 | + | |
679 | + def _main_loop_start(self, nb_iter:int): | |
680 | + | |
681 | + for i in range(3): self.print() | |
682 | + #self.printd("-"*80) | |
683 | + self.print("*"*73) | |
684 | + self.print("*"*20, f"MAIN LOOP ITERATION {self._iter_num} (START)", "*"*20) | |
685 | + self.print("*"*73, '\n') | |
686 | + #self.print(f"Iteration {self._iter_num}") | |
687 | + | |
688 | + # EXIT because of nb of iterations ? | |
689 | + if nb_iter is not None: | |
690 | + # Bad number of iterations or nb iterations reached => exit | |
691 | + if nb_iter <= 0 or nb_iter < self._iter_num: | |
692 | + self.print(f"Exit because number of iterations asked ({nb_iter}) has been reached") | |
693 | + self._DO_MAIN_LOOP = False | |
694 | + return | |
695 | + | |
696 | + # Wait a random number of sec before starting iteration | |
697 | + # (to let another agent having the chance to send a command before me) | |
698 | + random_waiting_sec = random.randint(0,5) | |
699 | + self.print(f"Waiting {random_waiting_sec} sec (random) before starting new iteration...") | |
700 | + time.sleep(random_waiting_sec) | |
701 | + | |
702 | + # (Test only) | |
703 | + # EXIT because max duration reached ? | |
704 | + if self.TEST_MAX_DURATION_SEC and (time.time()-self.start_time > self.TEST_MAX_DURATION_SEC): | |
705 | + self.print("Exit because of max duration set to ", self.TEST_MAX_DURATION_SEC, "s") | |
706 | + self._kill_running_device_cmd_if_exists(self.name) | |
707 | + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: self._TEST_test_results() | |
708 | + self._DO_MAIN_LOOP = False | |
709 | + return | |
710 | + | |
711 | + self._set_status(self.STATUS_MAIN_LOOP) | |
712 | + self.show_mode_and_status() | |
713 | + | |
714 | + | |
715 | + | |
716 | + def _command_process_if_exists(self): | |
717 | + ''' Processing the next pending command if exists ''' | |
718 | + | |
719 | + self.print() | |
720 | + self.print() | |
721 | + self.print("*"*10, "NEXT COMMAND PROCESSING (START)", "*"*10, '\n') | |
722 | + | |
723 | + | |
724 | + # Purge commands (every N iterations, delete old commands) | |
725 | + N=5 | |
726 | + if ((self._iter_num-1) % N) == 0: | |
727 | + self.print("Purging old commands if exists") | |
728 | + #AgentCmd.purge_old_commands_for_agent(self.name) | |
729 | + self._purge_old_commands_sent_to_me() | |
730 | + | |
731 | + # Get next command and process it (if exists) | |
732 | + cmd = self._get_next_valid_and_not_running_command() | |
733 | + #self._set_status(self.STATUS_GENERAL_PROCESS) | |
734 | + #if cmd: self.command_process(cmd) | |
735 | + if cmd: | |
736 | + self.print('-'*6) | |
737 | + self.print('-'*6, "RECEIVED NEW COMMAND TO PROCESS:") | |
738 | + self.print('-'*6, cmd) | |
739 | + self.print('-'*6) | |
740 | + | |
741 | + # CASE 1 - AGENT LEVEL command | |
742 | + # => I process it directly without asking my DC | |
743 | + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel) | |
744 | + if self.is_agent_level_cmd(cmd): | |
745 | + self.print("(AGENT LEVEL CMD)") | |
746 | + try: | |
747 | + self._exec_agent_cmd(cmd) | |
748 | + except AttributeError as e: | |
749 | + self.log_e(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e) | |
750 | + self.log_e("Thus => I ignore this command...") | |
751 | + cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND") | |
752 | + cmd.set_as_pending() | |
753 | + cmd.set_as_skipped() | |
754 | + | |
755 | + # CASE 2 - DEVICE LEVEL command | |
756 | + # => I delegate it to my DC | |
757 | + # => Execute it only if I am active and no currently running another device level cmd | |
758 | + # => Long execution time, so I will execute it in parallel (in a new thread or process) | |
759 | + elif self.is_device_level_cmd(cmd): | |
760 | + self.print("(DEVICE LEVEL CMD)") | |
761 | + try: | |
762 | + self.exec_device_cmd_if_possible(cmd) | |
763 | + except (UnimplementedGenericCmdException) as e: | |
764 | + #except (UnknownGenericCmdException, UnimplementedGenericCmdException, UnknownNativeCmdException) as e: | |
765 | + self.log_e(f"EXCEPTION caught by {type(self).__name__} (from Agent mainloop) for command '{cmd.name}'", e) | |
766 | + self.log_e("Thus ==> ignore this command") | |
767 | + cmd.set_result(e) | |
768 | + #cmd.set_as_killed_by(type(self).__name__) | |
769 | + cmd.set_as_skipped() | |
770 | + #raise | |
771 | + | |
772 | + # CASE 3 - INVALID COMMAND | |
773 | + else: | |
774 | + #raise Exception("INVALID COMMAND: " + cmd.name) | |
775 | + self.print("******************************************************") | |
776 | + self.print("*************** ERROR: INVALID COMMAND ***************") | |
777 | + self.print("******************************************************") | |
778 | + self.print("Thus => I ignore this command...") | |
779 | + cmd.set_result("ERROR: INVALID COMMAND") | |
780 | + cmd.set_as_skipped() | |
781 | + | |
782 | + self.print() | |
783 | + self.print("*"*10, "NEXT COMMAND PROCESSING (END)", "*"*10, "\n") | |
784 | + | |
785 | + | |
786 | + | |
787 | + def print_TEST_MODE(self): | |
788 | + if self.TEST_MODE: | |
789 | + self.printd("[IN TEST MODE]") | |
790 | + self.print("Flush previous commands to be sure to start in clean state") | |
791 | + AgentCmd.delete_pending_commands_for_agent(self.name) | |
792 | + else: | |
793 | + self.printd("[IN NORMAL MODE]") | |
794 | + self.TEST_MAX_DURATION_SEC=None | |
795 | + | |
796 | + | |
797 | + def _purge_old_commands_sent_to_me(self): | |
798 | + AgentCmd.purge_old_commands_sent_to_agent(self.name) | |
799 | + | |
800 | + | |
801 | + def _routine_process(self): | |
802 | + """ | |
803 | + Routine processing. | |
804 | + This is a command or set of commands that this agent sends regularly, | |
805 | + (or just a regular processing) | |
806 | + at each iteration | |
807 | + """ | |
808 | + self.print() | |
809 | + self.print() | |
810 | + self.print("*"*10, "ROUTINE PROCESSING (START)", "*"*10, '\n') | |
811 | + | |
812 | + self._set_status(self.STATUS_ROUTINE_PROCESS) | |
813 | + self.routine_process_body() | |
814 | + self.print() | |
815 | + self.print("*"*10, "ROUTINE PROCESSING (END)", "*"*10) | |
816 | + | |
817 | + # To be overridden by subclasses | |
818 | + def routine_process_body(self): | |
819 | + if self.TEST_MODE: self._test_routine_process() | |
820 | + | |
821 | + | |
822 | + """ | |
823 | + def purge_commands(self): | |
824 | + ### | |
825 | + Delete commands (which I am recipient of) older than COMMANDS_PEREMPTION_HOURS (like 48h) | |
826 | + ATTENTION !!! EXCEPT the RUNNING command !!! | |
827 | + | |
828 | + NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc) | |
829 | + ### | |
830 | + | |
831 | + self.printd("Looking for old commands to purge...") | |
832 | + ### | |
833 | + COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS) | |
834 | + #self.printd("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW) | |
835 | + old_commands = AgentCmd.objects.filter( | |
836 | + # only commands for me | |
837 | + recipient = self.name, | |
838 | + # only pending commands | |
839 | + sender_deposit_time__lt = COMMAND_PEREMPTION_DATE_FROM_NOW, | |
840 | + ) | |
841 | + ### | |
842 | + old_commands = AgentCmd.get_old_commands_for_agent(self.name) | |
843 | + if old_commands.exists(): | |
844 | + self.printd("Found old commands to delete:") | |
845 | + for cmd in old_commands: self.printd(cmd) | |
846 | + old_commands.delete() | |
847 | + """ | |
848 | + | |
849 | + def waitfor(self, nbsec): | |
850 | + self.print(f"Now, waiting for {nbsec} seconds...") | |
851 | + time.sleep(nbsec) | |
852 | + | |
853 | + def _set_status(self, status:str): | |
854 | + #self.printd(f"[{status}] (switching from status {self.status})") | |
855 | + self.printd(f"[{status}]") | |
856 | + self.status = status | |
857 | + return False | |
858 | + | |
859 | + def _set_mode(self, mode:str): | |
860 | + #self.printd(f"Switching from mode {self.mode} to mode {mode}") | |
861 | + self.print(f"[NEW MODE {mode}]") | |
862 | + self.mode = mode | |
863 | + | |
864 | + def _is_active(self): | |
865 | + return self.mode == self.MODE_ACTIVE | |
866 | + def _is_idle(self): | |
867 | + return not self._is_active() | |
868 | + | |
869 | + def _set_active(self): | |
870 | + self._set_mode(self.MODE_ACTIVE) | |
871 | + | |
872 | + def _set_idle(self): | |
873 | + self._set_mode(self.MODE_IDLE) | |
874 | + | |
875 | + def show_mode_and_status(self): | |
876 | + self.print(f"CURRENT MODE is {self.mode} (status is {self.status})") | |
877 | + | |
878 | + def die(self): | |
879 | + self._set_status(self.STATUS_EXIT) | |
880 | + | |
881 | + """ | |
882 | + suspend/resume | |
883 | + """ | |
884 | + def suspend(self): | |
885 | + """ | |
886 | + TODO: | |
887 | + Mode IDLE (doit rester à l'écoute d'un resume, | |
888 | + et doit continuer à alimenter les tables pour informer de son état via tables agents_logs, | |
889 | + et lire table agents_command pour reprendre via resume, | |
890 | + et update la table agents_survey pour donner son status "idle" | |
891 | + """ | |
892 | + self._set_idle() | |
893 | + return True | |
894 | + | |
895 | + def resume(self): | |
896 | + """ | |
897 | + Quit suspend() mode | |
898 | + """ | |
899 | + self._set_active() | |
900 | + return True | |
901 | + | |
902 | + | |
903 | + #TODO: | |
904 | + def _set_agent_device_aliases_from_config(self, agent_alias): | |
905 | + for a in self._my_client_agents_aliases: | |
906 | + # TODO: activer | |
907 | + ##self._my_client_agents[a] = self.config.get_paramvalue(a,'general','real_agent_device_name') | |
908 | + pass | |
909 | + | |
910 | + def _set_mode_from_config(self, agent_name): | |
911 | + # --- Get the startmode of the AgentX | |
912 | + modestr = self.config.get_paramvalue(agent_name,'general','startmode') | |
913 | + if self.config.get_last_errno() != self.config.NO_ERROR: | |
914 | + raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") | |
915 | + if (modestr == None): | |
916 | + return True | |
917 | + # --- Set the mode according the startmode value | |
918 | + mode = self.MODE_IDLE | |
919 | + if modestr.upper() == 'RUN': | |
920 | + mode = self.MODE_ACTIVE | |
921 | + self._set_mode(mode) | |
922 | + return True | |
923 | + | |
924 | + | |
925 | + """ | |
926 | + ================================================================= | |
927 | + Generic methods that may be specialized (overriden) by subclasses | |
928 | + ================================================================= | |
929 | + """ | |
930 | + | |
931 | + def init(self): | |
932 | + self.printd("Initializing...") | |
933 | + self._set_status(self.STATUS_INIT) | |
934 | + | |
935 | + def _load_config(self): | |
936 | + """ | |
937 | + TODO: | |
938 | + 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) | |
939 | + """ | |
940 | + ''' | |
941 | + # SETUP | |
942 | + try: | |
943 | + self.config = get_object_or_404(Config, id=1) | |
944 | + # By default, set mode to SCHEDULER (False = REMOTE, which should never be the default) | |
945 | + self.config.global_mode = True | |
946 | + self.config.save() | |
947 | + # self.config = Config.objects.get(pk=1) | |
948 | + # self.config = Config.objects.get()[0] | |
949 | + except Exception as e: | |
950 | + # except Config.ObjectDoesNotExist: | |
951 | + self.printd("Config read (or write) exception", str(e)) | |
952 | + return -1 | |
953 | + ''' | |
954 | + self.printd("Loading the config file...") | |
955 | + self.config.load() | |
956 | + if self.config.get_last_errno() != self.config.NO_ERROR: | |
957 | + raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") | |
958 | + if not self.config.is_config_contents_changed(): | |
959 | + return | |
960 | + | |
961 | + # === display informations | |
962 | + # --- Get the assembly aliases of the unit | |
963 | + assembled_mount_aliases = [] | |
964 | + assembled_channel_aliases = [] | |
965 | + assembled_aliases = [] | |
966 | + unit_alias = self.config.get_aliases('unit')[0] | |
967 | + params = self.config.get_params(unit_alias) | |
968 | + for param in params: | |
969 | + if param['section']=="assembly" and param['key']=="alias": | |
970 | + assembled_aliases.append(param['value']) | |
971 | + #self.printd(f"Unit {unit_alias} is the assembly of {assembled_aliases}") | |
972 | + | |
973 | + self.printd("--------- Components of the unit -----------") | |
974 | + self.printd("Configuration file is {}".format(self.config.get_configfile())) | |
975 | + alias = self.config.get_aliases('unit')[0] | |
976 | + namevalue = self.config.get_paramvalue(alias,'unit','description') | |
977 | + self.printd(f"Unit alias is {alias}. Description is {namevalue}:") | |
978 | + unit_subtags = self.config.get_unit_subtags() | |
979 | + for unit_subtag in unit_subtags: | |
980 | + aliases = self.config.get_aliases(unit_subtag) | |
981 | + for alias in aliases: | |
982 | + namevalue = self.config.get_paramvalue(alias,unit_subtag,'description') | |
983 | + self.printd(f"- {unit_subtag} alias is {alias}. Description is {namevalue}") | |
984 | + # --- fill the list of mount and channel assembled | |
985 | + if alias in assembled_aliases: | |
986 | + if unit_subtag=="mount": | |
987 | + assembled_mount_aliases.append(alias) | |
988 | + elif unit_subtag=="channel": | |
989 | + assembled_channel_aliases.append(alias) | |
990 | + | |
991 | + self.printd("--------- Assembly of the unit -----------") | |
992 | + self.printd(f"Assembled mount aliases: {assembled_mount_aliases}") | |
993 | + self.printd(f"Assembled channel aliases: {assembled_channel_aliases}") | |
994 | + | |
995 | + # --- Get the home of the mount[0] | |
996 | + mount_alias = assembled_mount_aliases[0] | |
997 | + home = self.config.get_paramvalue(mount_alias,'MountPointing','home') | |
998 | + | |
999 | + self.printd("------------------------------------------") | |
1000 | + hostname = socket.gethostname() | |
1001 | + self._computer_alias = '' | |
1002 | + unit_subtag = 'computer' | |
1003 | + aliases = self.config.get_aliases(unit_subtag) | |
1004 | + for alias in aliases: | |
1005 | + self.printd("alias", alias) | |
1006 | + value = self.config.get_paramvalue(alias,'local','hostname') | |
1007 | + self.printd("value", value) | |
1008 | + if value == hostname: | |
1009 | + self.printd("value", value) | |
1010 | + self._computer_alias = alias | |
1011 | + value = self.config.get_paramvalue(alias,unit_subtag,'description') | |
1012 | + self._computer_description = value | |
1013 | + value = self.config.get_paramvalue(alias,'path','data') | |
1014 | + # Overrides default value | |
1015 | + self._path_data = value | |
1016 | + break | |
1017 | + self.printd(f"hostname = {hostname}") | |
1018 | + self.printd(f"path_data = {self._path_data}") | |
1019 | + self.printd(f"home = {home}") | |
1020 | + self.printd("------------------------------------------") | |
1021 | + | |
1022 | + # --- update the log parameters | |
1023 | + ##self.log.path_data = self._path_data | |
1024 | + ##print("new self.log.path_data is", self.log.path_data) | |
1025 | + self.log.set_global_path_data(self._path_data) | |
1026 | + self.printd("new self.log.global_path_data is", self.log.get_global_path_data()) | |
1027 | + self.log.home = home | |
1028 | + | |
1029 | + | |
1030 | + #def update_survey(self): | |
1031 | + def _log_agent_status(self): | |
1032 | + """ | |
1033 | + Save (update) this agent current mode and status in DB | |
1034 | + """ | |
1035 | + self.printd("Updating the agent survey database table...") | |
1036 | + #self.printd("- fetching table line for agent", self.name) | |
1037 | + # only necessary when using process (not necessary with threads) | |
1038 | + #with transaction.atomic(): | |
1039 | + #self._agent_survey = AgentSurvey.objects.get(name=self.name) | |
1040 | + self._agent_survey.mode = self.mode | |
1041 | + self._agent_survey.status = self.status | |
1042 | + self._agent_survey.iteration = self._iter_num | |
1043 | + self._agent_survey.save() | |
1044 | + #self._agent_survey.save(update_fields=["mode", "status"]) | |
1045 | + | |
1046 | + | |
1047 | + """ | |
1048 | + def send_command(self, cmd_name): | |
1049 | + recipient_agent = self.name if self.TEST_COMMANDS_DEST=="myself" else self.TEST_COMMANDS_DEST | |
1050 | + AgentCmd.objects.create(sender=self.name, recipient=recipient_agent, name=cmd_name) | |
1051 | + """ | |
1052 | + #def send_command(self, to_agent, cmd_type, cmd_name, cmd_args=None): | |
1053 | + def send_cmd_to(self, to_agent, cmd_name, cmd_args=None): | |
1054 | + """ | |
1055 | + #ex: send_command(“AgentX”,”GENERIC”,”EVAL”,“3+4”) | |
1056 | + ex: send_command(“AgentX”,"EVAL”,“3+4”) | |
1057 | + """ | |
1058 | + #return AgentCmd.send_cmd(self.name, self._get_real_agent_name_for_alias(to_agent), cmd_name, cmd_args) | |
1059 | + cmd = self.create_cmd_for(to_agent, cmd_name, cmd_args) | |
1060 | + cmd.send() | |
1061 | + return cmd | |
1062 | + | |
1063 | + def create_cmd_for(self, to_agent, cmd_name, cmd_args=None)->AgentCmd: | |
1064 | + ''' | |
1065 | + real_agent_name = self._get_real_agent_name(to_agent) | |
1066 | + real_cmd_name = cmd_name | |
1067 | + if '.' in real_agent_name: | |
1068 | + real_agent_name, component_name = real_agent_name.split('.') | |
1069 | + real_cmd_name = component_name+'.'+cmd_name | |
1070 | + return AgentCmd.create(self.name, real_agent_name, real_cmd_name, cmd_args) | |
1071 | + try: | |
1072 | + real_agent_name = self._get_real_agent_name(to_agent) | |
1073 | + except KeyError as e: | |
1074 | + ''' | |
1075 | + real_agent_name = self._get_real_agent_name(to_agent) | |
1076 | + if not real_agent_name: | |
1077 | + self.log_e("UNKNOWN AgentDevice ALIAS", to_agent) | |
1078 | + #self.log_e("Exception raised", e) | |
1079 | + self.log_e(f"=> Thus, I do not send this command '{cmd_name}'") | |
1080 | + return None | |
1081 | + return AgentCmd.create(self.name, real_agent_name, cmd_name, cmd_args) | |
1082 | + ''' | |
1083 | + return AgentCmd( | |
1084 | + sender=self.name, | |
1085 | + recipient=self._get_real_agent_name_for_alias(recipient_agent_alias_name), | |
1086 | + name=cmd_name | |
1087 | + ) | |
1088 | + ''' | |
1089 | + | |
1090 | + def _get_next_valid_and_not_running_command(self)->AgentCmd: | |
1091 | + """ | |
1092 | + Return next VALID (not expired) command (read from the DB command table) | |
1093 | + which is relevant to this agent. | |
1094 | + Commands are read in chronological order | |
1095 | + """ | |
1096 | + self._set_status(self.STATUS_GET_NEXT_COMMAND) | |
1097 | + self.print("Looking for a new command to process (sent by another agent):") | |
1098 | + | |
1099 | + # 1) Get all pending commands for me (return if None) | |
1100 | + # Not sure this is necessary to do it in a transaction, | |
1101 | + # but there might be a risk | |
1102 | + # that a command status is modified while we are reading... | |
1103 | + with transaction.atomic(): | |
1104 | + self._pending_commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name) | |
1105 | + commands = self._pending_commands | |
1106 | + if not commands.exists(): | |
1107 | + self.print("<None>") | |
1108 | + return None | |
1109 | + self.printd("Current pending (or running) commands are (time ordered):") | |
1110 | + AgentCmd.show_commands(commands) | |
1111 | + | |
1112 | + # 2) If there is a "do_exit" or "do_abort" command pending (even at the end of the list), | |
1113 | + # which is VALID (not expired), | |
1114 | + # then pass it straight away to general_process() for execution | |
1115 | + for cmd in commands: | |
1116 | + #if cmd.name in ("do_exit", "do_abort", "do_flush_commands"): break | |
1117 | + if cmd.name in ("do_exit", "do_abort"): break | |
1118 | + #if cmd.name in ("do_exit", "do_abort", "do_flush_commands"): | |
1119 | + if cmd.name in ("do_exit", "do_abort"): | |
1120 | + if cmd.is_running(): | |
1121 | + return None | |
1122 | + if cmd.is_expired(): | |
1123 | + cmd.set_as_outofdate() | |
1124 | + return None | |
1125 | + return cmd | |
1126 | + | |
1127 | + # 3) If first (oldest) command is currently running | |
1128 | + # (status CMD_RUNNING), then do nothing and return | |
1129 | + """ | |
1130 | + cmd_executing = Command.objects.filter( | |
1131 | + # only commands for me | |
1132 | + recipient = self.name, | |
1133 | + # only pending commands | |
1134 | + recipient_status_code = Command.CMD_STATUS_CODES.CMD_RUNNING, | |
1135 | + ).first() | |
1136 | + #if cmd_executing.exists(): | |
1137 | + if cmd_executing: | |
1138 | + """ | |
1139 | + #cmd = commands[0] | |
1140 | + cmd = commands.first() | |
1141 | + if cmd.is_expired(): | |
1142 | + cmd.set_as_outofdate() | |
1143 | + return None | |
1144 | + if cmd.is_running(): | |
1145 | + #self.printd(f"There is currently a running command ({cmd_executing.first().name}), so I do nothing (wait for end of execution)") | |
1146 | + self.print(f"There is currently a running command ({cmd.name})") | |
1147 | + """ | |
1148 | + # Check that this command is not expired | |
1149 | + if cmd.is_expired(): | |
1150 | + self.printd("But this command is expired, so set its status to OUTOFDATE, and go on") | |
1151 | + cmd_executing.set_as_outofdate() | |
1152 | + else: | |
1153 | + """ | |
1154 | + self.print(f"Thus, I won't execute any new command until this command execution is finished") | |
1155 | + # TODO: kill si superieur a MAX_EXEC_TIME | |
1156 | + return None | |
1157 | + | |
1158 | + ''' | |
1159 | + # 4) Tag all expired commands | |
1160 | + for cmd in commands: | |
1161 | + if cmd.is_expired(): cmd.set_as_outofdate() | |
1162 | + # break at 1st "valid" command (not expired) | |
1163 | + else: break | |
1164 | + | |
1165 | + # 5) If no more commands to process, return None | |
1166 | + if cmd.is_expired(): return None | |
1167 | + ''' | |
1168 | + | |
1169 | + # 6) Current cmd must now be a valid (not expired) and PENDING one, | |
1170 | + # so return it for execution | |
1171 | + #self.printd(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}") | |
1172 | + #self.printd(f"Starting processing of this command") | |
1173 | + return cmd | |
1174 | + | |
1175 | + | |
1176 | + | |
1177 | + #def _exec_agent_general_cmd(self, cmd:Command): | |
1178 | + def _exec_agent_cmd(self, cmd:AgentCmd): | |
1179 | + | |
1180 | + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...") | |
1181 | + self.print(f"Starting execution of an AGENT LEVEL cmd...") | |
1182 | + | |
1183 | + # Update read time to say that the command has been READ | |
1184 | + cmd.set_read_time() | |
1185 | + cmd.set_as_running() | |
1186 | + | |
1187 | + # SPECIFIC command (only related to me, not to any agent) | |
1188 | + if self._is_agent_specific_cmd(cmd): | |
1189 | + self.print("(Agent level SPECIFIC cmd)") | |
1190 | + # Execute method self."cmd.name"() | |
1191 | + # This can raise an exception (caught by this method caller) | |
1192 | + self.exec_cmd_from_its_name(cmd) | |
1193 | + ''' | |
1194 | + try: | |
1195 | + except AttributeError as e: | |
1196 | + self.printd(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e) | |
1197 | + self.printd("Thus => I ignore this command...") | |
1198 | + cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND") | |
1199 | + cmd.set_as_pending() | |
1200 | + cmd.set_as_skipped() | |
1201 | + return | |
1202 | + ''' | |
1203 | + cmd.set_result("Agent level SPECIFIC cmd done") | |
1204 | + cmd.set_as_processed() | |
1205 | + self.print("...Agent level SPECIFIC cmd has been executed") | |
1206 | + return | |
1207 | + | |
1208 | + # GENERAL command (related to any agent) | |
1209 | + self.print("(Agent level GENERAL CMD)") | |
1210 | + _,cmd_name,cmd_args = cmd.get_full_name_parts() | |
1211 | + #cmd_name, cmd_args = cmd.tokenize() | |
1212 | + #if cmd.name == "set_state:active": | |
1213 | + #elif cmd.name == "set_state:idle": | |
1214 | + if cmd_name == "set_state": | |
1215 | + if not cmd_args: raise ValueError() | |
1216 | + state = cmd_args[0] | |
1217 | + if state == "active": self._set_active() | |
1218 | + if state == "idle": self._set_idle() | |
1219 | + cmd.set_result("I am now " + state) | |
1220 | + time.sleep(1) | |
1221 | + elif cmd_name in ("do_flush_commands"): | |
1222 | + self.printd("flush_commands received: Delete all pending commands") | |
1223 | + AgentCmd.delete_pending_commands_for_agent(self.name) | |
1224 | + cmd.set_result('DONE') | |
1225 | + elif cmd_name in ("do_abort", "do_exit", "do_restart_init"): | |
1226 | + #self.printd("Current pending commands are:") | |
1227 | + #Command.show_commands(self._pending_commands) | |
1228 | + self.print("Aborting current executing command if exists:") | |
1229 | + self._kill_running_device_cmd_if_exists(cmd.sender) | |
1230 | + if cmd_name == "do_restart_init": | |
1231 | + self.print("restart_init received: Restarting from init()") | |
1232 | + self._DO_RESTART=True | |
1233 | + elif cmd.name == "do_exit": | |
1234 | + self._DO_EXIT=True | |
1235 | + cmd.set_result('SHOULD BE DONE NOW') | |
1236 | + else: | |
1237 | + ''' | |
1238 | + name = cmd.name | |
1239 | + args = None | |
1240 | + if " " in name: name,args = name.split() | |
1241 | + if name == "do_eval": | |
1242 | + if args==None: raise(ValueError) | |
1243 | + cmd.set_result(eval(args)) | |
1244 | + ''' | |
1245 | + if cmd_name == "do_eval": | |
1246 | + if not cmd_args: raise ValueError() | |
1247 | + cmd.set_result(eval(cmd_args)) | |
1248 | + | |
1249 | + cmd.set_as_processed() | |
1250 | + self.print("...Agent level GENERAL cmd has been executed") | |
1251 | + | |
1252 | + # If cmd is "do_exit", kill myself (without any question, this is an order soldier !) | |
1253 | + # This "do_exit" should normally kill any current thread (to be checked...) | |
1254 | + if cmd.name == "do_exit": | |
1255 | + self.print("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :") | |
1256 | + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name) | |
1257 | + AgentCmd.show_commands(commands, True) | |
1258 | + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results() | |
1259 | + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: | |
1260 | + self._TEST_test_results() | |
1261 | + #self._DO_EXIT=True | |
1262 | + #exit(0) | |
1263 | + | |
1264 | + | |
1265 | + | |
1266 | + | |
1267 | + ''' | |
1268 | + def do_log(self): | |
1269 | + #"" | |
1270 | + log à 2 endroits ou 1 seul | |
1271 | + - in file | |
1272 | + - in db | |
1273 | + #"" | |
1274 | + self.printd("Logging data...") | |
1275 | + ''' | |
1276 | + | |
1277 | + def exec_cmd_from_its_name(self, cmd:AgentCmd): | |
1278 | + func = cmd.name | |
1279 | + if cmd.args: | |
1280 | + return getattr(self, func)(*cmd.args) | |
1281 | + else: | |
1282 | + return getattr(self, func)() | |
1283 | + | |
1284 | + def is_agent_level_cmd(self, cmd:AgentCmd): | |
1285 | + return cmd.is_agent_general_cmd() or self._is_agent_specific_cmd(cmd) | |
1286 | + | |
1287 | + ''' | |
1288 | + def _exec_agent_cmd(self, cmd:Command): | |
1289 | + # AGENT "GENERAL LEVEL" command | |
1290 | + # => I process it directly without asking my DC | |
1291 | + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel) | |
1292 | + if cmd.is_agent_level_general_cmd(): | |
1293 | + self.printd("********** -- AGENT LEVEL GENERAL CMD *********") | |
1294 | + self._exec_agent_general_cmd(cmd) | |
1295 | + | |
1296 | + # AGENT "SPECIFIC LEVEL" command | |
1297 | + # => I process it directly without asking my DC | |
1298 | + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel) | |
1299 | + #elif self._is_agent_level_specific_cmd(cmd): | |
1300 | + else: | |
1301 | + self.printd("********** -- AGENT LEVEL SPECIFIC CMD *********") | |
1302 | + self._exec_agent_specific_cmd(cmd) | |
1303 | + ''' | |
1304 | + | |
1305 | + def _is_agent_specific_cmd(self, cmd:AgentCmd): | |
1306 | + return cmd.name in self.AGENT_SPECIFIC_COMMANDS | |
1307 | + | |
1308 | + ''' | |
1309 | + def _exec_agent_specific_cmd(self, cmd:Command): | |
1310 | + # Execute method self."cmd.name"() | |
1311 | + self.exec_cmd_from_its_name(cmd) | |
1312 | + ''' | |
1313 | + | |
1314 | + def is_in_test_mode(self): | |
1315 | + return self.TEST_MODE | |
1316 | + | |
1317 | + | |
1318 | + """ | |
1319 | + ================================================================================================ | |
1320 | + DEVICE SPECIFIC FUNCTIONS (abstract for Agent, overriden and implemented by AgentDevice) | |
1321 | + ================================================================================================ | |
1322 | + """ | |
1323 | + | |
1324 | + # To be overriden by subclass (AgentDevice) | |
1325 | + # @abstract | |
1326 | + def is_device_level_cmd(self, cmd): | |
1327 | + return False | |
1328 | + | |
1329 | + # to be overriden by subclass (AgentDevice) | |
1330 | + # @abstract | |
1331 | + def exec_device_cmd_if_possible(self, cmd:AgentCmd): | |
1332 | + pass | |
1333 | + | |
1334 | + # TO BE OVERRIDEN by subclass (AgentDevice) | |
1335 | + # @abstract | |
1336 | + def exec_device_cmd(self, cmd:AgentCmd): | |
1337 | + #self.exec_cmd_from_its_name(cmd) | |
1338 | + pass | |
1339 | + | |
1340 | + | |
1341 | + | |
1342 | + | |
1343 | + | |
1344 | + """ | |
1345 | + ================================================================= | |
1346 | + TEST DEDICATED FUNCTIONS | |
1347 | + ================================================================= | |
1348 | + """ | |
1349 | + | |
1350 | + def _set_debug_mode(self, mode:bool): | |
1351 | + self.DEBUG_MODE=mode | |
1352 | + def _set_with_simulator(self, mode:bool): | |
1353 | + self.WITH_SIMULATOR=mode | |
1354 | + def _set_test_mode(self, mode:bool): | |
1355 | + self.TEST_MODE=mode | |
1356 | + | |
1357 | + def _TEST_get_next_command_to_send(self)->AgentCmd: | |
1358 | + cmd_full_name = next(self.TEST_COMMANDS, None) | |
1359 | + #return cmd_name | |
1360 | + if cmd_full_name is None: return None | |
1361 | + if ' ' not in cmd_full_name: raise Exception('Command is malformed:', cmd_full_name) | |
1362 | + agent_recipient,cmd_name = cmd_full_name.split(' ', 1) | |
1363 | + ##recipient_agent = self.name if self.TEST_COMMANDS_DEST=="myself" else self.TEST_COMMANDS_DEST | |
1364 | + #return Command(sender=self.name, recipient=recipient_agent, name=cmd_name) | |
1365 | + cmd = self.create_cmd_for(agent_recipient, cmd_name) | |
1366 | + # If no cmd created (because of error, bad AgentDevice name), call again this method for next cmd | |
1367 | + if cmd is None: return self._TEST_get_next_command_to_send() | |
1368 | + return cmd | |
1369 | + | |
1370 | + """ | |
1371 | + def simulator_send_next_command(self): | |
1372 | + #self._current_test_cmd = "set_state:idle" if self._current_test_cmd=="set_state:active" else "set_state:active" | |
1373 | + #if self._nb_test_cmds == 4: self._current_test_cmd = "do_exit" | |
1374 | + cmd_name = next(self.TEST_COMMANDS, None) | |
1375 | + #self.printd("next cmd is ", cmd_name) | |
1376 | + if cmd_name is None: return | |
1377 | + #Command.objects.create(sender=self.name, recipient=self.name, name=cmd_name) | |
1378 | + recipient_agent = self.name if self.TEST_COMMANDS_DEST=="myself" else self.TEST_COMMANDS_DEST | |
1379 | + Command.objects.create(sender=self.name, recipient=recipient_agent, name=cmd_name) | |
1380 | + #time.sleep(1) | |
1381 | + #self._TEST_current_cmd_idx += 1 | |
1382 | + #self._nb_test_cmds += 1 | |
1383 | + """ | |
1384 | + | |
1385 | + def _test_routine_process(self): | |
1386 | + """ | |
1387 | + TEST MODE ONLY | |
1388 | + """ | |
1389 | + | |
1390 | + self.print("(TEST mode) Trying to send a new command if possible...") | |
1391 | + # There is a current command being processed | |
1392 | + # => check if next command is "do_abort" | |
1393 | + # => if so, instantly send a "do_abort" to abort previous command | |
1394 | + if self._cmdts is not None: | |
1395 | + self.print(f"Waiting for end execution of cmd '{self._cmdts.name}' (sent to {self._cmdts.recipient}) ...") | |
1396 | + # Update cmdts fields from DB | |
1397 | + self._cmdts.refresh_from_db() | |
1398 | + if self._cmdts.is_pending() or self._cmdts.is_running(): | |
1399 | + if self._next_cmdts is None: | |
1400 | + # If next command is "do_abort" then abort becomes the new current command (to be sent) | |
1401 | + self._next_cmdts = self._TEST_get_next_command_to_send() | |
1402 | + if self._next_cmdts and self._next_cmdts.name == "do_abort": | |
1403 | + # Wait a little to give a chance to agentB to start execution of current command, | |
1404 | + # so that we can abort it then (otherwise it won't be aborted!!) | |
1405 | + time.sleep(4) | |
1406 | + self._cmdts = self._next_cmdts | |
1407 | + self._next_cmdts = None | |
1408 | + self.print("***") | |
1409 | + #self.print(f"*** SEND ", self._cmdts) | |
1410 | + self.print(f"***", self._cmdts) | |
1411 | + self.print("***") | |
1412 | + self._cmdts.send() | |
1413 | + | |
1414 | + # Current cmd is no more running | |
1415 | + else: | |
1416 | + # Execution was not completed | |
1417 | + #if self._cmdts.is_expired() or self._cmdts.is_skipped() or self._cmdts.is_killed(): | |
1418 | + if self._cmdts.is_skipped() or self._cmdts.is_killed(): | |
1419 | + self.print("Command was not completed") | |
1420 | + # 2 possible scenarios: | |
1421 | + # - (1) Send the SAME command again | |
1422 | + ''' | |
1423 | + self.printd("Command was not completed, so I send it again") | |
1424 | + # The command was not completed, so, make a copy of it and send it again | |
1425 | + # For this, it is enough to set primary key to None, | |
1426 | + # then the send() command below will save a NEW command | |
1427 | + #self._cmdts = copy.copy(self._cmdts) | |
1428 | + self._cmdts.id = None | |
1429 | + SEND_A_NEW_COMMAND = True | |
1430 | + ''' | |
1431 | + # - (2) Send next command | |
1432 | + #self._cmdts = None | |
1433 | + # Execution was not complete => get result | |
1434 | + elif self._cmdts.is_executed(): | |
1435 | + cmdts_res = self._cmdts.get_result() | |
1436 | + self.print(f"Cmd executed. Result is '{cmdts_res}'") | |
1437 | + #cmdts_is_processed = True | |
1438 | + ''' Optimisation possible pour gagner une iteration: | |
1439 | + self._cmdts = self.simulator_get_next_command_to_send() | |
1440 | + # No more command to send (from simulator) => return | |
1441 | + if self._cmdts is None: return | |
1442 | + SEND_A_NEW_COMMAND = True | |
1443 | + ''' | |
1444 | + # Set cmdts to None so that a new command will be sent at next iteration | |
1445 | + self._cmdts = None | |
1446 | + | |
1447 | + # No currently running command => get a new command and SEND it | |
1448 | + if self._cmdts is None: | |
1449 | + if self._next_cmdts is not None: | |
1450 | + self._cmdts = self._next_cmdts | |
1451 | + self._next_cmdts = None | |
1452 | + else: | |
1453 | + self._cmdts = self._TEST_get_next_command_to_send() | |
1454 | + # No more command to send (from simulator) => return and EXIT | |
1455 | + if self._cmdts is None: | |
1456 | + self._DO_MAIN_LOOP = False | |
1457 | + return | |
1458 | + # Send cmd (= set as pending and save) | |
1459 | + self.print("***") | |
1460 | + #self.printd(f"*** SEND ", self._cmdts) | |
1461 | + self.print(f"*** NEW COMMAND TO SEND is:", self._cmdts) | |
1462 | + self.print("***") | |
1463 | + #self._cmdts.set_as_pending() | |
1464 | + # SEND | |
1465 | + self._cmdts.send() | |
1466 | + #cmdts_is_processed = False | |
1467 | + #cmdts_res = None | |
1468 | + | |
1469 | + def _TEST_test_results(self): | |
1470 | + if self.TEST_COMMANDS_LIST == [] : return | |
1471 | + nb_commands_to_send = len(self.TEST_COMMANDS_LIST) | |
1472 | + nb_commands_sent, commands = self._TEST_test_results_start() | |
1473 | + #nb_commands_to_send = len(self.TEST_COMMANDS_LIST) | |
1474 | + | |
1475 | + # General (default) test | |
1476 | + #self.printd(commands[0].name, "compared to", self.TEST_COMMANDS_LIST[0].split()[1]) | |
1477 | + assert commands[0].name == self.TEST_COMMANDS_LIST[0].split()[1] | |
1478 | + last_cmd = commands[-1] | |
1479 | + assert last_cmd.name == self.TEST_COMMANDS_LIST[-1].split()[1] | |
1480 | + assert last_cmd.name == "do_exit" | |
1481 | + assert last_cmd.is_executed() | |
1482 | + assert last_cmd.get_result() == "SHOULD BE DONE NOW" | |
1483 | + | |
1484 | + nb_asserted = 0 | |
1485 | + nb_agent_general = 0 | |
1486 | + nb_unknown = 0 | |
1487 | + nb_unimplemented = 0 | |
1488 | + for cmd in commands: | |
1489 | + assert cmd.is_executed() or cmd.is_killed() or cmd.is_skipped() | |
1490 | + nb_asserted += 1 | |
1491 | + if cmd.is_agent_general_cmd(): | |
1492 | + nb_agent_general += 1 | |
1493 | + if cmd.name == "do_unknown": | |
1494 | + assert cmd.is_skipped() | |
1495 | + #assert "UnimplementedGenericCmdException" in cmd.get_result() | |
1496 | + assert "INVALID COMMAND" in cmd.get_result() | |
1497 | + nb_unknown += 1 | |
1498 | + #if cmd.name in ["do_unimplemented", "do_unknown"]: | |
1499 | + if cmd.name == "do_unimplemented": | |
1500 | + assert cmd.is_skipped() | |
1501 | + assert "UnimplementedGenericCmdException" in cmd.get_result() | |
1502 | + nb_unimplemented += 1 | |
1503 | + assert nb_asserted == nb_commands_sent | |
1504 | + self.print(nb_commands_to_send, "cmds I had to send <==>", nb_asserted, "cmds executed (or killed), ", nb_commands_to_send-nb_commands_sent, "cmd ignored") | |
1505 | + self.print("Among executed commands:") | |
1506 | + self.print(f"- {nb_agent_general} AGENT general command(s)") | |
1507 | + self.print("-", nb_unimplemented, "unimplemented command(s) => UnimplementedGenericCmdException raised then command was skipped") | |
1508 | + self.print("-", nb_unknown, "unknown command(s) => skipped") | |
1509 | + | |
1510 | + # Now test that any "AD get_xx" following a "AD set_xx value" command has result = value | |
1511 | + for i,cmd_set in enumerate(commands): | |
1512 | + if cmd_set.name.startswith('set_'): | |
1513 | + commands_after = commands[i+1:] | |
1514 | + for cmd_get in commands_after: | |
1515 | + if cmd_get.name.startswith('get_') and cmd_get.name[4:]==cmd_set.name[4:] and cmd_get.device_type==cmd_set.device_type: | |
1516 | + self.print("cmd_get.result == cmd_set.args ?", cmd_get.result, cmd_set.args) | |
1517 | + assert cmd_get.get_result() == ','.join(cmd_set.args), "A get_xx command did not gave the expected result as set by a previous set_xx command" | |
1518 | + break | |
1519 | + | |
1520 | + # Specific (detailed) test (to be overriden by subclass) | |
1521 | + nb_asserted2 = self.TEST_test_results_main(commands) | |
1522 | + self._TEST_test_results_end(nb_asserted) | |
1523 | + | |
1524 | + def _TEST_test_results_start(self): | |
1525 | + self.print() | |
1526 | + self.print("--- Testing if the commands I SENT had the awaited result") | |
1527 | + self.print("Here are the last commands I sent:") | |
1528 | + #commands = list(Command.get_last_N_commands_for_agent(self.name, 16)) | |
1529 | + #commands = Command.get_last_N_commands_sent_to_agent(self.name, 16) | |
1530 | + nb_commands = len(self.TEST_COMMANDS_LIST) | |
1531 | + if "ad_unknown get_dec" in self.TEST_COMMANDS_LIST: nb_commands -= 1 | |
1532 | + commands = AgentCmd.get_last_N_commands_sent_by_agent(self.name, nb_commands) | |
1533 | + AgentCmd.show_commands(commands) | |
1534 | + return nb_commands, commands | |
1535 | + """ OLD SCENARIO | |
1536 | + nb_asserted = 0 | |
1537 | + for cmd in commands: | |
1538 | + if cmd.name == "specific0": | |
1539 | + assert cmd.is_skipped() | |
1540 | + nb_asserted+=1 | |
1541 | + if cmd.name == "specific1": | |
1542 | + assert cmd.result == "in step #5/5" | |
1543 | + assert cmd.is_executed() | |
1544 | + nb_asserted+=1 | |
1545 | + if cmd.name in ("specific2","specific3"): | |
1546 | + assert cmd.is_killed() | |
1547 | + nb_asserted+=1 | |
1548 | + if cmd.name in ("specific4", "specific5", "specific6", "specific7", "specific8"): | |
1549 | + assert cmd.is_pending() | |
1550 | + nb_asserted+=1 | |
1551 | + # 2 cmds abort | |
1552 | + if cmd.name in ("do_abort"): | |
1553 | + assert cmd.is_executed() | |
1554 | + nb_asserted+=1 | |
1555 | + if cmd.name in ("do_exit"): | |
1556 | + assert cmd.is_executed() | |
1557 | + nb_asserted+=1 | |
1558 | + assert nb_asserted == 12 | |
1559 | + self.printd("--- Finished testing => result is ok") | |
1560 | + """ | |
1561 | + | |
1562 | + # To be overriden by subclass | |
1563 | + def TEST_test_results_main(self, commands): | |
1564 | + return 0 | |
1565 | + ''' | |
1566 | + nb_asserted = 0 | |
1567 | + self.printd("from simulator_test_results_main", commands) | |
1568 | + for cmd in commands: | |
1569 | + assert cmd.is_executed() | |
1570 | + nb_asserted+=1 | |
1571 | + return nb_asserted | |
1572 | + ''' | |
1573 | + | |
1574 | + def _TEST_test_results_end(self, nb_asserted): | |
1575 | + #nb_commands_to_send = len(self.TEST_COMMANDS_LIST) | |
1576 | + #self.printd(nb_asserted, "vs", nb_commands_to_send) | |
1577 | + #assert nb_asserted == nb_commands_to_send | |
1578 | + #self.printd(f"************** Finished testing => result is ok ({nb_asserted} assertions) **************") | |
1579 | + printFullTerm(Colors.GREEN, f"************** Finished testing => result is ok ({nb_asserted} assertions) **************") | |
1580 | + | |
1581 | + | |
1582 | + | |
1583 | + | |
1584 | + | |
1585 | + | |
1586 | +""" | |
1587 | +================================================================= | |
1588 | + MAIN | |
1589 | +================================================================= | |
1590 | +""" | |
1591 | + | |
1592 | +def extract_parameters(): | |
1593 | + """ Usage: Agent.py [-t] [configfile] """ | |
1594 | + # arg 1 : -t | |
1595 | + # arg 2 : configfile | |
1596 | + DEBUG_MODE = False | |
1597 | + TEST_MODE = False | |
1598 | + WITH_SIM = False | |
1599 | + VERBOSE_MODE = False | |
1600 | + configfile = None | |
1601 | + printd("args:", sys.argv) | |
1602 | + for arg in sys.argv[1:] : | |
1603 | + if arg == "-t": TEST_MODE = True | |
1604 | + elif arg == "-s": WITH_SIM = True | |
1605 | + elif arg == "-d": DEBUG_MODE = True | |
1606 | + elif arg == "-v": VERBOSE_MODE = True | |
1607 | + else: configfile = arg | |
1608 | + ''' | |
1609 | + if len(sys.argv) > 1: | |
1610 | + if sys.argv[1] == "-t": | |
1611 | + TEST_MODE = True | |
1612 | + if len(sys.argv) == 3: | |
1613 | + configfile = sys.argv[2] | |
1614 | + else: | |
1615 | + configfile = sys.argv[1] | |
1616 | + ''' | |
1617 | + return DEBUG_MODE, WITH_SIM, TEST_MODE, VERBOSE_MODE, configfile | |
1618 | + | |
1619 | +#def build_agent(Agent_type:Agent, name="GenericAgent", RUN_IN_THREAD=True): | |
1620 | +def build_agent(Agent_type:Agent2, RUN_IN_THREAD=True): | |
1621 | + DEBUG_MODE, WITH_SIM, TEST_MODE, VERBOSE_MODE, configfile = extract_parameters() | |
1622 | + #agent = Agent("GenericAgent", configfile, RUN_IN_THREAD=True) | |
1623 | + #agent = Agent_type(configfile, RUN_IN_THREAD, DEBUG_MODE=DEBUG_MODE) | |
1624 | + agent = Agent_type(configfile, RUN_IN_THREAD) | |
1625 | + #agent = Agent_type(name, configfile, RUN_IN_THREAD) | |
1626 | + agent._set_with_simulator(WITH_SIM) | |
1627 | + agent._set_test_mode(TEST_MODE) | |
1628 | + #agent._set_debug_mode(DEBUG_MODE) | |
1629 | + return agent | |
1630 | + | |
1631 | + | |
1632 | +if __name__ == "__main__": | |
1633 | + | |
1634 | + # with thread | |
1635 | + RUN_IN_THREAD=True | |
1636 | + # with process | |
1637 | + #RUN_IN_THREAD=False | |
1638 | + | |
1639 | + agent = build_agent(Agent2, RUN_IN_THREAD=RUN_IN_THREAD) | |
1640 | + obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"] | |
1641 | + path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"] | |
1642 | + unit = os.environ["unit_name"] | |
1643 | + print("TOTO") | |
1644 | + agent.printd(obs_config_file_path) | |
1645 | + agent.printd(path_to_obs_config_folder) | |
1646 | + agent.printd(unit) | |
1647 | + from src.core.pyros_django.obsconfig.configpyros import ConfigPyros | |
1648 | + oc = ConfigPyros(obs_config_file_path) | |
1649 | + | |
1650 | + print("\n") | |
1651 | + print("- Observatory:", oc.get_obs_name()) | |
1652 | + | |
1653 | + my_unit_name = oc.get_units_name()[0] | |
1654 | + my_unit = (oc.get_units()[my_unit_name]) | |
1655 | + #print("- Unit description:", my_unit) | |
1656 | + | |
1657 | + print("\n") | |
1658 | + print("- Computers:", oc.get_computers()) | |
1659 | + | |
1660 | + print("\n") | |
1661 | + print("- Active Computers:", oc.get_active_computers()) | |
1662 | + | |
1663 | + print("\n") | |
1664 | + print("- Active Devices:", oc.get_active_devices()) | |
1665 | + | |
1666 | + print("\n") | |
1667 | + print("- Unit:", my_unit_name) | |
1668 | + print(oc.get_unit_by_name(my_unit_name)) | |
1669 | + | |
1670 | + print("\n") | |
1671 | + print("- Unit topology:", oc.get_topology(my_unit_name)) | |
1672 | + | |
1673 | + print("\n") | |
1674 | + print("- Unit active Agents:", oc.get_active_agents(my_unit_name)) | |
1675 | + print(oc.get_agents(my_unit_name)) | |
1676 | + | |
1677 | + print("\n") | |
1678 | + print("- Unit Agents per computer:", oc.get_agents_per_computer(my_unit_name)) | |
1679 | + | |
1680 | + print("\n") | |
1681 | + print("- Unit Agents per device:", oc.get_agents_per_device(my_unit_name)) | |
1682 | + | |
1683 | + print("\n") | |
1684 | + print("- Unit Channel groups:", oc.get_channel_groups(my_unit_name)) | |
1685 | + | |
1686 | + print("\n") | |
1687 | + print("- Unit Channels:", oc.get_channels(my_unit_name)) | |
1688 | + | |
1689 | + print("\n") | |
1690 | + print("- Unit/Channel info:", oc.get_channel_information(my_unit_name, 'OpticalChannel_up')) | |
1691 | + | |
1692 | + print("\n") | |
1693 | + print("- Unit Components agents:", oc.get_components_agents(my_unit_name)) | |
1694 | + | |
1695 | + print("\n") | |
1696 | + print("- Unit database:", oc.get_database_for_unit(my_unit_name)) | |
1697 | + | |
1698 | + print("\n") | |
1699 | + print("- Devices names:", oc.get_devices_names()) | |
1700 | + print("\n") | |
1701 | + print("- Devices names & files:", oc.get_devices_names_and_file()) | |
1702 | + print("\n") | |
1703 | + print("- Devices:", oc.get_devices()) | |
1704 | + | |
1705 | + print("\n") | |
1706 | + | |
1707 | + exit(0) | |
1708 | + #agent = build_agent(Agent, name="GenericAgent", RUN_IN_THREAD=RUN_IN_THREAD) | |
1709 | + ''' | |
1710 | + TEST_MODE, WITH_SIM, configfile = extract_parameters() | |
1711 | + agent = Agent("GenericAgent", configfile, RUN_IN_THREAD=True) | |
1712 | + #agent.setSimulatorMode(TEST_MODE) | |
1713 | + agent.setTestMode(TEST_MODE) | |
1714 | + agent.setWithSimulator(WITH_SIM) | |
1715 | + self.printd(agent) | |
1716 | + ''' | |
1717 | + agent.run() | ... | ... |
src/core/pyros_django/obsconfig/configpyros.py
... | ... | @@ -280,11 +280,8 @@ class ConfigPyros: |
280 | 280 | capability["attributes"] = component_attributes |
281 | 281 | return capability |
282 | 282 | |
283 | - | |
284 | - | |
285 | - def get_devices_names_and_file(self)->dict: | |
283 | + def get_devices_names_and_file(self) -> dict: | |
286 | 284 | """ |
287 | - | |
288 | 285 | Return a dictionary giving the device file name by the device name |
289 | 286 | Returns: |
290 | 287 | dict: key is device name, value is file name |
... | ... | @@ -455,7 +452,7 @@ class ConfigPyros: |
455 | 452 | exit(1) |
456 | 453 | #return None |
457 | 454 | |
458 | - def __init__(self,observatory_config_file:str,unit_name:str="") -> None: | |
455 | + def __init__(self, observatory_config_file:str, unit_name:str="") -> None: | |
459 | 456 | """ |
460 | 457 | Initiate class with the config file |
461 | 458 | set content attribute to a dictionary containing all values from the config file |
... | ... | @@ -470,8 +467,7 @@ class ConfigPyros: |
470 | 467 | else: |
471 | 468 | self.unit_name = unit_name |
472 | 469 | |
473 | - | |
474 | - def get_obs_name(self)->str: | |
470 | + def get_obs_name(self) -> str: | |
475 | 471 | """ |
476 | 472 | Return name of the observatory |
477 | 473 | |
... | ... | @@ -480,7 +476,7 @@ class ConfigPyros: |
480 | 476 | """ |
481 | 477 | return self.obs_config["OBSERVATORY"]["name"] |
482 | 478 | |
483 | - def get_channels(self,unit_name:str)->dict: | |
479 | + def get_channels(self, unit_name: str) -> dict: | |
484 | 480 | |
485 | 481 | """ |
486 | 482 | return dictionary of channels |
... | ... | @@ -498,7 +494,7 @@ class ConfigPyros: |
498 | 494 | channels[channel["name"]] = channel |
499 | 495 | return channels |
500 | 496 | |
501 | - def get_computers(self)->dict: | |
497 | + def get_computers(self) -> dict: | |
502 | 498 | """ |
503 | 499 | return dictionary of computers |
504 | 500 | |
... | ... | @@ -515,11 +511,11 @@ class ConfigPyros: |
515 | 511 | computer["computer_config"]= self.read_and_check_config_file(self.CONFIG_PATH+computer["file"])["COMPUTER"] |
516 | 512 | computers[computer["name"]] = computer |
517 | 513 | return computers |
518 | - | |
519 | - def get_devices(self)->dict: | |
514 | + | |
515 | + def get_devices(self) -> dict: | |
520 | 516 | """ |
521 | 517 | return dictionary of devices |
522 | - | |
518 | + | |
523 | 519 | Returns: |
524 | 520 | dict: [description] |
525 | 521 | """ |
... | ... | @@ -570,7 +566,7 @@ class ConfigPyros: |
570 | 566 | agents[agent["name"]] = agent |
571 | 567 | return agents |
572 | 568 | |
573 | - def get_channel_groups(self,unit_name:str)->dict: | |
569 | + def get_channel_groups(self, unit_name: str) -> dict: | |
574 | 570 | """ |
575 | 571 | Return dictionary of channel groups, tell the logic between groups of channels and within a group of channels |
576 | 572 | |
... | ... | @@ -588,7 +584,7 @@ class ConfigPyros: |
588 | 584 | info["groups"][group_id] = group |
589 | 585 | return info |
590 | 586 | |
591 | - def get_channel_information(self,unit_name:str,channel_name:str)->dict: | |
587 | + def get_channel_information(self, unit_name: str, channel_name: str) -> dict: | |
592 | 588 | """ |
593 | 589 | Return information of the given channel name of a unit |
594 | 590 | |
... | ... | @@ -602,7 +598,7 @@ class ConfigPyros: |
602 | 598 | channels = self.get_channels(unit_name) |
603 | 599 | return channels[channel_name] |
604 | 600 | |
605 | - def get_topology(self,unit_name:str)->dict: | |
601 | + def get_topology(self, unit_name:str)->dict: | |
606 | 602 | """ |
607 | 603 | Return dictionary of the topology of the observatory |
608 | 604 | |
... | ... | @@ -623,7 +619,7 @@ class ConfigPyros: |
623 | 619 | topology[key] = branch |
624 | 620 | return topology |
625 | 621 | |
626 | - def get_active_agents(self,unit_name:str)->list: | |
622 | + def get_active_agents(self, unit_name: str) -> list: | |
627 | 623 | """ |
628 | 624 | Return the list of active agents (i.e. agents that have an association with a device) |
629 | 625 | |
... | ... | @@ -649,7 +645,7 @@ class ConfigPyros: |
649 | 645 | result[unit["name"]] = unit |
650 | 646 | return result |
651 | 647 | |
652 | - def get_components_agents(self,unit_name:str)->dict: | |
648 | + def get_components_agents(self, unit_name: str) -> dict: | |
653 | 649 | """ |
654 | 650 | Return dictionary of component_agents of the given unit |
655 | 651 | |
... | ... | @@ -683,9 +679,9 @@ class ConfigPyros: |
683 | 679 | """ |
684 | 680 | return list(self.get_units().keys()) |
685 | 681 | |
686 | - def get_unit_by_name(self,name:str)->dict: | |
682 | + def get_unit_by_name(self, name: str) -> dict: | |
687 | 683 | """ |
688 | - Return dictionary containing definition of the unit that match the given name | |
684 | + Return dictionary containing definition of the unit that matches the given name | |
689 | 685 | |
690 | 686 | Args: |
691 | 687 | name (str): name of the unit |
... | ... | @@ -695,7 +691,7 @@ class ConfigPyros: |
695 | 691 | """ |
696 | 692 | return self.get_units()[name] |
697 | 693 | |
698 | - def get_agents_per_computer(self,unit_name:str)->dict: | |
694 | + def get_agents_per_computer(self, unit_name: str) -> dict: | |
699 | 695 | """ |
700 | 696 | Return dictionary that give for each computer, what are the associated agents to it as a list |
701 | 697 | |
... | ... | @@ -795,7 +791,7 @@ class ConfigPyros: |
795 | 791 | """ |
796 | 792 | return self.get_devices()[device_name] |
797 | 793 | |
798 | - def get_database_for_unit(self,unit_name:str)->dict: | |
794 | + def get_database_for_unit(self, unit_name: str) -> dict: | |
799 | 795 | """ |
800 | 796 | Return dictionary of attributes of the database for an unit |
801 | 797 | |
... | ... | @@ -807,7 +803,7 @@ class ConfigPyros: |
807 | 803 | """ |
808 | 804 | return self.get_unit_by_name(unit_name)["DATABASE"] |
809 | 805 | |
810 | - def get_device_for_agent(self,unit_name:str,agent_name:str)->str: | |
806 | + def get_device_for_agent(self, unit_name: str, agent_name: str) -> str: | |
811 | 807 | """ |
812 | 808 | Return device name associated to the agent |
813 | 809 | |
... | ... | @@ -822,7 +818,8 @@ class ConfigPyros: |
822 | 818 | for device in agents_per_device: |
823 | 819 | if agent_name in agents_per_device[device]: |
824 | 820 | return self.get_device_information(device) |
825 | - def get_unit_of_computer(self,computer_name:str)->str: | |
821 | + | |
822 | + def get_unit_of_computer(self, computer_name: str) -> str: | |
826 | 823 | """ |
827 | 824 | Return the name of the unit where the computer is used |
828 | 825 | |
... | ... | @@ -836,7 +833,7 @@ class ConfigPyros: |
836 | 833 | if(computer_name in self.get_agents_per_computer(unit_name)): |
837 | 834 | return unit_name |
838 | 835 | |
839 | - def get_unit_of_device(self,device_name:str)->str: | |
836 | + def get_unit_of_device(self, device_name:str)->str: | |
840 | 837 | """ |
841 | 838 | Return the name of the unit where the device is used |
842 | 839 | |
... | ... | @@ -864,7 +861,7 @@ class ConfigPyros: |
864 | 861 | return self.get_devices()[device_name]["device_config"].get("power") |
865 | 862 | |
866 | 863 | |
867 | - def get_device_capabilities(self,device_name:str)->list: | |
864 | + def get_device_capabilities(self, device_name:str)->list: | |
868 | 865 | """ |
869 | 866 | Return dictionary that contains informations about capabilities if this information is present in the device config file |
870 | 867 | |
... | ... | @@ -892,7 +889,7 @@ class ConfigPyros: |
892 | 889 | """ |
893 | 890 | return self.get_devices()[device_name]["device_config"].get("connector") |
894 | 891 | |
895 | - def get_computer_power(self,computer_name:str)->dict: | |
892 | + def get_computer_power(self, computer_name: str) -> dict: | |
896 | 893 | """ |
897 | 894 | Return dictionary that contains informations about power if this information is present in the device config file |
898 | 895 | ... | ... |
src/core/pyros_django/pyros/settings.py
... | ... | @@ -404,5 +404,6 @@ python_version = subprocess.run( "python --version | cut -d ' ' -f 2 | cut -d '. |
404 | 404 | python_version = python_version.stdout |
405 | 405 | today = "2021-11-09" |
406 | 406 | django_version_major,django_version_minor = django.VERSION[:2][0],django.VERSION[:2][1] |
407 | -pyros_version = "0.2.12.0" | |
408 | -VERSION_NUMBER = f"{pyros_version}_{django_version_major}.{django_version_minor}_{python_version}_{today}" | |
409 | 407 | \ No newline at end of file |
408 | +pyros_version = "0.3.0.0" | |
409 | +#pyros_version = "0.2.12.0" | |
410 | +VERSION_NUMBER = f"{pyros_version}_{django_version_major}.{django_version_minor}_{python_version}_{today}" | ... | ... |