Blame view

src/core/pyros_django/obsconfig/obsconfig_class.py 53 KB
a2dbbde1   Alexis Koralewski   Adding new config...
1
2
3
#!/usr/bin/env python3
import pykwalify.core
import sys
0318e3c9   Alexis Koralewski   Add tests for F05...
4
5
6
7
8
import yaml
import logging
import os
import pickle
import time
a2dbbde1   Alexis Koralewski   Adding new config...
9
from datetime import datetime
0318e3c9   Alexis Koralewski   Add tests for F05...
10
from pykwalify.errors import PyKwalifyException, SchemaError
a2dbbde1   Alexis Koralewski   Adding new config...
11
12
from pathlib import Path

0318e3c9   Alexis Koralewski   Add tests for F05...
13

a2dbbde1   Alexis Koralewski   Adding new config...
14
class OBSConfig:
0318e3c9   Alexis Koralewski   Add tests for F05...
15
    # (AKo) : Config file path is checked on the settings file, if the file isn't valid (i.e not found) the error will be launched by the settings file when starting the application
a2dbbde1   Alexis Koralewski   Adding new config...
16
17
18
19
20
21
22
23
    devices_links = {}
    current_file = None
    #COMPONENT_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/components/")
    #GENERIC_DEVICES_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/devices/")
    pickle_file = "obsconfig.p"
    obs_config = None
    devices = None
    computers = None
0318e3c9   Alexis Koralewski   Add tests for F05...
24
    agents = None
a2dbbde1   Alexis Koralewski   Adding new config...
25
26
27
    obs_config_file_content = None
    #obs_config_path = os.environ.get("PATH_TO_OBSCONF_FOLDER",os.path.join(os.environ["DJANGO_PATH"],"../../../privatedev/config/default/"))
    errors = None
2d10959d   Alexis Koralewski   Adding mandatory ...
28
29
30
    MANDATORY_AGENTS = {
        "AgentSP":None,
        "AgentScheduler":None,
dd27c2bc   Alexis Koralewski   Updating agent co...
31
32
        "AgentMajordome":None,
        "AgentM":None
2d10959d   Alexis Koralewski   Adding mandatory ...
33
34
35
36

    }
    
    
0318e3c9   Alexis Koralewski   Add tests for F05...
37
    def verify_if_pickle_needs_to_be_updated(self, observatory_config_file) -> bool:
a2dbbde1   Alexis Koralewski   Adding new config...
38
39
40
41
42
43
44
45
46
47
48
49
50
51
        """

        Args:
            observatory_config_file ([type]): [description]

        Returns:
            bool: [description]
        """
        self.CONFIG_PATH = os.path.dirname(observatory_config_file)+"/"
        self.obs_config_path = self.CONFIG_PATH
        #self.CONFIG_PATH = self.obs_config_path
        if os.path.isfile(self.CONFIG_PATH+self.pickle_file) == False:
            return True
        else:
0318e3c9   Alexis Koralewski   Add tests for F05...
52
53
            pickle_file_mtime = os.path.getmtime(
                self.CONFIG_PATH+self.pickle_file)
a2dbbde1   Alexis Koralewski   Adding new config...
54
55
            obs_config_mtime = os.path.getmtime(observatory_config_file)

0318e3c9   Alexis Koralewski   Add tests for F05...
56
57
            obs_config = self.read_and_check_config_file(
                observatory_config_file)
a2dbbde1   Alexis Koralewski   Adding new config...
58
59
            if obs_config_mtime > pickle_file_mtime:
                # create obs file (yaml) from pickle["obsconfig"] with date of pickle within history folder-> nom ficher + année + mois + jour + datetime (avec secondes) -> YYYY/MM/DD H:m:s
0318e3c9   Alexis Koralewski   Add tests for F05...
60
61
                pickle_datetime = datetime.utcfromtimestamp(
                    pickle_file_mtime).strftime("%Y%m%d_%H%M%S")
a2dbbde1   Alexis Koralewski   Adding new config...
62
                # Create history folder if doesn't exist
0318e3c9   Alexis Koralewski   Add tests for F05...
63
64
                Path(self.obs_config_path +
                     "/history/").mkdir(parents=True, exist_ok=True)
a2dbbde1   Alexis Koralewski   Adding new config...
65
                file_name = f"{self.obs_config_path}/history/observatory_{pickle_datetime}.yml"
0318e3c9   Alexis Koralewski   Add tests for F05...
66
                config_file = open(observatory_config_file, "r")
a2dbbde1   Alexis Koralewski   Adding new config...
67
68
69
70
71

                with open(file_name, 'w') as f:
                    f.write(config_file.read())
                return True
            if obs_config == None:
0318e3c9   Alexis Koralewski   Add tests for F05...
72
73
                print(
                    f"Error when trying to read config file (path of config file : {observatory_config_file}")
a2dbbde1   Alexis Koralewski   Adding new config...
74
75
76
                return -1
            self.obs_config = obs_config
            # check last date of modification for devices files
40d27b85   Alexis Koralewski   Add 'inventory' s...
77
            for device in self.obs_config["OBSERVATORY"]["INVENTORY"]["DEVICES"]:
a2dbbde1   Alexis Koralewski   Adding new config...
78
79
80
81
82
                device_file = self.CONFIG_PATH+device["DEVICE"]["file"]
                device_file_mtime = os.path.getmtime(device_file)
                if device_file_mtime > pickle_file_mtime:
                    return True

40d27b85   Alexis Koralewski   Add 'inventory' s...
83
            for computer in self.obs_config["OBSERVATORY"]["INVENTORY"]["COMPUTERS"]:
a2dbbde1   Alexis Koralewski   Adding new config...
84
85
86
87
88
89
90
                computer_file = self.CONFIG_PATH+computer["COMPUTER"]["file"]
                computer_file_mtime = os.path.getmtime(computer_file)
                if computer_file_mtime > pickle_file_mtime:
                    return True
        return False

    def load(self, observatory_config_file):
0318e3c9   Alexis Koralewski   Add tests for F05...
91
92
        pickle_needs_to_be_updated = self.verify_if_pickle_needs_to_be_updated(
            observatory_config_file)
a2dbbde1   Alexis Koralewski   Adding new config...
93
94
95
96
97
98
99
100
101
102
        # check if we already read and load devices configuration and if pickle needs to be updated
        if pickle_needs_to_be_updated == False and self.devices != None:
            return None
        else:
            if os.path.isfile(self.CONFIG_PATH+self.pickle_file) and pickle_needs_to_be_updated == False:
                print("Reading pickle file")
                try:
                    can_pickle_file_be_read = False
                    while can_pickle_file_be_read != True:
                        if os.access(self.CONFIG_PATH+self.pickle_file, os.R_OK):
0318e3c9   Alexis Koralewski   Add tests for F05...
103
104
                            pickle_dict = pickle.load(
                                open(self.CONFIG_PATH+self.pickle_file, "rb"))
a2dbbde1   Alexis Koralewski   Adding new config...
105
106
107
108
109
110
111
112
113
114
115
                            can_pickle_file_be_read = True
                        else:
                            time.sleep(0.5)
                except IOError:
                    print("Error when reading the pickle file")
                try:
                    self.obs_config = pickle_dict["obs_config"]
                    self.computers = pickle_dict["computers"]
                    self.devices = pickle_dict["devices"]
                    self.devices_links = pickle_dict["devices_links"]
                    self.obs_config_file_content = pickle_dict["obs_config_file_content"]
0318e3c9   Alexis Koralewski   Add tests for F05...
116
                    self.raw_config = pickle_dict["raw_config"]
83b4b4b2   Alexis Koralewski   Fixing PYROS.bat,...
117
                    #self.agents = pickle_dict["agents"]
a2dbbde1   Alexis Koralewski   Adding new config...
118
119
                except:
                    # we rewrite the pickle file, the content will be the same otherwise we would be in the else case
