Commit b4d06c1f6b97ce9d8984e001cde3f5f159897ee8

Authored by Alexis Koralewski
1 parent be09bdbc
Exists in dev

Improving device configuration inheritance of an generic configuration (add capa…

…bilities inheritance), on hardware configuration page now display if device is an attached device
src/core/pyros_django/obsconfig/configpyros.py
... ... @@ -175,25 +175,26 @@ class ConfigPyros:
175 175  
176 176 def read_capability_of_device(self,capability:dict)->dict:
177 177 """
178   -
  178 + Read capability of device and inherit attributes from generic component then overwrite attributes defined in device config
179 179  
180 180 Args:
181   - dict ([type]): [description]
  181 + capability (dict): dictionary containing a capabilitiy (keys : component and attributes)
182 182  
183 183 Returns:
184   - dict: [description]
  184 + dict: dictionary of capability inherited by generic component and overwritten his attributes by current attributes of capability
185 185 """
186 186  
187 187 component_attributes = self.read_generic_component_and_return_attributes(capability["component"])
188 188  
189 189 attributes = {}
  190 + # get all attributes of device's capability
190 191 for attribute in capability["attributes"]:
191 192 attribute = attribute["attribute"]
192 193  
193 194 attributes[attribute.pop("key")] = attribute
194 195  
  196 + # for each attributes of generic component attributes
195 197 for attribute_name in attributes.keys():
196   - # merging values from general component to specific values of component in config file
197 198 if "is_Enum" in component_attributes[attribute_name].keys():
198 199 # check if value of this attribute is in Enum of general component attribute definition
199 200 enum_values = component_attributes[attribute_name]["value"]
... ... @@ -207,6 +208,7 @@ class ConfigPyros:
207 208 new_attributes = {**component_attributes[attribute_name],**attributes[attribute_name]}
208 209 component_attributes[attribute_name] = new_attributes
209 210  
  211 + # return inherited and overwritten attributes of capability
210 212 capability["attributes"] = component_attributes
211 213 return capability
212 214  
... ... @@ -215,9 +217,9 @@ class ConfigPyros:
215 217 def get_devices_names_and_file(self)->dict:
216 218 """
217 219  
218   -
  220 + Return a dictionary giving the device file name by the device name
219 221 Returns:
220   - dict: [description]
  222 + dict: key is device name, value is file name
221 223 """
222 224 devices_names_and_files = {}
223 225 for device in self.obs_config["OBSERVATORY"]["DEVICES"]:
... ... @@ -228,74 +230,116 @@ class ConfigPyros:
228 230  
229 231 def read_device_config_file(self,config_file_name:str,is_generic=False)->dict:
230 232 """
231   -
  233 + Read the device config file, inherit generic device config if "generic" key is present in "DEVICE".
  234 + Associate capabilities of attached_devices if this device has attached_devices.
  235 + Inherit capabilities from generic component and overwritte attributes defined in the device config
232 236 Args:
233   - config_file_name (str): [description]
  237 + config_file_name (str): file name to be read
  238 + is_generic (bool, optional): tells if we're reading a generic configuration (is_generic =True) or not (is_generic = False). Defaults to False.
234 239  
235 240 Returns:
236   - dict: [description]
  241 + dict: formatted device configuration (attributes, capabilities...)
237 242 """
238 243 self.current_file = config_file_name
239 244 print(f"Reading {config_file_name}")
240 245 try:
241 246 with open(config_file_name, 'r') as stream:
242 247 config_file = yaml.safe_load(stream)
  248 + # if we're reading a generic device configuration, the path to get the schema is different than usual
243 249 if is_generic:
244 250 self.SCHEMA_PATH = os.path.join(os.path.abspath(os.path.dirname(config_file_name)),"../schemas/")
245 251 else:
246 252 self.SCHEMA_PATH = os.path.join(os.path.abspath(os.path.dirname(config_file_name)),"../../../config/schemas/")
247 253  
  254 + # read and verify that the device configuration match the schema
248 255 result = self.check_and_return_config(config_file_name,self.SCHEMA_PATH+config_file["schema"])
  256 + # if the configuration didn't match the schema or had an error when reading the file
249 257 if result == None:
250 258 print("Error when reading and validating config file, please check the errors right above")
251 259 exit(-1)
252 260 else:
  261 + # the configuration is valid
  262 + # storing DEVICE key in device (DEVICE can contains : attributes of device, capabilities, attached devices)
253 263 device = result["DEVICE"]
254   -
  264 + generic_device_config = None
  265 + # if the device is associated to an generic configuration, we'll read that generic configuration to inherit his attributes
255 266 if "generic" in device:
  267 + # storing the whole current config
256 268 current_config = result
257   - generic_device_config = self.read_device_config_file(self.GENERIC_DEVICES_PATH+device["generic"],True)
258   -
  269 + # read and get the generic device config
  270 + generic_device_config = self.read_device_config_file(self.GENERIC_DEVICES_PATH+device["generic"],is_generic=True)
  271 + # merge whole device config but we need to merge capabilities differently after
259 272 new_config = {**generic_device_config["DEVICE"],**current_config["DEVICE"]}
260   -
261 273 result["DEVICE"] = new_config
262 274  
  275 + # device has capabilities
