From 0a3173ec863d239cf1e03fb26811dfa22b9f6823 Mon Sep 17 00:00:00 2001
From: tbadts <thomas.badts@inria.fr>
Date: Tue, 26 Nov 2024 10:24:29 +0100
Subject: [PATCH] Small reformating

---
 e2clab/app.py                          | 16 +++++------
 e2clab/config.py                       |  2 +-
 e2clab/constants/__init__.py           | 22 ++++++---------
 e2clab/experiment.py                   | 34 +++++++++++++----------
 e2clab/managers/monitoring_kwollect.py |  3 +-
 e2clab/probe.py                        | 38 ++++++++++++++------------
 e2clab/tests/unit/test_constants.py    | 13 ---------
 e2clab/tests/unit/test_experiment.py   | 12 ++------
 e2clab/tests/unit/test_probe.py        | 22 +++++++++++++--
 e2clab/utils.py                        |  3 ++
 10 files changed, 82 insertions(+), 83 deletions(-)

diff --git a/e2clab/app.py b/e2clab/app.py
index a95f2529..ae5a2ec5 100644
--- a/e2clab/app.py
+++ b/e2clab/app.py
@@ -7,7 +7,7 @@ import copy
 import json
 import logging
 from pathlib import Path
-from typing import Union
+from typing import Optional, Union
 
 from enoslib.api import Results, run_play
 from enoslib.enos_inventory import EnosInventory
@@ -48,8 +48,8 @@ class App:
         artifacts_dir: Path,
         roles: Roles,
         all_serv_extra_inf: dict,
-        app_conf: str = None,
-        env_config: Path = None,
+        app_conf: Optional[str] = None,
+        env_config: Optional[Path] = None,
         optimization_config: dict[str, any] = None,
     ) -> None:
         """Create an application for the experiment
@@ -121,7 +121,7 @@ class App:
     def run_task(
         self,
         task: WorkflowTasks,
-        current_repeat: int = None,
+        current_repeat: Optional[int] = None,
     ) -> None:
         """Run application task
 
@@ -131,10 +131,10 @@ class App:
         """
 
         # TODO: propagate enum use to the rest of the module
-        task = task.value
+        task_name = task.value
 
         # Define our working directory
-        self.working_dir = self._prepare_working_dir(task=task)
+        self.working_dir = self._prepare_working_dir(task=task_name)
         self.current_repeat = current_repeat
         # Get task run relevant additional ansible variables
         self.ansible_vars = self._vars_to_inject_ansible()
@@ -144,7 +144,7 @@ class App:
         self.logger.debug(f"[ANSIBLE VARS] {self.ansible_vars}")
 
         # filtered_config is a copy
-        filtered_config = self.config.get_task_filtered_host_config(task=task)
+        filtered_config = self.config.get_task_filtered_host_config(task=task_name)
 
         self.logger.debug(f"[TASK CONFIG] {filtered_config}")
 
@@ -157,7 +157,7 @@ class App:
             self._print_workflow_validate(
                 host_conf=host_task_conf,
                 roles_validate=ansible_roles,
-                task=task,
+                task=task_name,
                 ret=ansible_play_return,
             )
 
diff --git a/e2clab/config.py b/e2clab/config.py
index 4a7080dc..92713a5f 100644
--- a/e2clab/config.py
+++ b/e2clab/config.py
@@ -223,7 +223,7 @@ class InfrastructureConfig(dict, E2clabConfig):
         """
         # dict type ensured by schema
         manager_key = manager.value.get_config_key()
-        manager_conf: dict = self.get_manager_conf(manager)
+        manager_conf = self.get_manager_conf(manager)
         if manager_conf is None:
             return
         # TODO: change managers API
diff --git a/e2clab/constants/__init__.py b/e2clab/constants/__init__.py
index 4acc9d13..d03e4006 100644
--- a/e2clab/constants/__init__.py
+++ b/e2clab/constants/__init__.py
@@ -13,12 +13,6 @@ PATH_ROOT_E2CLAB = Path(__file__).parent.parent.resolve()
 PATH_SERVICES_PLUGINS = PATH_ROOT_E2CLAB / "services" / "plugins"
 
 
