Commit 84131ac0974c507b5bba46dfa0891e1a2722a249
Exists in
dev
Merge branch 'dev' of https://gitlab.irap.omp.eu/epallier/pyros into dev
Showing
8 changed files
with
1954 additions
and
70 deletions
Show diff stats
.gitignore
PYROS deleted
@@ -1,39 +0,0 @@ | @@ -1,39 +0,0 @@ | ||
1 | -#!/usr/bin/env bash | ||
2 | - | ||
3 | -# 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 | ||
10 | - | ||
11 | -# test if docker is installed | ||
12 | -if [ -x "$(command -v docker)" ]; | ||
13 | -then | ||
14 | - docker=true | ||
15 | -else | ||
16 | - docker=false | ||
17 | -fi | ||
18 | - | ||
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 | -fi | ||
39 | - |
@@ -0,0 +1,198 @@ | @@ -0,0 +1,198 @@ | ||
1 | +#!/usr/bin/env python3 | ||
2 | + | ||
3 | +''' | ||
4 | +Shell scripting with python : | ||
5 | +- https://www.freecodecamp.org/news/python-for-system-administration-tutorial | ||
6 | +- https://github.com/ninjaaron/replacing-bash-scripting-with-python#if-the-shell-is-so-great-what-s-the-problem | ||
7 | +- https://linuxize.com/post/python-get-change-current-working-directory | ||
8 | +''' | ||
9 | + | ||
10 | +DEBUG = False | ||
11 | +#DEBUG = True | ||
12 | +#print(DEBUG) | ||
13 | + | ||
14 | + | ||
15 | +import sys | ||
16 | +import os | ||
17 | +from shutil import which | ||
18 | +import subprocess | ||
19 | + | ||
20 | + | ||
21 | + | ||
22 | +def run(command: str) : | ||
23 | + #print(command.split(' ')) | ||
24 | + return subprocess.run(command.split(' ')) | ||
25 | + #result = subprocess.run(command.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
26 | + | ||
27 | + | ||
28 | +# Guess Context (3 possibilities) : | ||
29 | +# - NO DOCKER, | ||
30 | +# or | ||
31 | +# - DOCKER OUT of container, | ||
32 | +# or | ||
33 | +# - or DOCKER IN container | ||
34 | + | ||
35 | +WITH_DOCKER_IS_SET = False if os.getenv('WITH_DOCKER') is None else True | ||
36 | +#print(WITH_DOCKER_IS_SET) | ||
37 | + | ||
38 | + | ||
39 | +#APP_FOLDER=False | ||
40 | +#[ -d ../app/ ] && APP_FOLDER=true | ||
41 | +APP_FOLDER = os.path.exists('../app/') | ||
42 | + | ||
43 | +VENV = os.path.exists('./venv/') | ||
44 | + | ||
45 | +# test if docker is installed | ||
46 | +#[ -x "$(command -v docker)" ] && DOCKER_CMD=true | ||
47 | +DOCKER_CMD = which('docker') is not None | ||
48 | + | ||
49 | + | ||
50 | +# Pas utile vu qu'on va redémarrer systématiquement le container | ||
51 | +# test if container is running | ||
52 | +##DOCKER_CONTAINER_STARTED=false | ||
53 | +##$DOCKER_CMD && [ $(docker ps | grep 'pyros' | wc -l) -eq 2 ] && DOCKER_CONTAINER_STARTED=true | ||
54 | + | ||
55 | +# | ||
56 | +# SYNTHESIS | ||
57 | +# | ||
58 | + | ||
59 | +#DOCKER=false | ||
60 | +#[[ $VENV == false && $DOCKER_CMD == true ]] && DOCKER=true | ||
61 | +DOCKER = DOCKER_CMD and not VENV | ||
62 | + | ||
63 | +DOCKER_OUT_CONTAINER = DOCKER and not WITH_DOCKER_IS_SET | ||
64 | +#[[ $DOCKER == false && $WITH_DOCKER_IS_SET == true ]] && DOCKER_IN_CONTAINER=true | ||
65 | + | ||
66 | +if DEBUG : | ||
67 | + print(APP_FOLDER) | ||
68 | + print(VENV) | ||
69 | + print(DOCKER_CMD) | ||
70 | + #print(container) | ||
71 | + # Synthesis | ||
72 | + print(DOCKER) | ||
73 | + print(DOCKER_OUT_CONTAINER) | ||
74 | + | ||
75 | + print(sys.argv) | ||
76 | + exit(0) | ||
77 | + | ||
78 | +# no container ? => start container first | ||
79 | +#[ $container == false ] && cd docker/ && docker-compose up -d | ||
80 | + | ||
81 | +args = sys.argv[1:] | ||
82 | +args = ' '.join(args) | ||
83 | +#print(args) | ||
84 | + | ||
85 | +#PYROS_CMD = "python3 pyros.py --help" | ||
86 | +#PYROS_CMD = "python3 pyros.py $*" | ||
87 | +PYROS_CMD = "python3 pyros.py " + args | ||
88 | +PYROS_CMD = PYROS_CMD.rstrip() | ||
89 | + | ||
90 | +# DOCKER_OUT_CONTAINER true ? => docker exec | ||
91 | +#docker exec -it pyros python3 pyros.py $* | ||
92 | +#[ $DOCKER_OUT_CONTAINER == true ] && cd docker/ && docker-compose up -d && PREFIX='docker exec -it pyros' | ||
93 | +if DOCKER_OUT_CONTAINER : | ||
94 | + #cd docker/ | ||
95 | + os.chdir('docker') | ||
96 | + #docker-compose up -d | ||
97 | + run('docker-compose up -d') | ||
98 | + PYROS_CMD = 'docker exec -it pyros ' + PYROS_CMD | ||
99 | + | ||
100 | +#PYROS_CMD | ||
101 | +print("\n Executing command :", PYROS_CMD, "\n") | ||
102 | +res = run(PYROS_CMD) | ||
103 | + | ||
104 | +''' | ||
105 | +#Print the stdout and stderr | ||
106 | +print() | ||
107 | +print(result.args) | ||
108 | +print() | ||
109 | +print(result.stdout) | ||
110 | +print() | ||
111 | +print(result.stderr) | ||
112 | +''' | ||
113 | + | ||
114 | + | ||
115 | + | ||
116 | + | ||
117 | + | ||
118 | + | ||
119 | + | ||
120 | +''' | ||
121 | +########################## | ||
122 | +# Script BASH equivalent # | ||
123 | +########################## | ||
124 | + | ||
125 | +#!/usr/bin/env bash | ||
126 | + | ||
127 | +DEBUG=true | ||
128 | +DEBUG=false | ||
129 | + | ||
130 | +# test if user passed a command as parameter | ||
131 | +#if test $# -lt 1 ; then | ||
132 | + #echo "Missing command, use one of the commands below" | ||
133 | + #python3 pyros.py --help | ||
134 | + #exit | ||
135 | +#fi | ||
136 | + | ||
137 | +# Guess Context : | ||
138 | +# - NO DOCKER, | ||
139 | +# or | ||
140 | +# - DOCKER OUT of container, | ||
141 | +# or | ||
142 | +# - or DOCKER IN container | ||
143 | + | ||
144 | +WITH_DOCKER_IS_SET=true | ||
145 | +[ -z $WITH_DOCKER ] && WITH_DOCKER_IS_SET=false | ||
146 | + | ||
147 | +APP_FOLDER=false | ||
148 | +[ -d ../app/ ] && APP_FOLDER=true | ||
149 | + | ||
150 | +VENV=false | ||
151 | +[ -d ./venv/ ] && VENV=true | ||
152 | + | ||
153 | +# test if docker is installed | ||
154 | +DOCKER_CMD=false | ||
155 | +[ -x "$(command -v docker)" ] && DOCKER_CMD=true | ||
156 | + | ||
157 | + | ||
158 | +# test if container is running | ||
159 | +DOCKER_CONTAINER_STARTED=false | ||
160 | +$DOCKER_CMD && [ $(docker ps | grep 'pyros' | wc -l) -eq 2 ] && DOCKER_CONTAINER_STARTED=true | ||
161 | + | ||
162 | +# | ||
163 | +# SYNTHESIS | ||
164 | +# | ||
165 | + | ||
166 | +DOCKER=false | ||
167 | +[[ $VENV == false && $DOCKER_CMD == true ]] && DOCKER=true | ||
168 | + | ||
169 | +DOCKER_OUT_CONTAINER=false | ||
170 | +[[ $DOCKER == true && $WITH_DOCKER_IS_SET == false ]] && DOCKER_OUT_CONTAINER=true | ||
171 | +#[[ $DOCKER == false && $WITH_DOCKER_IS_SET == true ]] && DOCKER_IN_CONTAINER=true | ||
172 | + | ||
173 | +if $DEBUG ; then | ||
174 | + echo $APP_FOLDER | ||
175 | + echo $VENV | ||
176 | + echo $DOCKER_CMD | ||
177 | + echo $container | ||
178 | + | ||
179 | + # Synthesis | ||
180 | + echo $DOCKER | ||
181 | + echo $DOCKER_OUT_CONTAINER | ||
182 | + | ||
183 | + exit | ||
184 | +fi | ||
185 | + | ||
186 | + | ||
187 | +# no container ? => start container first | ||
188 | +#[ $container == false ] && cd docker/ && docker-compose up -d | ||
189 | + | ||
190 | +# DOCKER_OUT_CONTAINER true ? docker exec | ||
191 | +PREFIX='' | ||
192 | +#docker exec -it pyros python3 pyros.py $* | ||
193 | +[ $DOCKER_OUT_CONTAINER == true ] && cd docker/ && docker-compose up -d && PREFIX='docker exec -it pyros' | ||
194 | + | ||
195 | +echo $PREFIX | ||
196 | +$PREFIX python3 pyros.py $* | ||
197 | + | ||
198 | +''' |
pyros.py
@@ -34,9 +34,11 @@ _previous_dir = None | @@ -34,9 +34,11 @@ _previous_dir = None | ||
34 | AGENTS = { | 34 | AGENTS = { |
35 | #"agentX" : "agent", | 35 | #"agentX" : "agent", |
36 | "agent" : "Agent", | 36 | "agent" : "Agent", |
37 | + "agent2" : "Agent", | ||
37 | "agentX" : "AgentX", | 38 | "agentX" : "AgentX", |
38 | "agentA" : "AgentA", | 39 | "agentA" : "AgentA", |
39 | "agentB" : "AgentB", | 40 | "agentB" : "AgentB", |
41 | + "agentC" : "AgentC", | ||
40 | "agentM" : "AgentM", | 42 | "agentM" : "AgentM", |
41 | "agentSP" : "AgentSP", | 43 | "agentSP" : "AgentSP", |
42 | #"agentDevice" : "AgentDevice", | 44 | #"agentDevice" : "AgentDevice", |
@@ -289,7 +291,7 @@ def printFullTerm(color: Colors, string: str): | @@ -289,7 +291,7 @@ def printFullTerm(color: Colors, string: str): | ||
289 | printColor(color, "-" * (columns - value)) | 291 | printColor(color, "-" * (columns - value)) |
290 | return 0 | 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 | Set environment variables if they aren't defined in the current environment. | 296 | Set environment variables if they aren't defined in the current environment. |
295 | Get the variables from .env file if it exists or create this file using a copy of the env_sample | 297 | Get the variables from .env file if it exists or create this file using a copy of the env_sample |
@@ -633,7 +635,7 @@ def initdb(): | @@ -633,7 +635,7 @@ def initdb(): | ||
633 | #@click.option('--format', '-f', type=click.Choice(['html', 'xml', 'text']), default='html', show_default=True) | 635 | #@click.option('--format', '-f', type=click.Choice(['html', 'xml', 'text']), default='html', show_default=True) |
634 | #@click.option('--port', default=8000) | 636 | #@click.option('--port', default=8000) |
635 | #def start(agent:str, configfile:str, test, verbosity): | 637 | #def start(agent:str, configfile:str, test, verbosity): |
636 | -def start(agent:str, configfile:str,observatory:str,unit:str): | 638 | +def start(agent:str, configfile:str, observatory:str, unit:str): |
637 | printd("Running start command") | 639 | printd("Running start command") |
638 | if configfile: | 640 | if configfile: |
639 | printd("With config file", configfile) | 641 | printd("With config file", configfile) |
@@ -660,10 +662,14 @@ def start(agent:str, configfile:str,observatory:str,unit:str): | @@ -660,10 +662,14 @@ def start(agent:str, configfile:str,observatory:str,unit:str): | ||
660 | obs_config_file_path = os.path.join(path_to_obs_config_folder,obs_config_file_name) | 662 | obs_config_file_path = os.path.join(path_to_obs_config_folder,obs_config_file_name) |
661 | os.environ["PATH_TO_OBSCONF_FILE"] = obs_config_file_path | 663 | os.environ["PATH_TO_OBSCONF_FILE"] = obs_config_file_path |
662 | os.environ["PATH_TO_OBSCONF_FOLDER"] = path_to_obs_config_folder | 664 | os.environ["PATH_TO_OBSCONF_FOLDER"] = path_to_obs_config_folder |
665 | + os.environ["unit_name"] = unit if unit else '' | ||
666 | + ''' | ||
663 | if unit: | 667 | if unit: |
664 | os.environ["unit_name"] = unit | 668 | os.environ["unit_name"] = unit |
665 | else: | 669 | else: |
666 | os.environ["unit_name"] = "" | 670 | os.environ["unit_name"] = "" |
671 | + ''' | ||
672 | + | ||
667 | # add path to pyros_django folder as the config class is supposed to work within this folder | 673 | # add path to pyros_django folder as the config class is supposed to work within this folder |
668 | #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'))}')\"" | 674 | #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'))}')\"" |
669 | cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.configpyros import ConfigPyros\nConfigPyros('{obs_config_file_path}')\"" | 675 | cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.configpyros import ConfigPyros\nConfigPyros('{obs_config_file_path}')\"" |
@@ -703,7 +709,6 @@ def start(agent:str, configfile:str,observatory:str,unit:str): | @@ -703,7 +709,6 @@ def start(agent:str, configfile:str,observatory:str,unit:str): | ||
703 | printd("Launching agent", agent_name, "...") | 709 | printd("Launching agent", agent_name, "...") |
704 | #if not test_mode(): execProcess(VENV_PYTHON + " manage.py runserver") | 710 | #if not test_mode(): execProcess(VENV_PYTHON + " manage.py runserver") |
705 | #if not test_mode(): execProcessFromVenv("start_agent_" + agent_name + ".py " + configfile) | 711 | #if not test_mode(): execProcessFromVenv("start_agent_" + agent_name + ".py " + configfile) |
706 | - | ||
707 | current_dir = os.getcwd() | 712 | current_dir = os.getcwd() |
708 | 713 | ||
709 | # OLD format agents: majordome, monitoring, alert... | 714 | # OLD format agents: majordome, monitoring, alert... |
@@ -0,0 +1,1717 @@ | @@ -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,11 +280,8 @@ class ConfigPyros: | ||
280 | capability["attributes"] = component_attributes | 280 | capability["attributes"] = component_attributes |
281 | return capability | 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 | Return a dictionary giving the device file name by the device name | 285 | Return a dictionary giving the device file name by the device name |
289 | Returns: | 286 | Returns: |
290 | dict: key is device name, value is file name | 287 | dict: key is device name, value is file name |
@@ -455,7 +452,7 @@ class ConfigPyros: | @@ -455,7 +452,7 @@ class ConfigPyros: | ||
455 | exit(1) | 452 | exit(1) |
456 | #return None | 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 | Initiate class with the config file | 457 | Initiate class with the config file |
461 | set content attribute to a dictionary containing all values from the config file | 458 | set content attribute to a dictionary containing all values from the config file |
@@ -470,8 +467,7 @@ class ConfigPyros: | @@ -470,8 +467,7 @@ class ConfigPyros: | ||
470 | else: | 467 | else: |
471 | self.unit_name = unit_name | 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 | Return name of the observatory | 472 | Return name of the observatory |
477 | 473 | ||
@@ -480,7 +476,7 @@ class ConfigPyros: | @@ -480,7 +476,7 @@ class ConfigPyros: | ||
480 | """ | 476 | """ |
481 | return self.obs_config["OBSERVATORY"]["name"] | 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 | return dictionary of channels | 482 | return dictionary of channels |
@@ -498,7 +494,7 @@ class ConfigPyros: | @@ -498,7 +494,7 @@ class ConfigPyros: | ||
498 | channels[channel["name"]] = channel | 494 | channels[channel["name"]] = channel |
499 | return channels | 495 | return channels |
500 | 496 | ||
501 | - def get_computers(self)->dict: | 497 | + def get_computers(self) -> dict: |
502 | """ | 498 | """ |
503 | return dictionary of computers | 499 | return dictionary of computers |
504 | 500 | ||
@@ -515,11 +511,11 @@ class ConfigPyros: | @@ -515,11 +511,11 @@ class ConfigPyros: | ||
515 | computer["computer_config"]= self.read_and_check_config_file(self.CONFIG_PATH+computer["file"])["COMPUTER"] | 511 | computer["computer_config"]= self.read_and_check_config_file(self.CONFIG_PATH+computer["file"])["COMPUTER"] |
516 | computers[computer["name"]] = computer | 512 | computers[computer["name"]] = computer |
517 | return computers | 513 | return computers |
518 | - | ||
519 | - def get_devices(self)->dict: | 514 | + |
515 | + def get_devices(self) -> dict: | ||
520 | """ | 516 | """ |
521 | return dictionary of devices | 517 | return dictionary of devices |
522 | - | 518 | + |
523 | Returns: | 519 | Returns: |
524 | dict: [description] | 520 | dict: [description] |
525 | """ | 521 | """ |
@@ -570,7 +566,7 @@ class ConfigPyros: | @@ -570,7 +566,7 @@ class ConfigPyros: | ||
570 | agents[agent["name"]] = agent | 566 | agents[agent["name"]] = agent |
571 | return agents | 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 | Return dictionary of channel groups, tell the logic between groups of channels and within a group of channels | 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,7 +584,7 @@ class ConfigPyros: | ||
588 | info["groups"][group_id] = group | 584 | info["groups"][group_id] = group |
589 | return info | 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 | Return information of the given channel name of a unit | 589 | Return information of the given channel name of a unit |
594 | 590 | ||
@@ -602,7 +598,7 @@ class ConfigPyros: | @@ -602,7 +598,7 @@ class ConfigPyros: | ||
602 | channels = self.get_channels(unit_name) | 598 | channels = self.get_channels(unit_name) |
603 | return channels[channel_name] | 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 | Return dictionary of the topology of the observatory | 603 | Return dictionary of the topology of the observatory |
608 | 604 | ||
@@ -623,7 +619,7 @@ class ConfigPyros: | @@ -623,7 +619,7 @@ class ConfigPyros: | ||
623 | topology[key] = branch | 619 | topology[key] = branch |
624 | return topology | 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 | Return the list of active agents (i.e. agents that have an association with a device) | 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,7 +645,7 @@ class ConfigPyros: | ||
649 | result[unit["name"]] = unit | 645 | result[unit["name"]] = unit |
650 | return result | 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 | Return dictionary of component_agents of the given unit | 650 | Return dictionary of component_agents of the given unit |
655 | 651 | ||
@@ -683,9 +679,9 @@ class ConfigPyros: | @@ -683,9 +679,9 @@ class ConfigPyros: | ||
683 | """ | 679 | """ |
684 | return list(self.get_units().keys()) | 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 | Args: | 686 | Args: |
691 | name (str): name of the unit | 687 | name (str): name of the unit |
@@ -695,7 +691,7 @@ class ConfigPyros: | @@ -695,7 +691,7 @@ class ConfigPyros: | ||
695 | """ | 691 | """ |
696 | return self.get_units()[name] | 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 | Return dictionary that give for each computer, what are the associated agents to it as a list | 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,7 +791,7 @@ class ConfigPyros: | ||
795 | """ | 791 | """ |
796 | return self.get_devices()[device_name] | 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 | Return dictionary of attributes of the database for an unit | 796 | Return dictionary of attributes of the database for an unit |
801 | 797 | ||
@@ -807,7 +803,7 @@ class ConfigPyros: | @@ -807,7 +803,7 @@ class ConfigPyros: | ||
807 | """ | 803 | """ |
808 | return self.get_unit_by_name(unit_name)["DATABASE"] | 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 | Return device name associated to the agent | 808 | Return device name associated to the agent |
813 | 809 | ||
@@ -822,7 +818,8 @@ class ConfigPyros: | @@ -822,7 +818,8 @@ class ConfigPyros: | ||
822 | for device in agents_per_device: | 818 | for device in agents_per_device: |
823 | if agent_name in agents_per_device[device]: | 819 | if agent_name in agents_per_device[device]: |
824 | return self.get_device_information(device) | 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 | Return the name of the unit where the computer is used | 824 | Return the name of the unit where the computer is used |
828 | 825 | ||
@@ -836,7 +833,7 @@ class ConfigPyros: | @@ -836,7 +833,7 @@ class ConfigPyros: | ||
836 | if(computer_name in self.get_agents_per_computer(unit_name)): | 833 | if(computer_name in self.get_agents_per_computer(unit_name)): |
837 | return unit_name | 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 | Return the name of the unit where the device is used | 838 | Return the name of the unit where the device is used |
842 | 839 | ||
@@ -864,7 +861,7 @@ class ConfigPyros: | @@ -864,7 +861,7 @@ class ConfigPyros: | ||
864 | return self.get_devices()[device_name]["device_config"].get("power") | 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 | Return dictionary that contains informations about capabilities if this information is present in the device config file | 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,7 +889,7 @@ class ConfigPyros: | ||
892 | """ | 889 | """ |
893 | return self.get_devices()[device_name]["device_config"].get("connector") | 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 | Return dictionary that contains informations about power if this information is present in the device config file | 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,5 +404,6 @@ python_version = subprocess.run( "python --version | cut -d ' ' -f 2 | cut -d '. | ||
404 | python_version = python_version.stdout | 404 | python_version = python_version.stdout |
405 | today = "2021-11-09" | 405 | today = "2021-11-09" |
406 | django_version_major,django_version_minor = django.VERSION[:2][0],django.VERSION[:2][1] | 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 | \ No newline at end of file | 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}" |