0318e3c9   Alexis Koralewski   Add tests for F05...
120
121
                    print(
                        "Rewritting the pickle file (an error occured while reading it, the content will be the same as it was")
a2dbbde1   Alexis Koralewski   Adding new config...
122
                    pickle_dict = {}
0318e3c9   Alexis Koralewski   Add tests for F05...
123
124
125
126

                    self.obs_config = self.read_and_check_config_file(
                        observatory_config_file)
                    obs_file = open(observatory_config_file, "r")
a2dbbde1   Alexis Koralewski   Adding new config...
127
128
                    pickle_dict["raw_config"] = obs_file.read()
                    obs_file.close()
0318e3c9   Alexis Koralewski   Add tests for F05...
129
                    self.raw_config = pickle_dict["raw_config"]
a2dbbde1   Alexis Koralewski   Adding new config...
130
131
132
133
                    pickle_dict["obs_config"] = self.obs_config
                    pickle_dict["devices"] = self.get_devices()
                    pickle_dict["computers"] = self.get_computers()
                    pickle_dict["devices_links"] = self.devices_links
0318e3c9   Alexis Koralewski   Add tests for F05...
134
135
                    pickle_dict["obs_config_file_content"] = self.read_and_check_config_file(
                        observatory_config_file)
a2dbbde1   Alexis Koralewski   Adding new config...
136
                    print("Writing pickle file")
0318e3c9   Alexis Koralewski   Add tests for F05...
137
138
                    pickle.dump(pickle_dict, open(
                        self.CONFIG_PATH+self.pickle_file, "wb"))
a2dbbde1   Alexis Koralewski   Adding new config...
139
140
141
142
            else:
                print("Pickle file needs to be created or updated")
                pickle_dict = {}

0318e3c9   Alexis Koralewski   Add tests for F05...
143
144
                self.obs_config = self.read_and_check_config_file(
                    observatory_config_file)
a2dbbde1   Alexis Koralewski   Adding new config...
145
146
147
148
                pickle_dict["obs_config"] = self.obs_config
                pickle_dict["devices"] = self.get_devices()
                pickle_dict["computers"] = self.get_computers()
                pickle_dict["devices_links"] = self.devices_links
0318e3c9   Alexis Koralewski   Add tests for F05...
149
150
                pickle_dict["obs_config_file_content"] = self.read_and_check_config_file(
                    observatory_config_file)
a2dbbde1   Alexis Koralewski   Adding new config...
151
                print("Writing pickle file")
0318e3c9   Alexis Koralewski   Add tests for F05...
152
153
                pickle.dump(pickle_dict, open(
                    self.CONFIG_PATH+self.pickle_file, "wb"))
a2dbbde1   Alexis Koralewski   Adding new config...
154