-class ExtEnum(Enum):
-    @classmethod
-    def value_list(cls):
-        return [e.value for e in cls]
-
-
 class ConfFiles:
     LAYERS_SERVICES = "layers_services.yaml"
     NETWORK = "network.yaml"
@@ -38,14 +32,14 @@ CONF_FILES_LIST = [
 """
 
 
-class Environment(ExtEnum):
+class Environment(Enum):
     G5K: str = "g5k"
     IOT_LAB: str = "iotlab"
     CHAMELEON_CLOUD: str = "chameleoncloud"
     CHAMELEON_EDGE: str = "chameleonedge"
 
 
-SUPPORTED_ENVIRONMENTS = Environment.value_list()
+SUPPORTED_ENVIRONMENTS = [e.value for e in Environment]
 
 """
     CLI constants
@@ -57,7 +51,7 @@ ENV_ARTIFACTS_DIR = "E2C_ARTIFACTS_DIR"
 ENV_AUTO_PREFIX = "E2C"
 
 
-class Command(ExtEnum):
+class Command(Enum):
     DEPLOY: str = "deploy"
     LYR_SVC: str = "layers-services"
     NETWORK: str = "network"
@@ -65,20 +59,20 @@ class Command(ExtEnum):
     FINALIZE: str = "finalize"
 
 
-COMMAND_RUN_LIST = Command.value_list()
+COMMAND_RUN_LIST = [e.value for e in Command]
 
 """
     Workflow tasks
 """
 
 
-class WorkflowTasks(ExtEnum):
+class WorkflowTasks(Enum):
     PREPARE: str = "prepare"
     LAUNCH: str = "launch"
     FINALIZE: str = "finalize"
 
 
-WORKFLOW_TASKS = WorkflowTasks.value_list()
+WORKFLOW_TASKS = [e.value for e in WorkflowTasks]
 
 
 """
@@ -86,13 +80,13 @@ WORKFLOW_TASKS = WorkflowTasks.value_list()
 """
 
 
-class ManagerSvcs(ExtEnum):
+class ManagerSvcs(Enum):
     PROVENANCE: str = layers_services.PROVENANCE_SVC
     MONITORING: str = layers_services.MONITORING_SVC
     MONITORING_IOT: str = layers_services.MONITORING_IOT_SVC
 
 
-class MonitoringType(ExtEnum):
+class MonitoringType(Enum):
     TIG: str = layers_services.MONITORING_SVC_TIG
     TPG: str = layers_services.MONITORING_SVC_TPG
     DSTAT: str = layers_services.MONITORING_SVC_DSTAT
diff --git a/e2clab/experiment.py b/e2clab/experiment.py
index 8f6eed7b..31460690 100644
--- a/e2clab/experiment.py
+++ b/e2clab/experiment.py
@@ -42,7 +42,7 @@ class Experiment:
         self,
         scenario_dir: Path,
         artifacts_dir: Path,
-        repeat: int = None,
+        repeat: Optional[int] = None,
         app_conf_list: list[str] = [],
         optimization_config=None,
         optimization_id: Optional[UUID] = None,
@@ -62,14 +62,12 @@ class Experiment:
         self.logger = get_logger(__name__, ["EXP"])
 
         # Experiment components
-        self.infra = None
-        self.net = None
-        self.app = None
+        self.infra: Optional[Infrastructure] = None
+        self.net: Optional[Network] = None
+        self.app: Optional[App] = None
 
         self.probe = TaskProbe()
 
-        self.experiment_dir = None
-
     def __setstate__(self, state):
         """Ran when unpickling"""
         self.__dict__.update(state)
@@ -149,7 +147,7 @@ class Experiment:
 
         self.logger.info("Experiment network deployed")
 
-    def application(self, task: WorkflowTasks, app_conf: str = None) -> None:
+    def application(self, task: WorkflowTasks, app_conf: Optional[str] = None) -> None:
         """
         Enforce workflow definition
         """
@@ -176,18 +174,19 @@ class Experiment:
         )
 
         # Enforce task
