-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom relationship sorting #496
base: develop
Are you sure you want to change the base?
Conversation
@Kircheneer this is an idea of mine for sorting out relationships using contrib and avoiding false updates. The function can be used by both the Contrib NautobotAdapter and also by custom adapters for implementations. The only thing that is needed for the NautobotAdapter is to specify the The custom adapters will need to be sure to import and call the function in Thoughts? |
What do you think of making this the default behaviour? I don't think to-many relationships are ordered ever, so maybe we can just default to this? |
In this case though, I would probably like to do the sorting right in the loading |
@Kircheneer the issue is when the source and target load to-many relationships into an unordered list as is typically done. The problem is when that order varies between source and target. When that happens, the SSoT process gives a false update. All the items in the two comparing lists are exactly the same except for the order of the items themselves. That's what this particular PR aims to resolve. The challenge is the to-many relationships must be ordered after everything is loaded into the DiffSync store and must be done on both source and target adapters to ensure they both match. |
So I have been thinking about this again, and I don't see a scenario in which we would not want to do this. Do you think we can omit defining Also, since you have tuples in here, what kind of use case is there for using tuples with contrib? I don't think I have hit this before, hence the asking. Thanks! |
@Kircheneer Have been thinking about this. Not sure if omitting the The challenge comes when sorting the actual list of dictionaries, you need to define what key of the dictionaries to sort by. Not every dictionary has a Unless there is a different way to sort the relationships that would be better? |
You're right, I totally missed that. I want to try and have less implicit configuration (like the first element of the tuples in this list matching the field name on the model) and move towards explicit configuration. Do you think it would be possible that we somehow encode this information into the class TenantDict(TypedDict):
name: typing.Annotated[str, FieldType.SortBy] where Let me know what you think of the approach, I know it makes the implementation a bit more complicated but the user interface is cleaner in my opinion. |
@Kircheneer what if we did the annotation in the model creation? I think I have a working case on this.
|
I thought about that option, too, but didn't like how you'd end up having to define two data structures (TypedDict + Dataclass) for the same relationship |
@Kircheneer my main issue with including the annotation into the TypedDict is that every entry in that list will have that same metadata (so it'll be repeated a significant number of times), and more importantly is that you'd have to pull the first item from the list, then search it each key in the dictionary for the appropriate annotation to sort by it. That would make the code more complex and difficult to follow than putting the annotation into the model, even if it is two data structures in the annotation. |
The actual instances of the typed dict wouldn't have the metadata, you would only define it once on the definition of the typed dict, or am I missing something?
I think you would only have to look at |
@Kircheneer what are your thoughts on the changes made since last review? Basically, I have it set to where If it finds it, it'll sort then look for a key inside the dictionary, if applicable, and look for For example:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple clarifying comments, but the direction looks good, thanks!
@@ -44,6 +44,7 @@ class NautobotAdapter(Adapter): | |||
# This dictionary acts as an ORM cache. | |||
_cache: DefaultDict[str, Dict[ParameterSet, Model]] | |||
_cache_hits: DefaultDict[str, int] = defaultdict(int) | |||
sorted_relationships = () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand the newest version correctly, this field is no longer necessary, is that correct?
@@ -0,0 +1,105 @@ | |||
"""Functions for sorting DiffSync model lists ensuring they are sorted to prevent false actions.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a lot of this and of the existing base code should at some point be refactored to use annotationlib - this is not for now though, maybe you can just leave a comment?
return obj | ||
|
||
|
||
def sort_relationships(source: Adapter, target: Adapter): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we loop over every single object here - have you done some tests on performance implications? I guess if you don't have any sortable fields (i.e. your integration was built prior to the introduction of this mechanism and you haven't annotated any fields) the performance difference should be negligible, right?
@@ -179,3 +179,51 @@ If you need to perform the `create`, `update` and `delete` operations on the rem | |||
|
|||
!!! warning | |||
Special care should be taken when synchronizing new Devices with children Interfaces into a Nautobot instance that also defines Device Types with Interface components of the same name. When the new Device is created in Nautobot, its Interfaces will also be created as defined in the respective Device Type. As a result, when SSoT will attempt to create the children Interfaces loaded by the remote adapter, these will already exist in the target Nautobot system. In this scenario, if not properly handled, the sync will fail! Possible remediation steps may vary depending on the specific use-case, therefore this is left as an exercise to the reader/developer to solve for their specific context. | |||
|
|||
### Extra Step 2: Sorting Many-to-Many and Many-to-One Relationships |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is still using the old approach, is that correct? Not a problem for now since its a draft MR, I just want to make sure I understand the functionality correct.
@@ -167,6 +167,54 @@ Through us defining the model, Nautobot will now be able to dynamically load IP | |||
!!! note | |||
Although `Interface.ip_addresses` is a generic relation, there is only one content type (i.e. `ipam.ipaddress`) that may be related through this relation, therefore we don't have to specific this in any way. | |||
|
|||
## Sorting Relationships |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks up-to-date, right? So it should be an accurate representation of how to use this feature?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few things I noticed.
|
||
Loading M2M and N:1 relationship data from source and target destinations are typically not in the same order as each other. For example, the order of a device's interfaces from the source data may differ compared to the order Nautobot loads the data. | ||
|
||
To resolve this, each relationships must be properly sorted before the source and target are compared against eachater. An additional attribute called `sorted_relationships` must be defined in both the source and target adapters. This attribute must be identical between both adapters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To resolve this, each relationships must be properly sorted before the source and target are compared against eachater. An additional attribute called `sorted_relationships` must be defined in both the source and target adapters. This attribute must be identical between both adapters. | |
To resolve this, each relationships must be properly sorted before the source and target are compared against each other. An additional attribute called `sorted_relationships` must be defined in both the source and target adapters. This attribute must be identical between both adapters. |
|
||
### Extra Step 2: Sorting Many-to-Many and Many-to-One Relationships | ||
|
||
If you are not syncing any many-to-many relationships (M2M) or many-to-one (N:1) relationships from the many side, you can skip this step. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it a proper noun, ie Many-to-Many and Many-to-One?
Closes #457