0318e3c9   Alexis Koralewski   Add tests for F05...
155
    def check_and_return_config(self, yaml_file: str, schema_file: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
156
157
158
159
160
161
162
163
164
165
166
        """
        Check if yaml_file is valid for the schema_file and return an dictionary of the config file

        Args:
            yaml_file (str): Path to the config_file to be validated
            schema_file (str): Path to the schema file 

        Returns:
            dict: dictionary of the config file (with values)
        """
        # disable pykwalify error to clean the output
0318e3c9   Alexis Koralewski   Add tests for F05...
167
        # logging.disable(logging.ERROR)
a2dbbde1   Alexis Koralewski   Adding new config...
168
169
170
171
172
173
        try:
            can_yaml_file_be_read = False
            while can_yaml_file_be_read != True:
                if os.access(yaml_file, os.R_OK):
                    can_yaml_file_be_read = True
                else:
0318e3c9   Alexis Koralewski   Add tests for F05...
174
175
                    print(
                        f"{yaml_file} can't be accessed, waiting for availability")
a2dbbde1   Alexis Koralewski   Adding new config...
176
177
                    time.sleep(0.5)

0318e3c9   Alexis Koralewski   Add tests for F05...
178
179
            c = pykwalify.core.Core(source_file=yaml_file, schema_files=[
                                    self.SCHEMA_PATH+schema_file])
a2dbbde1   Alexis Koralewski   Adding new config...
180
181
182
            return c.validate(raise_exception=True)
        except SchemaError:
            for error in c.errors:
0318e3c9   Alexis Koralewski   Add tests for F05...
183
184
                print("Error :", str(error).split(". Path")[0])
                print("Path to error :", error.path)
a2dbbde1   Alexis Koralewski   Adding new config...
185
186
187
188
189
190
                self.errors = c.errors
            return None
        except IOError:
            print("Error when reading the observatory config file")

    @staticmethod
0318e3c9   Alexis Koralewski   Add tests for F05...
191
    def check_config(yaml_file: str, schema_file: str) -> any:
a2dbbde1   Alexis Koralewski   Adding new config...
192
193
194
195
196
197
198
199
200
201
202
        """
        Check if yaml_file is valid for the schema_file and return a boolean or list of errors according the schema

        Args:
            yaml_file (str): Path to the config_file to be validated
            schema_file (str): Path to the schema file 

        Returns:
            any: boolean (True) if the configuration is valid according the schema or a list of error otherwise
        """
        # disable pykwalify error to clean the output
0318e3c9   Alexis Koralewski   Add tests for F05...
203
        # logging.disable(logging.ERROR)
a2dbbde1   Alexis Koralewski   Adding new config...
204
205
206
207
208
209
        try:
            can_yaml_file_be_read = False
            while can_yaml_file_be_read != True:
                if os.access(yaml_file, os.R_OK):
                    can_yaml_file_be_read = True
                else:
0318e3c9   Alexis Koralewski   Add tests for F05...
210
211
                    print(
                        f"{yaml_file} can't be accessed, waiting for availability")
a2dbbde1   Alexis Koralewski   Adding new config...
212
                    time.sleep(0.5)
0318e3c9   Alexis Koralewski   Add tests for F05...
213
214
215

            c = pykwalify.core.Core(
                source_file=yaml_file, schema_files=[schema_file])
a2dbbde1   Alexis Koralewski   Adding new config...
216
217
218
219
            c.validate(raise_exception=True)
            return True
        except SchemaError:
            for error in c.errors:
0318e3c9   Alexis Koralewski   Add tests for F05...
220
221
222
                print("Error :", str(error).split(". Path")[0])
                print("Path to error :", error.path)

a2dbbde1   Alexis Koralewski   Adding new config...
223
224
225
            return c.errors
        except IOError:
            print("Error when reading the observatory config file")
0318e3c9   Alexis Koralewski   Add tests for F05...
226
227

    def read_and_check_config_file(self, yaml_file: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
228
229
230
231
232
233
234
235
236
237
238
239
240
        """
        Read the schema key of the config file to retrieve schema name and proceed to the checking of that config file
        Call check_and_return_config function and print its return.

        Args:
            yaml_file (str): path to the config file
        Returns:
            dict: Dictionary of the config file (with values)
        """
        self.current_file = yaml_file
        try:
            can_config_file_be_read = False
            while can_config_file_be_read != True:
0318e3c9   Alexis Koralewski   Add tests for F05...
241

a2dbbde1   Alexis Koralewski   Adding new config...
242
243
244
                if os.access(yaml_file, os.R_OK):
                    can_config_file_be_read = True
                else:
0318e3c9   Alexis Koralewski   Add tests for F05...
245
246
                    print(
                        f"{yaml_file} can't be accessed, waiting for availability")
a2dbbde1   Alexis Koralewski   Adding new config...
247
248
249
250
251
                    time.sleep(0.5)
            with open(yaml_file, 'r') as stream:
                print(f"Reading {yaml_file}")
                config_file = yaml.safe_load(stream)

0318e3c9   Alexis Koralewski   Add tests for F05...
252
253
254
255
            self.DJANGO_PATH = os.environ.get(
                "DJANGO_PATH", os.path.abspath(os.path.dirname(yaml_file)))
            self.SCHEMA_PATH = os.path.join(
                self.DJANGO_PATH, "../../../config/schemas/")
a2dbbde1   Alexis Koralewski   Adding new config...
256
            self.CONFIG_PATH = self.obs_config_path
0318e3c9   Alexis Koralewski   Add tests for F05...
257
258
259
260
261
262
            self.COMPONENT_PATH = os.path.join(
                self.DJANGO_PATH, "../../../config/components/")
            self.GENERIC_DEVICES_PATH = os.path.join(
                self.DJANGO_PATH, "../../../config/devices/")
            result = self.check_and_return_config(
                yaml_file, config_file["schema"])
a2dbbde1   Alexis Koralewski   Adding new config...
263
            if result == None:
0318e3c9   Alexis Koralewski   Add tests for F05...
264
265
                print(
                    "Error when reading and validating config file, please check the errors right above")
a2dbbde1   Alexis Koralewski   Adding new config...
266
267
268
269
270
271
272
273
                exit(1)
            return result

        except yaml.YAMLError as exc:
            print(exc)
        except Exception as e:
            print(e)
            return None
0318e3c9   Alexis Koralewski   Add tests for F05...
274
275

    def read_generic_component_and_return_attributes(self, component_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
276
277
278
        file_path = self.COMPONENT_PATH + component_name + ".yml"
        try:
            with open(file_path, 'r') as stream:
0318e3c9   Alexis Koralewski   Add tests for F05...
279
280
                config_file = yaml.safe_load(stream)

a2dbbde1   Alexis Koralewski   Adding new config...
281
282
                attributes = {}
                for attribute in config_file:
0318e3c9   Alexis Koralewski   Add tests for F05...
283

a2dbbde1   Alexis Koralewski   Adding new config...
284
285
286
287
288
289
290
291
                    attribute = attribute["attribute"]
                    attributes[attribute.pop("key")] = attribute
                return attributes
        except yaml.YAMLError as exc:
            print(exc)
        except Exception as e:
            print(e)
            return None
0318e3c9   Alexis Koralewski   Add tests for F05...
292
293

    def read_capability_of_device(self, capability: dict) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
294
295
296
297
298
299
300
301
302
        """
        Read capability of device and inherit attributes from generic component then overwrite attributes defined in device config

        Args:
            capability (dict): dictionary containing a capabilitiy (keys : component and attributes)

        Returns:
            dict: dictionary of capability inherited by generic component and overwritten his attributes by current attributes of capability
        """
0318e3c9   Alexis Koralewski   Add tests for F05...
303
304
305
306

        component_attributes = self.read_generic_component_and_return_attributes(
            capability["component"])

a2dbbde1   Alexis Koralewski   Adding new config...
307
308
309
310
        attributes = {}
        # get all attributes of device's capability
        for attribute in capability["attributes"]:
            attribute = attribute["attribute"]
0318e3c9   Alexis Koralewski   Add tests for F05...
311

a2dbbde1   Alexis Koralewski   Adding new config...
312
            attributes[attribute.pop("key")] = attribute
0318e3c9   Alexis Koralewski   Add tests for F05...
313

a2dbbde1   Alexis Koralewski   Adding new config...
314
315
316
        # for each attributes of generic component attributes
        for attribute_name in attributes.keys():
            # merge attributes of general component with specified component in device config file
0318e3c9   Alexis Koralewski   Add tests for F05...
317
318
            new_attributes = {
                **component_attributes[attribute_name], **attributes[attribute_name]}
a2dbbde1   Alexis Koralewski   Adding new config...
319
320
            if "is_enum" in component_attributes[attribute_name].keys():
                # make an intersection of both list of values
0318e3c9   Alexis Koralewski   Add tests for F05...
321
322
                new_attributes["value"] = list(set(attributes[attribute_name]["value"]) & set(
                    component_attributes[attribute_name]["value"]))
a2dbbde1   Alexis Koralewski   Adding new config...
323
                if len(new_attributes["value"]) == 0:
0318e3c9   Alexis Koralewski   Add tests for F05...
324
325
                    print(
                        f"Value of lastly read device's attribute '{attribute_name}' isn't one of the values of component configuration for this device (component configuration value(s): {component_attributes[attribute_name]['value']}) (actual value : {attributes[attribute_name]['value']})")
a2dbbde1   Alexis Koralewski   Adding new config...
326
327
                    exit(1)
            component_attributes[attribute_name] = new_attributes
0318e3c9   Alexis Koralewski   Add tests for F05...
328

a2dbbde1   Alexis Koralewski   Adding new config...
329
330
331
332
333
334
335
336
337
338
339
        # return inherited and overwritten attributes of capability
        capability["attributes"] = component_attributes
        return capability

    def get_devices_names_and_file(self) -> dict:
        """
        Return a dictionary giving the device file name by the device name 
        Returns:
            dict: key is device name, value is file name
        """
        devices_names_and_files = {}
40d27b85   Alexis Koralewski   Add 'inventory' s...
340
        for device in self.obs_config["OBSERVATORY"]["INVENTORY"]["DEVICES"]:
a2dbbde1   Alexis Koralewski   Adding new config...
341
            device = device["DEVICE"]
0318e3c9   Alexis Koralewski   Add tests for F05...
342

a2dbbde1   Alexis Koralewski   Adding new config...
343
344
345
            devices_names_and_files[device["name"]] = device["file"]
        return devices_names_and_files

0318e3c9   Alexis Koralewski   Add tests for F05...
346
    def read_device_config_file(self, config_file_name: str, is_generic=False) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
        """
        Read the device config file, inherit generic device config if "generic" key is present in "DEVICE".
        Associate capabilities of attached_devices if this device has attached_devices.
        Inherit capabilities from generic component and overwritte attributes defined in the device config
        Args:
            config_file_name (str): file name to be read
            is_generic (bool, optional): tells if we're reading a generic configuration (is_generic =True) or not (is_generic = False). Defaults to False.

        Returns:
            dict: formatted device configuration (attributes, capabilities...)
        """
        self.current_file = config_file_name
        devices_name_and_file = self.get_devices_names_and_file()
        if not is_generic:
            # check if device config file is listed in observatory configuration
0318e3c9   Alexis Koralewski   Add tests for F05...
362
363
            current_device_name = [device for device, file_name in devices_name_and_file.items(
            ) if file_name in config_file_name[len(self.CONFIG_PATH):]]
a2dbbde1   Alexis Koralewski   Adding new config...
364
            if len(current_device_name) <= 0:
0318e3c9   Alexis Koralewski   Add tests for F05...
365
366
                print(
                    f"Current file '{config_file_name[len(self.CONFIG_PATH):]}' isn't listed in observatory configuration")
a2dbbde1   Alexis Koralewski   Adding new config...
367
                print("The devices names and files are: ")
0318e3c9   Alexis Koralewski   Add tests for F05...
368
369
370
                for device_name, file_name in devices_name_and_file.items():
                    print(
                        f"device name: '{device_name}', device filename: '{file_name}'")
a2dbbde1   Alexis Koralewski   Adding new config...
371
372
373
374
375
376
                exit(1)
        print(f"Reading {config_file_name}")
        try:
            with open(config_file_name, 'r') as stream:
                config_file = yaml.safe_load(stream)
                # if we're reading a generic device configuration, the path to get the schema is different than usual
0318e3c9   Alexis Koralewski   Add tests for F05...
377
378
379
                self.SCHEMA_PATH = os.path.join(
                    self.DJANGO_PATH, "../../../config/schemas/")

a2dbbde1   Alexis Koralewski   Adding new config...
380
                # read and verify that the device configuration match the schema
0318e3c9   Alexis Koralewski   Add tests for F05...
381
382
383
                # result will contain the final device configuration (processed)
                result = self.check_and_return_config(
                    config_file_name, config_file["schema"])
a2dbbde1   Alexis Koralewski   Adding new config...
384
385
                # if the configuration didn't match the schema or had an error when reading the file
                if result == None:
0318e3c9   Alexis Koralewski   Add tests for F05...
386
387
388
                    # TODO : throw exception in check_and_return_config ?
                    print(
                        "Error when reading and validating config file, please check the errors right above")
a2dbbde1   Alexis Koralewski   Adding new config...
389
390
391
392
                    exit(1)
                else:
                    # the configuration is valid
                    # storing DEVICE key in device (DEVICE can contains : attributes of device, capabilities, attached devices)
0318e3c9   Alexis Koralewski   Add tests for F05...
393
                    # device will be used to navigate through the configuration
a2dbbde1   Alexis Koralewski   Adding new config...
394
395
396
397
398
399
400
                    device = result["DEVICE"]
                    generic_device_config = None
                    # if the device is associated to an generic configuration, we'll read that generic configuration to inherit his attributes
                    if "generic" in device:
                        # storing the whole current config
                        current_config = result
                        # read and get the generic device config
0318e3c9   Alexis Koralewski   Add tests for F05...
401
402
                        generic_device_config = self.read_device_config_file(
                            self.GENERIC_DEVICES_PATH+device["generic"], is_generic=True)
a2dbbde1   Alexis Koralewski   Adding new config...
403
                        # merge whole device config but we need to merge capabilities differently after
0318e3c9   Alexis Koralewski   Add tests for F05...
404
405
                        new_config = {
                            **generic_device_config["DEVICE"], **current_config["DEVICE"]}
a2dbbde1   Alexis Koralewski   Adding new config...
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
                        result["DEVICE"] = new_config

                    # device has capabilities
                    if "CAPABILITIES" in device:
                        capabilities = []
                        # if the device is associated to a generic device we need to associate his capabilities with the generic capabilities and overwrite them
                        if generic_device_config != None:
                            # we're making a copy of generic device config so we can remove items during loop
                            copy_generic_device_config = generic_device_config.copy()
                            # We have to extend capabilities of generic device configuration
                            for capability in current_config["DEVICE"]["CAPABILITIES"]:
                                is_capability_in_generic_config = False
                                current_config_capability = capability["CAPABILITY"]
                                current_config_component = current_config_capability["component"]
                                # find if this component was defined in generic_device_config
0318e3c9   Alexis Koralewski   Add tests for F05...
421
                                for index, generic_config_capability in enumerate(generic_device_config["DEVICE"]["CAPABILITIES"]):
a2dbbde1   Alexis Koralewski   Adding new config...
422
423
424
425
                                    # if the current capability is the capability of the component we're looking for
                                    if current_config_component == generic_config_capability["component"]:
                                        is_capability_in_generic_config = True
                                        # we're merging their attributes
0318e3c9   Alexis Koralewski   Add tests for F05...
426
427
                                        new_attributes = generic_config_capability["attributes"].copy(
                                        )
a2dbbde1   Alexis Koralewski   Adding new config...
428
                                        attributes = {}
0318e3c9   Alexis Koralewski   Add tests for F05...
429
                                        current_config_attributes = current_config_capability["attributes"]
a2dbbde1   Alexis Koralewski   Adding new config...
430
431
432
                                        generic_config_attributes = generic_config_capability["attributes"]
                                        for attribute in current_config_attributes:
                                            attribute = attribute["attribute"]
0318e3c9   Alexis Koralewski   Add tests for F05...
433
434
                                            attributes[attribute.pop(
                                                "key")] = attribute
a2dbbde1   Alexis Koralewski   Adding new config...
435
436
437
                                        # for each attributes of device component attributes
                                        for attribute_name in attributes.keys():
                                            # merge attributes of general component with specified component in device config file
0318e3c9   Alexis Koralewski   Add tests for F05...
438
439
                                            new_attributes[attribute_name] = {
                                                **generic_config_attributes[attribute_name], **attributes[attribute_name]}
a2dbbde1   Alexis Koralewski   Adding new config...
440
441
                                            if "is_enum" in generic_config_attributes[attribute_name].keys():
                                                # make an intersection of both list of values
0318e3c9   Alexis Koralewski   Add tests for F05...
442
443
                                                new_attributes[attribute_name]["value"] = list(set(attributes[attribute_name]["value"]) & set(
                                                    generic_config_attributes[attribute_name]["value"]))
a2dbbde1   Alexis Koralewski   Adding new config...
444
                                                if len(new_attributes[attribute_name]["value"]) == 0:
0318e3c9   Alexis Koralewski   Add tests for F05...
445
446
                                                    print(
                                                        f"Value of device '{config_file_name}' for attribute '{attribute_name}' isn't one of the values of generic configuration for this device (generic value(s): {generic_config_attributes[attribute_name]['value']}) (actual value : {attributes[attribute_name]['value']})")
a2dbbde1   Alexis Koralewski   Adding new config...
447
448
                                                    exit(1)
                                        # removing this capability from generic device configuration
0318e3c9   Alexis Koralewski   Add tests for F05...
449
450
451
452
                                        generic_device_config["DEVICE"]["CAPABILITIES"].pop(
                                            index)
                                        capabilities.append(
                                            {"component": current_config_component, "attributes": new_attributes})
a2dbbde1   Alexis Koralewski   Adding new config...
453
454
                                        break
                                if is_capability_in_generic_config == False:
0318e3c9   Alexis Koralewski   Add tests for F05...
455
456
                                    current_config_capability = self.read_capability_of_device(
                                        current_config_capability)
a2dbbde1   Alexis Koralewski   Adding new config...
457
                                    # the component defined in the current_config isn't defined in generic config (should not happen but we'll deal with that case anyway) : we're simply adding this capability
0318e3c9   Alexis Koralewski   Add tests for F05...
458
459
                                    capabilities.append(
                                        current_config_capability)
a2dbbde1   Alexis Koralewski   Adding new config...
460
461
462
463
464
465
466
                            # looping through generic device config's capabilities in order to add them to current device configuration
                            for generic_config_capability in generic_device_config["DEVICE"]["CAPABILITIES"]:
                                capabilities.append(generic_config_capability)
                        else:
                            # device not associated to a generic device configuration
                            for capability in device["CAPABILITIES"]:
                                capability = capability["CAPABILITY"]
0318e3c9   Alexis Koralewski   Add tests for F05...
467
468
                                capabilities.append(
                                    self.read_capability_of_device(capability))
a2dbbde1   Alexis Koralewski   Adding new config...
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
                        device["CAPABILITIES"] = capabilities
                        # associate capabilities to final device configuration (stored in result variable)
                        result["DEVICE"]["CAPABILITIES"] = device["CAPABILITIES"]
                    if "ATTACHED_DEVICES" in device.keys():
                        # device has attached devices, we need to read their configuration in order to get their capabilities and add them to the current device
                        devices_name_and_file = self.get_devices_names_and_file()
                        active_devices = self.get_active_devices()
                        for attached_device in device["ATTACHED_DEVICES"]:
                            is_attached_device_link_to_agent = False
                            # we're looking for if the attached device is associated to an agent (i.e. the device is considered as 'active'). However an attached_device shoudn't be active
                            for active_device in active_devices:
                                if devices_name_and_file[active_device] == attached_device["file"]:
                                    # the attached device is an active device (so it's linked to an agent)
                                    is_attached_device_link_to_agent = True
                                    break
                            if self.CONFIG_PATH+attached_device["file"] != config_file_name and not is_attached_device_link_to_agent:
0318e3c9   Alexis Koralewski   Add tests for F05...
485
486
                                # if the attached device isn't the device itself and not active

a2dbbde1   Alexis Koralewski   Adding new config...
487
                                # get configuration of attached device
0318e3c9   Alexis Koralewski   Add tests for F05...
488
489
                                config_of_attached_device = self.read_device_config_file(
                                    self.CONFIG_PATH+attached_device["file"])
a2dbbde1   Alexis Koralewski   Adding new config...
490
491
                                capabilities_of_attached_device = None
                                if "CAPABILITIES" in config_of_attached_device["DEVICE"].keys():
0318e3c9   Alexis Koralewski   Add tests for F05...
492
493
                                    capabilities_of_attached_device = config_of_attached_device[
                                        "DEVICE"]["CAPABILITIES"]
a2dbbde1   Alexis Koralewski   Adding new config...
494
495
                                if capabilities_of_attached_device != None:
                                    # get name of device corresponding to the config file name
0318e3c9   Alexis Koralewski   Add tests for F05...
496
497
498
499
500
                                    parent_device_name = [device for device, file_name in devices_name_and_file.items(
                                    ) if file_name == config_file_name[len(self.CONFIG_PATH):]]
                                    attached_device_name = [device for device, file_name in devices_name_and_file.items(
                                    ) if file_name == attached_device["file"]]
                                    if len(parent_device_name) > 0:
a2dbbde1   Alexis Koralewski   Adding new config...
501
502
                                        parent_device_name = parent_device_name[0]
                                    else:
0318e3c9   Alexis Koralewski   Add tests for F05...
503
504
505
506
507
508
509
                                        print(
                                            f"Attached device filename '{config_file_name[len(self.CONFIG_PATH):]}' is not listed in observatory devices files names")
                                        print(
                                            "The devices names and files are: ")
                                        for device_name, file_name in devices_name_and_file.items():
                                            print(
                                                f"device name: '{device_name}', device filename: '{file_name}'")
a2dbbde1   Alexis Koralewski   Adding new config...
510
                                        exit(1)
0318e3c9   Alexis Koralewski   Add tests for F05...
511
                                    if len(attached_device_name) > 0:
a2dbbde1   Alexis Koralewski   Adding new config...
512
513
                                        attached_device_name = attached_device_name[0]
                                    else:
0318e3c9   Alexis Koralewski   Add tests for F05...
514
515
516
517
518
519
520
521

                                        print(
                                            f"Attached device filename '{attached_device['file']}' is not listed in observatory devices files names")
                                        print(
                                            "The devices names and files are: ")
                                        for device_name, file_name in devices_name_and_file.items():
                                            print(
                                                f"device name: '{device_name}', device filename: '{file_name}'")
a2dbbde1   Alexis Koralewski   Adding new config...
522
523
524
525
526
                                        exit(1)
                                    # associate attached device to his 'parent' device (parent device is the currently read device)
                                    self.devices_links[attached_device_name] = parent_device_name
                                    for capability in capabilities_of_attached_device:
                                        # add capabilities of attached device to current device
0318e3c9   Alexis Koralewski   Add tests for F05...
527
528
                                        result["DEVICE"]["CAPABILITIES"].append(
                                            capability)
a2dbbde1   Alexis Koralewski   Adding new config...
529
530
531
532
533
534
                return result
        except yaml.YAMLError as exc:
            print(exc)
        except Exception as e:
            print(e)
            exit(1)
0318e3c9   Alexis Koralewski   Add tests for F05...
535
            # return None
a2dbbde1   Alexis Koralewski   Adding new config...
536

0318e3c9   Alexis Koralewski   Add tests for F05...
537
    def __init__(self, observatory_config_file: str, unit_name: str = "") -> None:
a2dbbde1   Alexis Koralewski   Adding new config...
538
539
540
541
542
543
544
545
546
547
548
549
550
        """
        Initiate class with the config file
        set content attribute to a dictionary containing all values from the config file

        Args:
            config_file_name (str): path to the config file
        """
        self.load(observatory_config_file)
        if unit_name == "":
            # By default we will use the first unit
            self.unit_name = self.get_units_name()[0]
        else:
            self.unit_name = unit_name
2d10959d   Alexis Koralewski   Adding mandatory ...
551
552
        # call get_agents so the class will check if mandatory agents are in the obsconfig 
        self.get_agents(self.unit_name)
a2dbbde1   Alexis Koralewski   Adding new config...
553
554
555
556
557
558
559
560
561
562
563

    def get_obs_name(self) -> str:
        """
        Return name of the observatory

        Returns:
            str: Name of the observatory
        """
        return self.obs_config["OBSERVATORY"]["name"]

    def get_channels(self, unit_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
564
565
566
567
568
569
570
571
572
573
        """
        return dictionary of channels

        Args:
            unit_name (str): Name of the unit
        Returns:
            dict: [description]
        """
        unit = self.get_unit_by_name(unit_name)
        channels = {}
0318e3c9   Alexis Koralewski   Add tests for F05...
574

a2dbbde1   Alexis Koralewski   Adding new config...
575
576
577
578
        for channel_id in range(len(unit["TOPOLOGY"]["CHANNELS"])):
            channel = unit["TOPOLOGY"]["CHANNELS"][channel_id]["CHANNEL"]
            channels[channel["name"]] = channel
        return channels
0318e3c9   Alexis Koralewski   Add tests for F05...
579

a2dbbde1   Alexis Koralewski   Adding new config...
580
581
582
583
584
585
586
587
588
589
590
    def get_computers(self) -> dict:
        """
        return dictionary of computers

        Returns:
            dict: [description]
        """
        if self.computers != None:
            return self.computers
        else:
            computers = {}
40d27b85   Alexis Koralewski   Add 'inventory' s...
591
592
            for computer_id in range(len(self.obs_config["OBSERVATORY"]["INVENTORY"]["COMPUTERS"])):
                computer = self.obs_config["OBSERVATORY"]["INVENTORY"]["COMPUTERS"][computer_id]["COMPUTER"]
820e5db5   Alexis Koralewski   Adding new method...
593
                if "file" in computer.keys():
0318e3c9   Alexis Koralewski   Add tests for F05...
594
595
                    computer["computer_config"] = self.read_and_check_config_file(
                        self.CONFIG_PATH+computer["file"])["COMPUTER"]
820e5db5   Alexis Koralewski   Adding new method...
596
                print(computer)
a2dbbde1   Alexis Koralewski   Adding new config...
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
                computers[computer["name"]] = computer
            return computers

    def get_devices(self) -> dict:
        """
        return dictionary of devices

        Returns:
            dict: [description]
        """
        if self.devices != None:
            return self.devices

        else:
            devices = {}
40d27b85   Alexis Koralewski   Add 'inventory' s...
612
613
            for device_id in range(len(self.obs_config["OBSERVATORY"]["INVENTORY"]["DEVICES"])):
                device = self.obs_config["OBSERVATORY"]["INVENTORY"]["DEVICES"][device_id]["DEVICE"]
0318e3c9   Alexis Koralewski   Add tests for F05...
614
615
616
                if("file" in device.keys()):
                    device["device_config"] = self.read_device_config_file(
                        self.CONFIG_PATH+device["file"])["DEVICE"]
a2dbbde1   Alexis Koralewski   Adding new config...
617
618
619
                devices[device["name"]] = device
            return devices

0318e3c9   Alexis Koralewski   Add tests for F05...
620
    def get_devices_names(self) -> list:
a2dbbde1   Alexis Koralewski   Adding new config...
621
622
623
624
625
626
        """Return the list of devices names of an observatory

        Returns:
            list: list of names of devices
        """
        devices_names = []
40d27b85   Alexis Koralewski   Add 'inventory' s...
627
        for device in self.obs_config["OBSERVATORY"]["INVENTORY"]["DEVICES"]:
a2dbbde1   Alexis Koralewski   Adding new config...
628
629
630
            devices_names.append(device["DEVICE"]["name"])
        return devices_names

0318e3c9   Alexis Koralewski   Add tests for F05...
631
    def get_agents(self, unit_name) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
632
633
634
635
636
637
638
639
640
641
642
643
644
645
        """
        return dictionary of agents 

        Args:
            unit_name (str): name of the unit

        Returns:
            dict: dictionary of agents. For each agents tell the name, computer, device, protocole, etc...
        """
        unit = self.get_unit_by_name(unit_name)
        if self.agents != None:
            return self.agents
        else:
            agents = {}
0318e3c9   Alexis Koralewski   Add tests for F05...
646

a2dbbde1   Alexis Koralewski   Adding new config...
647
648
649
650
651
            for agent_id in range(len(unit["AGENTS"])):
                # Agents is a list containing dictionary that have only one key
                key = list(unit["AGENTS"][agent_id].keys())[0]
                agent = unit["AGENTS"][agent_id][key]
                agents[agent["name"]] = agent
2d10959d   Alexis Koralewski   Adding mandatory ...
652
653
654
655
656
                if agent["name"] in self.MANDATORY_AGENTS.keys():
                    self.MANDATORY_AGENTS[agent["name"]] = agent
            for key in self.MANDATORY_AGENTS:
                if self.MANDATORY_AGENTS[key] == None:
                    raise MissingMandatoryAgentException(key)
a2dbbde1   Alexis Koralewski   Adding new config...
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
            return agents

    def get_layouts(self, unit_name: str) -> dict:
        """
        Return dictionary of layouts

        Args:
            unit_name (str): name of the unit
        Returns:
            dict: dictionary of layouts
        """
        unit = self.get_unit_by_name(unit_name)
        info = {}
        info["layouts"] = {}
        for layout_id in range(len(unit["TOPOLOGY"]["LAYOUTS"])):
            layout = unit["TOPOLOGY"]["LAYOUTS"][layout_id]["LAYOUT"]
            info["layouts"][layout["name"]] = layout
        return info

0318e3c9   Alexis Koralewski   Add tests for F05...
676
    def get_albums(self, unit_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
        """
        Return dictionary of layouts

        Args:
            unit_name (str): name of the unit
        Returns:
            dict: dictionary of layouts
        """
        unit = self.get_unit_by_name(unit_name)
        info = {}
        info["albums"] = {}
        for album_id in range(len(unit["TOPOLOGY"]["ALBUMS"])):
            album = unit["TOPOLOGY"]["ALBUMS"][album_id]["ALBUM"]
            info["albums"][album["name"]] = album
        return info

    def get_channel_information(self, unit_name: str, channel_name: str) -> dict:
        """
        Return information of the given channel name of a unit

        Args:
            unit_name (str): Name of the unit 
            channel_name (str): name of the channel

        Returns:
            dict: dictionary containing all values that define this channel
        """
        channels = self.get_channels(unit_name)
        return channels[channel_name]

0318e3c9   Alexis Koralewski   Add tests for F05...
707
    def get_topology(self, unit_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
        """
        Return dictionary of the topology of the observatory

        Args:
            unit_name (str): Name of the unit
        Returns:
            dict: dictionary representing the topology of an unit (security, mount, channels, layouts, albums)
        """
        unit = self.get_unit_by_name(unit_name)
        topology = {}
        for key in unit["TOPOLOGY"].keys():
            branch = unit["TOPOLOGY"][key]
            if key == "CHANNELS":
                topology[key] = self.get_channels(unit_name)
            elif key == "LAYOUTS":
                topology[key] = self.get_layouts(unit_name)
            elif key == "ALBUMS":
                topology[key] = self.get_albums(unit_name)
            else:
                topology[key] = branch
        return topology

    def get_active_agents(self, unit_name: str) -> list:
        """
        Return the list of active agents (i.e. agents that have an association with a device)

        Args:
            unit_name (str): Name of the unit

        Returns:
            list: kist of the name of active agents
        """
        return list(self.get_agents(unit_name).keys())

0318e3c9   Alexis Koralewski   Add tests for F05...
742
    def get_units(self) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
        """
        Return all units sort by name defined in the config file

        Returns:
            dict: dictionary giving for a unit_name, his content (name,database,topology,agents,...)
        """
        result = {}
        units = self.obs_config["OBSERVATORY"]["UNITS"]
        for unit in units:
            unit = unit["UNIT"]
            result[unit["name"]] = unit
        return result

    def get_components_agents(self, unit_name: str) -> dict:
        """
        Return dictionary of component_agents of the given unit

        Args:
            unit_name (str): Name of the unit

        Returns:
            dict: dictionary sort by component name giving the associated agent (agent name)
        """
        components_agents = {}
        topology = self.get_topology(unit_name)
        for element in topology:
0318e3c9   Alexis Koralewski   Add tests for F05...
769
            if element in ("SECURITY", "MOUNT", "CHANNELS"):
a2dbbde1   Alexis Koralewski   Adding new config...
770
771
772
773
774
775
776
777
778
779
780
                if(element != "CHANNELS"):
                    for component_agent in topology[element]["COMPONENT_AGENTS"]:
                        component_name = list(component_agent.keys())[0]
                        components_agents[component_name] = component_agent[component_name]
                else:
                    for channel in topology[element]:
                        for component_agent in topology[element][channel]["COMPONENT_AGENTS"]:
                            component_name = list(component_agent.keys())[0]
                            components_agents[component_name] = component_agent[component_name]
        return components_agents

0318e3c9   Alexis Koralewski   Add tests for F05...
781
    def get_units_name(self) -> list:
a2dbbde1   Alexis Koralewski   Adding new config...
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
        """
        Return list of units names 

        Returns:
            [list]: names of units
        """
        return list(self.get_units().keys())

    def get_unit_by_name(self, name: str) -> dict:
        """
        Return dictionary containing definition of the unit that matches the given name

        Args:
            name (str): name of the unit

        Returns:
            dict: dictonary representing the unit
        """
        return self.get_units()[name]
0318e3c9   Alexis Koralewski   Add tests for F05...
801

a2dbbde1   Alexis Koralewski   Adding new config...
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
    def get_agents_per_computer(self, unit_name: str) -> dict:
        """
        Return dictionary that give for each computer, what are the associated agents to it as a list

        Args:
            unit_name (str): Name of the unit

        Returns:
            dict:  dictionary that give for each computer, what are the associated agents to it as a list

        """
        agents_per_computer = {}
        agents = self.get_agents(unit_name)
        for agent in agents:
            computer_name = agents[agent]["computer"]
            if(agents[agent]["computer"] not in agents_per_computer.keys()):
                agents_per_computer[computer_name] = [agent]
            else:
                agents_per_computer[computer_name].append(agent)
        return agents_per_computer

0318e3c9   Alexis Koralewski   Add tests for F05...
823
    def get_agents_per_device(self, unit_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
        """
        Return dictionary that give for each device, what are the associated agents to it as a list

        Args:
            unit_name (str): Name of the unit

        Returns:
            dict:  dictionary that give for each device, what are the associated agents to it as a list

        """
        agents_per_device = {}
        agents = self.get_agents(unit_name)
        for agent in agents:
            if("device" in agents[agent].keys()):
                device_name = agents[agent]["device"]
                if device_name in self.get_devices_names():
                    if(agents[agent]["device"] not in agents_per_device.keys()):
                        agents_per_device[device_name] = [agent]
                    else:
                        agents_per_device[device_name].append(agent)
                else:
0318e3c9   Alexis Koralewski   Add tests for F05...
845
846
                    print(
                        f"Error: device name '{device_name}' for agent '{agent}' is not known in the configuration file. The device name must match one of the names defined in the DEVICES section")
a2dbbde1   Alexis Koralewski   Adding new config...
847
848
849
                    exit(1)
        return agents_per_device

0318e3c9   Alexis Koralewski   Add tests for F05...
850
    def get_active_devices(self) -> list:
a2dbbde1   Alexis Koralewski   Adding new config...
851
852
853
854
855
856
857
858
859
860
861
862
        """
        Return a list of active device names

        Returns:
            list: list of active device names
        """
        active_devices = []
        for unit_name in self.get_units():
            for device in self.get_agents_per_device(unit_name):
                active_devices.append(device)
        return active_devices

0318e3c9   Alexis Koralewski   Add tests for F05...
863
    def get_active_computers(self) -> list:
a2dbbde1   Alexis Koralewski   Adding new config...
864
865
866
867
868
869
870
871
872
873
874
875
876
        """
        Return a list of active computer names

        Returns:
            list: list of active computer names
        """
        active_computers = []
        for unit_name in self.get_units():
            unit = self.get_unit_by_name(unit_name)
            for computer in self.get_agents_per_computer(unit_name):
                active_computers.append(computer)
        return active_computers

0318e3c9   Alexis Koralewski   Add tests for F05...
877
    def get_agent_information(self, unit_name: str, agent_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
878
879
880
881
882
883
884
885
886
887
888
889
        """
        Give the dictionary of attributes of the agent for an unit.

        Args:
            unit (dict): dictonary representing the unit
            agent_name (str): agent name

        Returns:
            dict: dictionary containing attributes of the agent
        """
        return self.get_agents(unit_name)[agent_name]

0318e3c9   Alexis Koralewski   Add tests for F05...
890
    def get_device_information(self, device_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
        """
        Give the dictionary of the attributes of the device 

        Args:
            device_name (str): device name

        Returns:
            dict: dictionary containing attributes of the device
        """
        return self.get_devices()[device_name]

    def get_database_for_unit(self, unit_name: str) -> dict:
        """
        Return dictionary of attributes of the database for an unit

        Args:
            unit_name (str): unit name

        Returns:
            dict: dictionary of attributes of the database for an unit
        """
        return self.get_unit_by_name(unit_name)["DATABASE"]

2d10959d   Alexis Koralewski   Adding mandatory ...
914
915
916
917
918
919
920
921
922
923
924
925
926
927
    def get_database_environment(self, unit_name:str)->str:
        """
        Return file path to environment file with the attributes of the database for an unit

        Args:
            unit_name (str): unit name

        Returns:
            str: path to the environment file of the database for an unit
        """
        database_dict = self.get_unit_by_name(unit_name)["DATABASE"]
        complete_envfile_path = os.path.join(self.obs_config_path, database_dict["file"])
        return complete_envfile_path

a2dbbde1   Alexis Koralewski   Adding new config...
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
    def get_device_for_agent(self, unit_name: str, agent_name: str) -> str:
        """
        Return device name associated to the agent

        Args:
            unit (dict): dictonary representing the unit
            agent_name (str): agent name

        Returns:
            str: device name associated to this agent
        """
        agents_per_device = self.get_agents_per_device(unit_name)
        for device in agents_per_device:
            if agent_name in agents_per_device[device]:
                return self.get_device_information(device)

    def get_unit_of_computer(self, computer_name: str) -> str:
        """
        Return the name of the unit where the computer is used

        Args:
            computer_name (str): computer name

        Returns:
            str: unit name
        """
        for unit_name in self.get_units():
            if(computer_name in self.get_agents_per_computer(unit_name)):
                return unit_name

0318e3c9   Alexis Koralewski   Add tests for F05...
958
    def get_unit_of_device(self, device_name: str) -> str:
a2dbbde1   Alexis Koralewski   Adding new config...
959
960
961
962
963
964
965
966
967
968
969
970
971
        """
        Return the name of the unit where the device is used

        Args:
            device_name (str): device name

        Returns:
            str: unit name
        """
        for unit_name in self.get_units():
            if(device_name in self.get_agents_per_device(unit_name)):
                return unit_name

0318e3c9   Alexis Koralewski   Add tests for F05...
972
    def get_device_power(self, device_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
973
974
975
976
977
978
979
980
981
982
983
984
        """
        Return dictionary that contains informations about power if this information is present in the device config file

        Return None if this information isn't stored in device's config file
        Args:
            device_name (str): name of the device

        Returns:
            dict: informations about power of device
        """
        return self.get_devices()[device_name]["device_config"].get("power")

0318e3c9   Alexis Koralewski   Add tests for F05...
985
    def get_device_capabilities(self, device_name: str) -> list:
a2dbbde1   Alexis Koralewski   Adding new config...
986
987
988
989
990
991
992
993
994
995
996
        """
        Return dictionary that contains informations about capabilities if this information is present in the device config file

        Return empty list if this information isn't stored in device's config file
        Args:
            device_name (str): name of the device

        Returns:
            list: list of capabilities of device
        """
        list_of_capabilities = []
0318e3c9   Alexis Koralewski   Add tests for F05...
997
998
        capabilities = self.get_devices(
        )[device_name]["device_config"].get("CAPABILITIES")
a2dbbde1   Alexis Koralewski   Adding new config...
999
1000
        return capabilities

0318e3c9   Alexis Koralewski   Add tests for F05...
1001
    def get_device_connector(self, device_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
        """
        Return dictionary that contains informations about connector if this information is present in the device config file

        Return None if this information isn't stored in device's config file
        Args:
            device_name (str): name of the device

        Returns:
            dict: informations about connector of device
        """
        return self.get_devices()[device_name]["device_config"].get("connector")

    def get_computer_power(self, computer_name: str) -> dict:
        """
        Return dictionary that contains informations about power if this information is present in the device config file

        Return None if this information isn't stored in device's config file
        Args:
            device_name (str): name of the device

        Returns:
            dict: informations about connector of device
        """
        return self.get_computers()[computer_name]["computer_config"].get("power")

0318e3c9   Alexis Koralewski   Add tests for F05...
1027
1028
1029
    def getDeviceControllerNameForAgent(self, unit_name: str, agent_name: str) -> tuple:
        agent = self.get_agent_information(unit_name, agent_name)
        return (agent["device"], "DeviceController"+agent["device"])
a2dbbde1   Alexis Koralewski   Adding new config...
1030

0318e3c9   Alexis Koralewski   Add tests for F05...
1031
    def getDeviceConfigForDeviceController(self, device_name: str) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
1032
1033
        return self.get_devices()[device_name]["device_config"]

0318e3c9   Alexis Koralewski   Add tests for F05...
1034
1035
1036
1037
1038
    def getCommParamsForAgentDevice(self, unit_name: str, agent_name: str) -> tuple:
        agent = self.get_agent_information(unit_name, agent_name)
        device_config = self.getDeviceConfigForDeviceController(
            agent["device"])
        comm_access = agent.get("comm_access", None)
a2dbbde1   Alexis Koralewski   Adding new config...
1039
        comm = device_config["comm"]
0318e3c9   Alexis Koralewski   Add tests for F05...
1040
        return (comm_access, comm)
a2dbbde1   Alexis Koralewski   Adding new config...
1041

0318e3c9   Alexis Koralewski   Add tests for F05...
1042
1043
    def getChannelCapabilities(self, unit_name: str, channel_name: str) -> list:
        channel = self.get_channel_information(unit_name, channel_name)
a2dbbde1   Alexis Koralewski   Adding new config...
1044
1045
1046
1047
        result = []
        for component_agent in channel["COMPONENT_AGENTS"]:
            component = list(component_agent.keys())[0]
            agent = component_agent[component]
0318e3c9   Alexis Koralewski   Add tests for F05...
1048
            device = self.getDeviceControllerNameForAgent(unit_name, agent)[0]
a2dbbde1   Alexis Koralewski   Adding new config...
1049
1050
1051
1052
1053
1054
            device_capabilities = self.get_device_capabilities(device)
            for capability in device_capabilities:
                if capability["component"] == component:
                    result.append(capability)
        return result

0318e3c9   Alexis Koralewski   Add tests for F05...
1055
    def getEditableAttributesOfCapability(self, capability: dict) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
1056
1057
1058
1059
1060
1061
1062
        editable_fields = {}
        attributes = capability.get("attributes")
        for attribute in attributes:
            if attributes[attribute]["is_editable"]:
                editable_fields[attribute] = attributes[attribute]
        return editable_fields

0318e3c9   Alexis Koralewski   Add tests for F05...
1063
    def getUneditableAttributesOfCapability(self, capability: dict) -> dict:
a2dbbde1   Alexis Koralewski   Adding new config...
1064
1065
1066
1067
1068
1069
1070
        uneditable_fields = {}
        attributes = capability.get("attributes")
        for attribute in attributes:
            if attributes[attribute]["is_editable"] == False:
                uneditable_fields[attribute] = attributes[attribute]
        return uneditable_fields

0318e3c9   Alexis Koralewski   Add tests for F05...
1071
1072
    def getEditableAttributesOfChannel(self, unit_name: str, channel_name: str) -> list:
        capabilities = self.getChannelCapabilities(unit_name, channel_name)
a2dbbde1   Alexis Koralewski   Adding new config...
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
        # merged_result = {}
        # for capability in capabilities:
        #     merged_result[capability["component"]] = self.getEditableAttributesOfCapability(capability)
        # return merged_result
        merged_result = []
        for capability in capabilities:
            attributes = self.getEditableAttributesOfCapability(capability)
            if len(attributes.keys()) > 0:
                merged_result.append(attributes)
        return merged_result

0318e3c9   Alexis Koralewski   Add tests for F05...
1084
1085
    def getUneditableAttributesOfChannel(self, unit_name: str, channel_name: str) -> list:
        capabilities = self.getChannelCapabilities(unit_name, channel_name)
a2dbbde1   Alexis Koralewski   Adding new config...
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
        # merged_result = {}
        # for capability in capabilities:
        #     merged_result[capability["component"]] = self.getEditableAttributesOfCapability(capability)
        # return merged_result
        merged_result = []
        for capability in capabilities:
            attributes = self.getUneditableAttributesOfCapability(capability)
            if len(attributes.keys()) > 0:
                merged_result.append(attributes)
        return merged_result

a2dbbde1   Alexis Koralewski   Adding new config...
1097
1098
1099
    # def getLogicOfChannelGroups(self,unit_name):
    #     return self.get_layouts(unit_name)["global_group_logic"]

0318e3c9   Alexis Koralewski   Add tests for F05...
1100
    def getLayoutByName(self, unit_name: str, name_of_layout):
a2dbbde1   Alexis Koralewski   Adding new config...
1101
1102
        return self.get_layouts(unit_name)["layouts"][name_of_layout]

0318e3c9   Alexis Koralewski   Add tests for F05...
1103
    def getAlbumByName(self, unit_name: str, name_of_album):
a2dbbde1   Alexis Koralewski   Adding new config...
1104
1105
        return self.get_albums(unit_name)["albums"][name_of_album]

0318e3c9   Alexis Koralewski   Add tests for F05...
1106
1107
1108
    def getEditableAttributesOfMount(self, unit_name):
        capabilities = self.get_device_capabilities(
            self.get_device_for_agent(unit_name, "mount")["name"])
a2dbbde1   Alexis Koralewski   Adding new config...
1109
1110
1111
1112
1113
1114
        merged_result = []
        for capability in capabilities:
            attributes = self.getEditableAttributesOfCapability(capability)
            if len(attributes.keys()) > 0:
                merged_result.append(attributes)
        return merged_result
0318e3c9   Alexis Koralewski   Add tests for F05...
1115

a2dbbde1   Alexis Koralewski   Adding new config...
1116
1117
1118
1119
    def getHorizonLine(self, unit_name):
        horizon = self.get_unit_by_name(unit_name).get("horizon")
        return horizon.get("line")

820e5db5   Alexis Koralewski   Adding new method...
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
    def getHome(self):
        """
        Return home of current unit

        Returns:
            str: string reprensenting home of unit
        """
        home = self.get_unit_by_name(self.unit_name).get("home")
        return home

    def get_agent_path_data_root(self, agent_name:str):
        """
        Return agent path_data_root

        Args:
            agent_name (str): _description_

        Returns:
            str|None: String reprensenting path_data_root
        """
        agent = self.get_agent_information(self.unit_name, agent_name)
        path_data_root = agent.get(path_data_root,None)
        return path_data_root
0318e3c9   Alexis Koralewski   Add tests for F05...
1143

2d10959d   Alexis Koralewski   Adding mandatory ...
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
class MissingMandatoryAgentException(Exception):
    """
    Exception raised when an mandatory Pyros Agent is missing in the observatory configuration.
    """

    def __init__(self, agent_name):
        message = f"Missing agent {agent_name} in observatory config. Please add an Agent/AgentDevice section in your observatory configuration file with the agent name {agent_name}."
        super().__init__(message)


a2dbbde1   Alexis Koralewski   Adding new config...
1154
1155
1156
1157
def main():
    # config = ConfigPyros("../../../../privatedev/config/guitalens/observatory_guitalens.yml")
    # unit_name = config.get_units_name()[0]
    # dc = config.getDeviceControllerNameForAgent(unit_name,"mount")[0]
0318e3c9   Alexis Koralewski   Add tests for F05...
1158
1159
    # print(config.getDeviceConfigForDeviceController(dc))
    # print(config.getCommParamsForAgentDevice(unit_name,"mount"))
a2dbbde1   Alexis Koralewski   Adding new config...
1160
1161
1162
1163
1164
1165
1166
    # print(config.getChannelCapabilities(unit_name,"OpticalChannel_up"))
    # print(config.get_channel_groups(unit_name))
    # print(config.getEditableAttributesOfCapability(config.getChannelCapabilities(unit_name,"OpticalChannel_up")[0]))
    # print(config.getEditableAttributesOfChannel(unit_name,"OpticalChannel_up"))
    config = OBSConfig("../../../../privatedev/config/tnc/observatory_tnc.yml")
    unit_name = config.get_units_name()[0]
    #dc = config.getDeviceControllerNameForAgent(unit_name,"mount")[0]
0318e3c9   Alexis Koralewski   Add tests for F05...
1167
1168
    # print(config.getDeviceConfigForDeviceController(dc))
    # print(config.getCommParamsForAgentDevice(unit_name,"mount"))
a2dbbde1   Alexis Koralewski   Adding new config...
1169
1170
1171
1172
1173
    # print(config.getChannelCapabilities(unit_name,"OpticalChannel_down2"))
    # print(config.get_channel_groups(unit_name))
    # print(config.getEditableAttributesOfCapability(config.getChannelCapabilities(unit_name,"OpticalChannel_down2")[0]))
    # print(config.getEditableAttributesOfChannel(unit_name,"OpticalChannel_down2"))
    print(config.getEditableAttributesOfMount(unit_name))
a2dbbde1   Alexis Koralewski   Adding new config...
1174

0318e3c9   Alexis Koralewski   Add tests for F05...
1175
1176
1177
1178
1179
1180
1181
    # print(config.get_devices()["FLI-Kepler4040"]["device_config"])
    # print(config.get_devices()["FLI-Kepler4040"]["device_config"]["CAPABILITIES"][1]["attributes"]["manufacturer"])
    # print(config.get_devices()["FLI-Kepler4040"]["device_config"]["CAPABILITIES"])
    # print(config.get_devices()["AstroMecCA-TM350"]["device_config"]["CAPABILITIES"])
if __name__ == "__main__":

    main()