-        self.logger.info(f"Enforcing workflow:{task}")
-        # self.app.run_task(task=task, current_repeat=self.repeat)
+        self.logger.info(f"Enforcing workflow:{task.value}")
         self.run_task(task=task, current_repeat=self.repeat)
-        self.logger.info(f"Done enforcing workflow:{task}")
+        self.logger.info(f"Done enforcing workflow:{task.value}")
 
-    def run_task(self, task: str, current_repeat: int):
+    def run_task(self, task: WorkflowTasks, current_repeat: Optional[int] = None):
         """Wrapper for application run_task"""
-        self.probe.set_start(task_name=task)
+        if not self.app:
+            raise E2clabError("Failed initializing App")
+        self.probe.set_start(record_name=task)
         self.app.run_task(task=task, current_repeat=current_repeat)
-        self.probe.set_end(task_name=task)
+        self.probe.set_end(record_name=task)
 
-    def finalize(self, app_conf: str = None, destroy: bool = False) -> None:
+    def finalize(self, app_conf: Optional[str] = None, destroy: bool = False) -> None:
         """
         Finalize experiment
         """
@@ -260,6 +259,11 @@ class Experiment:
         """
         Release (free) computing resources, e.g. kill G5k oar jobs
         """
+        if not self.infra:
+            raise E2clabError(
+                "Can't destroy an uninstantiated infrastructure."
+                "Have you run `e2clab layers_services` ?"
+            )
         self.logger.info("Destroying provider computing resource")
         self.infra.destroy()
         self.logger.info("Destroyed computing resources")
@@ -297,7 +301,7 @@ class Experiment:
         """Prints experiment directory to stdout"""
         print(self.get_exp_dir())
 
-    def get_exp_id(self) -> UUID:
+    def get_exp_id(self) -> str:
         return self.id
 
     def get_exp_dir(self) -> Path:
diff --git a/e2clab/managers/monitoring_kwollect.py b/e2clab/managers/monitoring_kwollect.py
index 274c9d1e..257b2e60 100644
--- a/e2clab/managers/monitoring_kwollect.py
+++ b/e2clab/managers/monitoring_kwollect.py
@@ -76,13 +76,12 @@ class MonitoringKwollectManager(Manager):
         kwollect_output_dir.mkdir(exist_ok=True)
 
         task_probe = TaskProbe.get_probe()
-        records = task_probe.get_records()
 
         metrics = self._get_metrics_str()
 
         step = self.config.get(STEP, WorkflowTasks.LAUNCH.value)
 
-        rec = records.get(step)
+        rec = task_probe.get_task_record(WorkflowTasks(step))
         if not rec:
             self.logger.warning(f"No timestamps found for {step}. Did you run it ?")
             return
diff --git a/e2clab/probe.py b/e2clab/probe.py
index d2740aa5..dcff374a 100644
--- a/e2clab/probe.py
+++ b/e2clab/probe.py
@@ -4,14 +4,15 @@ Probe singletons module
 
 from dataclasses import dataclass
 from datetime import datetime
+from typing import Hashable, Optional
 
 from e2clab.log import get_logger
 
 
 @dataclass
 class Record:
-    start: datetime
-    end: datetime
+    start: Optional[datetime] = None
+    end: Optional[datetime] = None
 
 
 class TaskProbe:
@@ -23,10 +24,10 @@ class TaskProbe:
 
     _instance = None
 
-    def __init__(self):
+    def __init__(self) -> None:
         self.logger = get_logger(__name__, ["PROBE"])
 
-        self._records: dict[str, Record] = {}
+        self._records: dict[Hashable, Record] = {}
 
     def __new__(cls):
         if cls._instance is None:
@@ -41,37 +42,38 @@ class TaskProbe:
         # restore unpickled probe as singleton
         type(self)._instance = self
 
-    def set_start(self, task_name: str) -> None:
-        self._check_record(task_name)
+    def set_start(self, record_name: Hashable) -> None:
+        self._check_record(record_name)
         timestmp = self._get_now()
-        self._records[task_name].start = timestmp
+        self._records[record_name].start = timestmp
 
