def _execute_register(device: dict, created: str, log: list): """ Tries to POST the device and updates the `device` dict with the resource from the database; if the device could not be uploaded the `device` param will contain the database version of the device, not the inputting one. This is because the majority of the information of a device is immutable (in concrete the fields used to compute the ETAG). :param device: Inputting device. It is replaced (keeping the reference) with the db version. :param created: Set the _created value to be the same for the device as for the register :param log: A log where to append the resulting device if execute_register has been successful :raise InnerRequestError: any internal error in the POST that is not about the device already existing. """ device['hid'] = 'dummy' new = True try: if created: device['created'] = created db_device = execute_post_internal(Naming.resource(device['@type']), device) except InnerRequestError as e: new = False try: db_device = _get_existing_device(e) # We add a benchmark todo move to another place? device['_id'] = db_device['_id'] ComponentDomain.benchmark(device) except DeviceNotFound: raise e else: log.append(db_device) device.clear() device.update(db_device) device['new'] = new # Note that the device is 'cleared' before return db_device
def update_devices(original: set, updated: set, replaced_place_id: str or None): """ Given the original set of devices of a place, and the updated version, it updates the database to achieve the updated set of devices. :param original: :param updated: :param replaced_place_id: The identifier of the replaced place. If updated is empty, this value is not used. :return: """ devices_to_remove_id = original - updated children_to_remove_id = ComponentDomain.get_components_in_set( list(devices_to_remove_id)) devices_to_remove_id |= children_to_remove_id devices_to_add_id = updated - original children_to_add_id = ComponentDomain.get_components_in_set( list(devices_to_add_id)) devices_to_add_id |= children_to_add_id for device_id in devices_to_remove_id: PlaceDomain.device_unset_place(device_id) for device_id in devices_to_add_id: PlaceDomain.device_set_place(device_id, replaced_place_id)
def inherit_place(place_id: ObjectId, device_id: str or ObjectId, components: list): """Copies the place from the parent device to the new components and materializes them in the place""" ComponentDomain.update_raw(components, {'$set': {'place': place_id}}) PlaceDomain.update_one_raw(place_id, {'$addToSet': { 'components': components }})
def _execute_register(device: dict, created: str, log: list): """ Tries to POST the device and updates the `device` dict with the resource from the database; if the device could not be uploaded the `device` param will contain the database version of the device, not the inputting one. This is because the majority of the information of a device is immutable (in concrete the fields used to compute the ETAG). :param device: Inputting device. It is replaced (keeping the reference) with the db version. :param created: Set the _created value to be the same for the device as for the register :param log: A log where to append the resulting device if execute_register has been successful :raise InnerRequestError: any internal error in the POST that is not about the device already existing. """ new = True try: if created: device['created'] = created db_device = execute_post_internal(Naming.resource(device['@type']), device) except InnerRequestError as e: new = False try: db_device = _get_existing_device(e) # We add a benchmark todo move to another place? device['_id'] = db_device['_id'] ComponentDomain.benchmark(device) external_synthetic_id_fields = pick( device, *DeviceDomain.external_synthetic_ids) # If the db_device was a placeholder # We want to override it with the new device if db_device.get('placeholder', False): # Eve do not generate defaults from sub-resources # And we really need the placeholder default set, specially when # discovering a device device['placeholder'] = False # We create hid when we validate (wrong thing) so we need to manually set it here as we won't # validate in this db operation device['hid'] = DeviceDomain.hid(device['manufacturer'], device['serialNumber'], device['model']) DeviceDomain.update_one_raw(db_device['_id'], {'$set': device}) elif not is_empty(external_synthetic_id_fields): # External Synthetic identifiers are not intrinsically inherent # of devices, and thus can be added later in other Snapshots # Note that the device / post and _get_existing_device() have already validated those ids DeviceDomain.update_one_raw( db_device['_id'], {'$set': external_synthetic_id_fields}) except DeviceNotFound: raise e else: log.append(db_device) device.clear() device.update(db_device) device['new'] = new # Note that the device is 'cleared' before return db_device
def _remove_nonexistent_components(self): """ This function needs to be executed at the end, after all components are in the DB and have _id In this stage, we have already added the components to the parent device. In the materialized components field of device we still have the old components. We need to remove those that are not present in this new snapshot. """ if 'components' in self.device: full_old_components = [ComponentDomain.get_one(component) for component in self.device['components']] for component_to_remove in ComponentDomain.difference(full_old_components, self.components): self.events.append_remove(component_to_remove, self.device)
def _remove_nonexistent_components(self): """ This function needs to be executed at the end, after all components are in the DB and have _id In this stage, we have already added the components to the parent device. In the materialized components field of device we still have the old components. We need to remove those that are not present in this new snapshot. """ if 'components' in self.device: full_old_components = [ ComponentDomain.get_one(component) for component in self.device['components'] ] for component_to_remove in ComponentDomain.difference( full_old_components, self.components): self.events.append_remove(component_to_remove, self.device)
def _validate_type_hid(self, field, value): """ General validation for inserting devices (the name of the function is forced by Cerberus, not a good one). - Tries to create hid and validates it. - If hid cannot be created: - If it has a parent, ensures that the device is unique. - If it has not a parent, validates that the device has an user provided _id. """ from ereuse_devicehub.resources.device.component.domain import ComponentDomain from ereuse_devicehub.resources.device.exceptions import DeviceNotFound try: self.document['hid'] = Naming.url_word(self.document['manufacturer']) + \ '-' + Naming.url_word(self.document['serialNumber']) + \ '-' + Naming.url_word(self.document['model']) except KeyError: del self.document['hid'] self.document['isUidSecured'] = False if '_id' not in self.document: # We do not validate here the unique constraint of _id if 'parent' in self.document: with suppress(KeyError, DeviceNotFound): component = ComponentDomain.get_similar_component(self.document, self.document['parent']) self._error('model', json_util.dumps({'NotUnique': component})) else: # If device has no parent and no hid, user needs to: or provide _id or forcing creating it if 'forceCreation' not in self.document or not self.document['forceCreation']: self._error('_id', json_util.dumps({'NeedsId': self.document})) # else: user forces us to create the device, it will be assigned an _id # else: user provided _id. We accept this, however is unsecured. else: self._validate_regex(HID_REGEX, field, self.document['hid']) self._validate_unique(True, field, self.document['hid'])
def materialize_parent(resource_name: str, events: list): """ Materializes the field 'parent' of events that only affect components (such as TestHardDrive or EraseBasic) :param resource_name: :param events: :return: """ if resource_name in Event.resource_types: for event in events: sub_schema = current_app.config["DOMAIN"][resource_name]["schema"] if "parent" in sub_schema: event["parent"] = ComponentDomain.get_parent(event["device"])["_id"]
def materialize_parent(resource_name: str, events: list): """ Materializes the field 'parent' of events that only affect components (such as TestHardDrive or EraseBasic) :param resource_name: :param events: :return: """ if resource_name in Event.resource_types: for event in events: sub_schema = current_app.config['DOMAIN'][resource_name]['schema'] if 'parent' in sub_schema: event['parent'] = ComponentDomain.get_parent(event['device'])['_id']
def materialize_components(resource_name: str, events: list): """ Materializes the field 'components' of selected events (not all of them) with the union of all the affected components, when the event is performed to computers :param resource_name: :param events: :return: """ if resource_name in Event.resource_types: for event in events: sub_schema = current_app.config["DOMAIN"][resource_name]["schema"] if "components" in sub_schema and sub_schema["components"].get("readonly", False): event["components"] = list(ComponentDomain.get_components_in_set(event["devices"]))
def update_devices(original: set, updated: set, replaced_place_id: str or None): """ Given the original set of devices of a place, and the updated version, it updates the database to achieve the updated set of devices. :param original: :param updated: :param replaced_place_id: The identifier of the replaced place. If updated is empty, this value is not used. :return: """ devices_to_remove_id = original - updated children_to_remove_id = ComponentDomain.get_components_in_set(list(devices_to_remove_id)) devices_to_remove_id |= children_to_remove_id devices_to_add_id = updated - original children_to_add_id = ComponentDomain.get_components_in_set(list(devices_to_add_id)) devices_to_add_id |= children_to_add_id for device_id in devices_to_remove_id: PlaceDomain.device_unset_place(device_id) for device_id in devices_to_add_id: PlaceDomain.device_set_place(device_id, replaced_place_id)
def materialize_components(resource_name: str, events: list): """ Materializes the field 'components' of selected events (not all of them) with the union of all the affected components, when the event is performed to computers :param resource_name: :param events: :return: """ if resource_name in Event.resource_types: for event in events: sub_schema = current_app.config['DOMAIN'][resource_name]['schema'] if 'components' in sub_schema and sub_schema['components'].get('readonly', False): event['components'] = list(ComponentDomain.get_components_in_set(event['devices']))
def get_add_remove(self, device: dict, new_parent: dict): """ Get the changes (events) that will need to be triggered for the given device. Changes will we saved in the same device in the reserved key '_change'. The function doesn't execute any event per se or validate that the user can do it. :param device: The device must have an hid :param new_parent: """ if not device['new']: try: old_parent = ComponentDomain.get_parent(device['_id']) except DeviceNotFound: # The component exists but had no parent device, until now self.events.append_add(device, new_parent) else: if not DeviceDomain.seem_equal(old_parent, new_parent): self.events.append_remove(device, old_parent) self.events.append_add(device, new_parent)
def _validate_type_hid(self, field, _): """ General validation for inserting devices (the name of the function is forced by Cerberus, not a good one). - Tries to create hid and validates it. - If hid cannot be created: - If it has a parent, ensures that the device is unique. - If it has not a parent, validates that the device has an user provided _id. """ from ereuse_devicehub.resources.device.component.domain import ComponentDomain from ereuse_devicehub.resources.device.exceptions import DeviceNotFound try: from ereuse_devicehub.resources.device.domain import DeviceDomain # todo this should not be done in the validation. Prove of this is that it needs to be done in # register/hooks again for placeholders self.document['hid'] = DeviceDomain.hid( self.document['manufacturer'], self.document['serialNumber'], self.document['model']) except KeyError: self.document['isUidSecured'] = False if '_id' not in self.document: # We do not validate here the unique constraint of _id if 'parent' in self.document: with suppress(KeyError, DeviceNotFound): component = ComponentDomain.get_similar_component( self.document, self.document['parent']) self._error('model', json_util.dumps({'NotUnique': component})) else: # If device has no parent and no hid, user needs to: or provide _id or forcing creating it if 'forceCreation' not in self.document or not self.document[ 'forceCreation']: self._error( '_id', json_util.dumps({'NeedsId': self.document})) # else: user forces us to create the device, it will be assigned an _id # else: user provided _id. We accept this, however is unsecured. else: from ereuse_devicehub.resources.device.settings import HID_REGEX self._validate_regex(HID_REGEX, field, self.document['hid']) self._validate_unique(True, field, self.document['hid'])
def update_materialized_computer(device_id: str, components_id: list, add: bool = True): update_query = {} inc = defaultdict(int) total_types = {RamModule.type_name, HardDrive.type_name} for component in ComponentDomain.get({'_id': {'$in': components_id}}): _type = component['@type'] if _type in total_types: inc[_type] += component.get('size', 0) elif _type == Processor.type_name and 'model' in component: update_query['$set' if add else '$unset'] = {'processorModel': component['model']} sign = 1 if add else -1 # We will add or subtract values depending if adding or removing components update_query['$inc'] = { 'totalRamSize': sign * inc[RamModule.type_name], 'totalHardDriveSize': sign * inc[HardDrive.type_name] } if add: component_query = {'$addToSet': {'components': {'$each': components_id}}} else: component_query = {'$pull': {'components': {'$in': components_id}}} update_query.update(component_query) current_app.data.driver.db['devices'].update({'_id': device_id}, update_query)