263 276 if "CAPABILITIES" in device:
264 277 capabilities = []
265   - for capability in device["CAPABILITIES"]:
266   - capability = capability["CAPABILITY"]
267   -
268   - capabilities.append(self.read_capability_of_device(capability))
269   -
270   -
  278 + # if the device is associated to a generic device we need to associate his capabilities with the generic capabilities and overwrite them
  279 + if generic_device_config != None:
  280 + # we're making a copy of generic device config so we can remove items during loop
  281 + copy_generic_device_config = generic_device_config.copy()
  282 + # We have to extend capabilities of generic device configuration's capabilities
  283 + for capability in current_config["DEVICE"]["CAPABILITIES"]:
  284 + is_capability_in_generic_config = False
  285 + current_config_capability = capability["CAPABILITY"]
  286 + current_config_component = current_config_capability["component"]
  287 + current_config_capability = self.read_capability_of_device(current_config_capability)
  288 + # find if this component was defined in generic_device_config
  289 + for index,generic_config_capability in enumerate(generic_device_config["DEVICE"]["CAPABILITIES"]):
  290 + # if the current capability is the capability of the component we're looking for
  291 + if current_config_component == generic_config_capability["component"]:
  292 + is_capability_in_generic_config = True
  293 + # we're merging their attributes
  294 + merged_attributes = {**generic_config_capability["attributes"],**current_config_capability["attributes"]}
  295 + # removing this capability from generic device configuration
  296 + generic_device_config["DEVICE"]["CAPABILITIES"].pop(index)
  297 + capabilities.append({"component": current_config_component,"attributes":merged_attributes})
  298 + break
  299 + if is_capability_in_generic_config == False:
  300 + # 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
  301 + capabilities.append(current_config_capability)
  302 + # looping through generic device config's capabilities in order to add them to current device configuration
  303 + for generic_config_capability in generic_device_config["DEVICE"]["CAPABILITIES"]:
  304 + capabilities.append(generic_config_capability)
  305 + else:
  306 + # device not associated to a generic device configuration
  307 + for capability in device["CAPABILITIES"]:
  308 + capability = capability["CAPABILITY"]
  309 + capabilities.append(self.read_capability_of_device(capability))
271 310  
272 311 device["CAPABILITIES"] = capabilities
  312 + # associate capabilities to final device configuration (stored in result variable)
  313 + result["DEVICE"]["CAPABILITIES"] = device["CAPABILITIES"]
273 314 if "ATTACHED_DEVICES" in device.keys():
274   -
  315 + # device has attached devices, we need to read their configuration in order to get their capabilities and add them to the current device
275 316 devices_name_and_file = self.get_devices_names_and_file()
276 317 active_devices = self.get_active_devices()
277 318 for attached_device in device["ATTACHED_DEVICES"]:
278 319 is_attached_device_link_to_agent = False
  320 + # 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
279 321 for active_device in active_devices:
280 322 if devices_name_and_file[active_device] == attached_device["file"]:
  323 + # the attached device is an active device (so it's linked to an agent)
281 324 is_attached_device_link_to_agent = True
282 325 break
283   -
284 326 if self.CONFIG_PATH+attached_device["file"] != config_file_name and not is_attached_device_link_to_agent:
  327 + # if the attached device isn't the device itself and not active
285 328  
  329 + # get configuration of attached device
286 330 config_of_attached_device = self.read_device_config_file(self.CONFIG_PATH+attached_device["file"])
287   -
288 331 capabilities_of_attached_device = None
289 332 if "CAPABILITIES" in config_of_attached_device["DEVICE"].keys():
290 333 capabilities_of_attached_device = config_of_attached_device["DEVICE"]["CAPABILITIES"]
291 334 if capabilities_of_attached_device != None:
292 335 # get name of device corresponding to the config file name
293   - parent_device_name = [device for device,file_name in devices_name_and_file.items() if file_name == config_file_name[len(self.CONFIG_PATH):] ][0]
294   -
  336 + parent_device_name = [device for device,file_name in devices_name_and_file.items() if file_name == config_file_name[len(self.CONFIG_PATH):]][0]
295 337 attached_device_name = [device for device,file_name in devices_name_and_file.items() if file_name == attached_device["file"]][0]
296 338  
  339 + # associate attached device to his 'parent' device (parent device is the currently read device)
297 340 self.devices_links[attached_device_name] = parent_device_name
298 341 for capability in capabilities_of_attached_device:
  342 + # add capabilities of attached device to current device
299 343 result["DEVICE"]["CAPABILITIES"].append(capability)
300 344 return result
301 345 except yaml.YAMLError as exc:
... ...
src/core/pyros_django/obsconfig/templates/obsconfig/obs_hardware_configuration.html
... ... @@ -21,8 +21,12 @@
21 21 <tr>
22 22  
23 23  
24   -
25   - <td> <a href="{% url 'device_details' device %}">{{ device }} </td>
  24 +
  25 + {% if devices_links|get_item:device in active_devices %}
  26 + <td> <a href="{% url 'device_details' device %}">{{ device }} (attached device)</td>
  27 + {% else %}
  28 + <td> <a href="{% url 'device_details' device %}">{{ device }} </td>
  29 + {% endif %}
26 30 <td> {{ devices|get_item:device|get_item:"device_config"|get_item:"description" }} </td>
27 31 {% if device in active_devices %}
28 32 <td class="in_use"> Yes </td>
... ...