-        self.logger.debug(f"Set start timestamp for {task_name}")
+        self.logger.debug(f"Set start timestamp for {record_name}")
 
-    def set_end(self, task_name: str) -> None:
-        self._check_record(task_name)
+    def set_end(self, record_name: Hashable) -> None:
+        self._check_record(record_name)
         timestmp = self._get_now()
-        self._records[task_name].end = timestmp
+        self._records[record_name].end = timestmp
 
-        self.logger.debug(f"Set start timestamp for {task_name}")
+        self.logger.debug(f"Set end timestamp for {record_name}")
 
     @staticmethod
     def _get_now() -> datetime:
         return datetime.now().astimezone()
 
-    def _check_record(self, task_name: str):
+    def _check_record(self, record_name: Hashable):
         """Init record if doesn't exist"""
-        if task_name not in self._records:
-            self._records[task_name] = Record(start=None, end=None)
+        if record_name not in self._records:
+            self._records[record_name] = Record()
 
-    def get_task_record(self, task_name):
-        return self._records.get(task_name, None)
+    def get_task_record(self, record_name: Hashable):
+        return self._records.get(record_name, None)
 
-    def get_records(self) -> dict[str, Record]:
+    def get_records(self) -> dict[Hashable, Record]:
         return self._records
 
     @classmethod
     def get_probe(cls):
+        """Get timestamp probe object (singleton)"""
         if not cls._instance:
             cls._instance = cls()
         return cls._instance
diff --git a/e2clab/tests/unit/test_constants.py b/e2clab/tests/unit/test_constants.py
index c02f69ed..eab10c99 100644
--- a/e2clab/tests/unit/test_constants.py
+++ b/e2clab/tests/unit/test_constants.py
@@ -1,23 +1,10 @@
 from unittest.mock import patch
 
-import e2clab.constants as e2cconst
 from e2clab.constants.default import load_def, prefix
 from e2clab.tests.unit import TestE2cLab
 
 
 class TestConstants(TestE2cLab):
-    def test_enum(self):
-        class TestClass(e2cconst.ExtEnum):
-            A = 1
-            B = 2
-            C = "test"
-            D = 4
-            E = 5
-
-        expect = [1, 2, "test", 4, 5]
-        res = TestClass.value_list()
-        self.assertEqual(expect, res)
-
     def test_prefix(self):
         self.assertEqual("E2C_TEST", prefix("TEST"))
 
diff --git a/e2clab/tests/unit/test_experiment.py b/e2clab/tests/unit/test_experiment.py
index 51a293d1..db9495f5 100644
--- a/e2clab/tests/unit/test_experiment.py
+++ b/e2clab/tests/unit/test_experiment.py
@@ -30,6 +30,7 @@ class TestExperiment(TestE2cLab):
             scenario_dir=self.test_folder,
             artifacts_dir=self.test_folder,
         )
+        self.def_exp.initiate()
 
     def tearDown(self) -> None:
         # Removing output folder
@@ -44,7 +45,7 @@ class TestExperiment(TestE2cLab):
         self.assertIsInstance(self.def_exp.id, str)
 
     def test_initiate(self):
-        with self.assertLogs("e2clab.experiment", level="INFO") as _:
+        with self.assertLogs("e2clab.experiment", level="INFO"):
             self.def_exp.initiate()
 
         self.assertTrue(Path(self.def_exp.experiment_dir).exists())
@@ -61,7 +62,6 @@ class TestExperiment(TestE2cLab):
         infra = MockInfra.return_value
         infra.deploy.return_value = (Roles({}), Networks({}))
 
