add system_uuid to sync run function

This commit is contained in:
Cayo Puigdefabregas 2022-06-13 17:33:22 +02:00
parent f86df91862
commit 686d0a9307

View file

@ -13,10 +13,14 @@ from teal.marshmallow import ValidationError
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.action.models import Remove from ereuse_devicehub.resources.action.models import Remove
from ereuse_devicehub.resources.device.models import Component, Computer, Device, DataStorage from ereuse_devicehub.resources.device.models import (
Component,
Computer,
DataStorage,
Device,
)
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
DEVICES_ALLOW_DUPLICITY = [ DEVICES_ALLOW_DUPLICITY = [
'RamModule', 'RamModule',
'Display', 'Display',
@ -26,12 +30,13 @@ DEVICES_ALLOW_DUPLICITY = [
'GraphicCard', 'GraphicCard',
] ]
class Sync: class Sync:
"""Synchronizes the device and components with the database.""" """Synchronizes the device and components with the database."""
def run(self, def run(
device: Device, self, device: Device, components: Iterable[Component] or None
components: Iterable[Component] or None) -> (Device, OrderedSet): ) -> (Device, OrderedSet):
"""Synchronizes the device and components with the database. """Synchronizes the device and components with the database.
Identifies if the device and components exist in the database Identifies if the device and components exist in the database
@ -72,9 +77,9 @@ class Sync:
blacklist = set() # type: Set[int] blacklist = set() # type: Set[int]
not_new_components = set() not_new_components = set()
for component in components: for component in components:
db_component, is_new = self.execute_register_component(component, db_component, is_new = self.execute_register_component(
blacklist, component, blacklist, parent=db_device
parent=db_device) )
db_components.add(db_component) db_components.add(db_component)
if not is_new: if not is_new:
not_new_components.add(db_component) not_new_components.add(db_component)
@ -83,10 +88,9 @@ class Sync:
db_device.components = db_components db_device.components = db_components
return db_device, actions return db_device, actions
def execute_register_component(self, def execute_register_component(
component: Component, self, component: Component, blacklist: Set[int], parent: Computer
blacklist: Set[int], ):
parent: Computer):
"""Synchronizes one component to the DB. """Synchronizes one component to the DB.
This method is a specialization of :meth:`.execute_register` This method is a specialization of :meth:`.execute_register`
@ -118,9 +122,12 @@ class Sync:
# if not, then continue with the traditional behaviour # if not, then continue with the traditional behaviour
try: try:
if component.hid: if component.hid:
db_component = Device.query.filter_by(hid=component.hid, owner_id=g.user.id).one() db_component = Device.query.filter_by(
assert isinstance(db_component, Device), \ hid=component.hid, owner_id=g.user.id
'{} must be a component'.format(db_component) ).one()
assert isinstance(
db_component, Device
), '{} must be a component'.format(db_component)
else: else:
# Is there a component similar to ours? # Is there a component similar to ours?
db_component = component.similar_one(parent, blacklist) db_component = component.similar_one(parent, blacklist)
@ -166,15 +173,31 @@ class Sync:
:return: The synced device from the db with the tags linked. :return: The synced device from the db with the tags linked.
""" """
assert inspect(device).transient, 'Device cannot be already synced from DB' assert inspect(device).transient, 'Device cannot be already synced from DB'
assert all(inspect(tag).transient for tag in device.tags), 'Tags cannot be synced from DB' assert all(
inspect(tag).transient for tag in device.tags
), 'Tags cannot be synced from DB'
db_device = None db_device = None
if device.hid: if isinstance(db_device, Computer):
if db_device.uuid:
db_device = Computer.query.filter_by(
uuid=device.uuid, owner_id=g.user.id, active=True
).one()
elif device.hid:
with suppress(ResourceNotFound):
db_device = Device.query.filter_by(
hid=device.hid, owner_id=g.user.id, active=True
).one()
elif device.hid:
with suppress(ResourceNotFound): with suppress(ResourceNotFound):
db_device = Device.query.filter_by(hid=device.hid, owner_id=g.user.id, active=True).one() db_device = Device.query.filter_by(
hid=device.hid, owner_id=g.user.id, active=True
).one()
if db_device and db_device.allocated: if db_device and db_device.allocated:
raise ResourceNotFound('device is actually allocated {}'.format(device)) raise ResourceNotFound('device is actually allocated {}'.format(device))
try: try:
tags = {Tag.from_an_id(tag.id).one() for tag in device.tags} # type: Set[Tag] tags = {
Tag.from_an_id(tag.id).one() for tag in device.tags
} # type: Set[Tag]
except ResourceNotFound: except ResourceNotFound:
raise ResourceNotFound('tag you are linking to device {}'.format(device)) raise ResourceNotFound('tag you are linking to device {}'.format(device))
linked_tags = {tag for tag in tags if tag.device_id} # type: Set[Tag] linked_tags = {tag for tag in tags if tag.device_id} # type: Set[Tag]
@ -182,16 +205,22 @@ class Sync:
sample_tag = next(iter(linked_tags)) sample_tag = next(iter(linked_tags))
for tag in linked_tags: for tag in linked_tags:
if tag.device_id != sample_tag.device_id: if tag.device_id != sample_tag.device_id:
raise MismatchBetweenTags(tag, sample_tag) # Tags linked to different devices raise MismatchBetweenTags(
tag, sample_tag
) # Tags linked to different devices
if db_device: # Device from hid if db_device: # Device from hid
if sample_tag.device_id != db_device.id: # Device from hid != device from tags if (
sample_tag.device_id != db_device.id
): # Device from hid != device from tags
raise MismatchBetweenTagsAndHid(db_device.id, db_device.hid) raise MismatchBetweenTagsAndHid(db_device.id, db_device.hid)
else: # There was no device from hid else: # There was no device from hid
if sample_tag.device.physical_properties != device.physical_properties: if sample_tag.device.physical_properties != device.physical_properties:
# Incoming physical props of device != props from tag's device # Incoming physical props of device != props from tag's device
# which means that the devices are not the same # which means that the devices are not the same
raise MismatchBetweenProperties(sample_tag.device.physical_properties, raise MismatchBetweenProperties(
device.physical_properties) sample_tag.device.physical_properties,
device.physical_properties,
)
db_device = sample_tag.device db_device = sample_tag.device
if db_device: # Device from hid or tags if db_device: # Device from hid or tags
self.merge(device, db_device) self.merge(device, db_device)
@ -199,17 +228,21 @@ class Sync:
device.tags.clear() # We don't want to add the transient dummy tags device.tags.clear() # We don't want to add the transient dummy tags
db.session.add(device) db.session.add(device)
db_device = device db_device = device
db_device.tags |= tags # Union of tags the device had plus the (potentially) new ones db_device.tags |= (
tags # Union of tags the device had plus the (potentially) new ones
)
try: try:
db.session.flush() db.session.flush()
except IntegrityError as e: except IntegrityError as e:
# Manage 'one tag per organization' unique constraint # Manage 'one tag per organization' unique constraint
if 'One tag per organization' in e.args[0]: if 'One tag per organization' in e.args[0]:
# todo test for this # todo test for this
id = int(e.args[0][135:e.args[0].index(',', 135)]) id = int(e.args[0][135 : e.args[0].index(',', 135)])
raise ValidationError('The device is already linked to tag {} ' raise ValidationError(
'from the same organization.'.format(id), 'The device is already linked to tag {} '
field_names=['device.tags']) 'from the same organization.'.format(id),
field_names=['device.tags'],
)
else: else:
raise raise
assert db_device is not None assert db_device is not None
@ -222,15 +255,14 @@ class Sync:
This method mutates db_device. This method mutates db_device.
""" """
if db_device.owner_id != g.user.id: if db_device.owner_id != g.user.id:
return return
for field_name, value in device.physical_properties.items(): for field_name, value in device.physical_properties.items():
if value is not None: if value is not None:
setattr(db_device, field_name, value) setattr(db_device, field_name, value)
@staticmethod @staticmethod
def add_remove(device: Computer, def add_remove(device: Computer, components: Set[Component]) -> OrderedSet:
components: Set[Component]) -> OrderedSet:
"""Generates the Add and Remove actions (but doesn't add them to """Generates the Add and Remove actions (but doesn't add them to
session). session).
@ -251,9 +283,13 @@ class Sync:
if adding: if adding:
# For the components we are adding, let's remove them from their old parents # For the components we are adding, let's remove them from their old parents
def g_parent(component: Component) -> Device: def g_parent(component: Component) -> Device:
return component.parent or Device(id=0) # Computer with id 0 is our Identity return component.parent or Device(
id=0
) # Computer with id 0 is our Identity
for parent, _components in groupby(sorted(adding, key=g_parent), key=g_parent): for parent, _components in groupby(
sorted(adding, key=g_parent), key=g_parent
):
set_components = OrderedSet(_components) set_components = OrderedSet(_components)
check_owners = (x.owner_id == g.user.id for x in set_components) check_owners = (x.owner_id == g.user.id for x in set_components)
# Is not Computer Identity and all components have the correct owner # Is not Computer Identity and all components have the correct owner
@ -263,21 +299,18 @@ class Sync:
class MismatchBetweenTags(ValidationError): class MismatchBetweenTags(ValidationError):
def __init__(self, def __init__(self, tag: Tag, other_tag: Tag, field_names={'device.tags'}):
tag: Tag, message = '{!r} and {!r} are linked to different devices.'.format(
other_tag: Tag, tag, other_tag
field_names={'device.tags'}): )
message = '{!r} and {!r} are linked to different devices.'.format(tag, other_tag)
super().__init__(message, field_names) super().__init__(message, field_names)
class MismatchBetweenTagsAndHid(ValidationError): class MismatchBetweenTagsAndHid(ValidationError):
def __init__(self, def __init__(self, device_id: int, hid: str, field_names={'device.hid'}):
device_id: int, message = 'Tags are linked to device {} but hid refers to device {}.'.format(
hid: str, device_id, hid
field_names={'device.hid'}): )
message = 'Tags are linked to device {} but hid refers to device {}.'.format(device_id,
hid)
super().__init__(message, field_names) super().__init__(message, field_names)
@ -285,6 +318,8 @@ class MismatchBetweenProperties(ValidationError):
def __init__(self, props1, props2, field_names={'device'}): def __init__(self, props1, props2, field_names={'device'}):
message = 'The device from the tag and the passed-in differ the following way:' message = 'The device from the tag and the passed-in differ the following way:'
message += '\n'.join( message += '\n'.join(
difflib.ndiff(yaml.dump(props1).splitlines(), yaml.dump(props2).splitlines()) difflib.ndiff(
yaml.dump(props1).splitlines(), yaml.dump(props2).splitlines()
)
) )
super().__init__(message, field_names) super().__init__(message, field_names)