-        self.def_exp.initiate()
         self.def_exp.infrastructure()
 
         infra.deploy.assert_called_once_with(
@@ -73,14 +73,12 @@ class TestExperiment(TestE2cLab):
         self.assertIsInstance(self.def_exp.infra, Infrastructure)
 
     def test_network_no_infra(self):
-        self.def_exp.initiate()
         with self.assertRaises(E2clabError):
             self.def_exp.network()
 
     @patch("e2clab.experiment.Network", autospec=True)
     def test_network(self, MockNetwork: MagicMock):
         # Prepare test
-        self.def_exp.initiate()
         self.def_exp.infra = True
         self.def_exp.roles = Roles({})
         self.def_exp.networks = Networks({})
@@ -94,7 +92,6 @@ class TestExperiment(TestE2cLab):
         self.assertIsInstance(self.def_exp.net, Network)
 
     def test_application_no_infra(self):
-        self.def_exp.initiate()
         with self.assertRaises(E2clabError):
             self.def_exp.application("prepare")
 
@@ -105,7 +102,6 @@ class TestExperiment(TestE2cLab):
         infra = MockInfra.return_value
         infra.all_serv_extra_inf.return_value = {}
 
-        self.def_exp.initiate()
         self.def_exp.infra = infra
         self.def_exp.roles = Roles({})
         self.def_exp.networks = Networks({})
@@ -115,7 +111,6 @@ class TestExperiment(TestE2cLab):
         app.run_task.assert_called_once_with(task="prepare", current_repeat=None)
 
     def test_finalize_no_infra(self):
-        self.def_exp.initiate()
         with self.assertRaises(E2clabError):
             self.def_exp.finalize()
 
@@ -126,7 +121,6 @@ class TestExperiment(TestE2cLab):
         app = MockApp.return_value
         infra = MockInfra.return_value
 
-        self.def_exp.initiate()
         self.def_exp.infra = infra
         self.def_exp.app = app
         self.def_exp.roles = Roles({})
@@ -149,7 +143,6 @@ class TestExperiment(TestE2cLab):
         app = MockApp.return_value
         infra = MockInfra.return_value
 
-        self.def_exp.initiate()
         self.def_exp.infra = infra
         self.def_exp.app = app
         self.def_exp.roles = Roles({})
@@ -169,7 +162,6 @@ class TestExperiment(TestE2cLab):
         app = MockApp.return_value
         infra = MockInfra.return_value
 
-        self.def_exp.initiate()
         self.def_exp.infra = infra
         self.def_exp.app = app
         self.def_exp.roles = Roles({})
diff --git a/e2clab/tests/unit/test_probe.py b/e2clab/tests/unit/test_probe.py
index 1542232a..b77c8078 100644
--- a/e2clab/tests/unit/test_probe.py
+++ b/e2clab/tests/unit/test_probe.py
@@ -4,6 +4,7 @@ Testing the probe.py module
 
 import shutil
 
+from e2clab.constants import WorkflowTasks
 from e2clab.probe import TaskProbe
 
 from . import TestE2cLab
@@ -20,10 +21,27 @@ class TestTaskProbe(TestE2cLab):
     def tearDownClass(cls):
         shutil.rmtree(cls.env)
 
+    def setUp(self):
+        self.probe = TaskProbe()
+
     def test_singleton(self):
-        probe = TaskProbe()
         probe2 = TaskProbe()
-        self.assertEqual(id(probe2), id(probe))
+        self.assertEqual(id(probe2), id(self.probe))
+
+    def test_set_start(self):
+        self.probe.set_start(WorkflowTasks.LAUNCH)
+        rec = self.probe.get_task_record(WorkflowTasks.LAUNCH)
+        self.assertIsNotNone(rec.start)
+        self.assertIsNone(rec.end)
+
+    def test_set_end(self):
+        self.probe.set_end(WorkflowTasks.LAUNCH)
+        rec = self.probe.get_task_record(WorkflowTasks.LAUNCH)
+        self.assertIsNone(rec.start)
+        self.assertIsNotNone(rec.end)
+
+    def test_check_record(self):
+        pass
 
     # TODO: test __setstate__
     def test_setstate(self):
diff --git a/e2clab/utils.py b/e2clab/utils.py
index 08609127..304b862d 100644
--- a/e2clab/utils.py
+++ b/e2clab/utils.py
@@ -144,3 +144,6 @@ def write_dot_param(file, envname: str, value, comment: bool = False):
     if comment:
         line = "# " + line
     file.write(line)
+
+
+load_yaml_file("notafile")
-- 
GitLab