Skip to content
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

fix(types) ignore tag order for object comparisons #240

Merged
merged 3 commits into from
Dec 7, 2020
Merged

Conversation

rainest
Copy link
Contributor

@rainest rainest commented Dec 3, 2020

Ignore tag order for object comparisons by sorting the string slices that contain them before checking object equality. This avoids issues with spurious diffs resulting in unnecessary changes when the tag order constructed by deck from dump YAML and the tag order reported by admin API JSON differ.

Because Kong object tags are logical string sets, their order does not matter: it is only a perceived difference because JSON and YAML lack set types and represent them with ordered arrays.

Fix #239 and a minor typo ("Copt" to "Copy" in one of the functions).

Implementation notes/questions:

We have Equal() and EqualWithOpts() functions for each object. The former is just return reflect.DeepEqual(thing1, thing2), is not modified by this PR, and would still report the objects not equal as such. The latter copies the objects first and then nulls out their IDs and/or created and updated timestamps based on the options provided before performing a comparison. This PR does modify EqualWithOpts() to ignore tag order, though it doesn't introduce a new option: IDs and timestamp differences do actually matter for objects, but tag order never does, so it doesn't seem like it makes sense to add an option.

I'm curious about the history behind this, since as far as I know deck never cares about timestamps (there's no CLI options to toggle that option, and dumps don't include timestamp data, but this doesn't mean that deck repeatedly updates objects because the nil timestamps in dumps do not match the not-nil timestamps) and only sort of cares about IDs (dump has an option to include them or omit them, but sync doesn't have an option to ignore them).

  • Do we use Equal() in practice, and if so, how/why? Do we need to modify it to also create copies and sort their tags?
  • Ditto for EqualWithOpts(): do we ever use that without ignoring IDs and timestamps?

Unrelated to that, types_test.go does not currently include tags on any of its test objects. We can cover that pretty easily by adding differently-ordered Tags to objects, e.g. after https://github.com/Kong/deck/blob/v1.2.3/state/types_test.go#L51 and confirming that EqualWithOpts() is still true, but I wanted to get a pass on the proposed implementation here before writing those out for every object (Golang generics, you cannot arrive soon enough).

@hbagdi
Copy link
Member

hbagdi commented Dec 3, 2020

I'm curious about the history behind this, since as far as I know deck never cares about timestamps (there's no CLI options to toggle that option, and dumps don't include timestamp data, but this doesn't mean that deck repeatedly updates objects because the nil timestamps in dumps do not match the not-nil timestamps) and only sort of cares about IDs (dump has an option to include them or omit them, but sync doesn't have an option to ignore them).

There is development history behind it. In an early PoC of decK, the code looked a lot different than it is today and there was a DAG built to solve the diffing problem. It turned out to be over-engineered. The code simplification kept major parts of the codebase, including this. There are no parts of code that care about timestamps in diffing.
ID is a little different. Around decK 0.5.0 or 0.6.0, decK again underwent a major re-write to simplify code and ensure 100% correctness without an elaborate test suite. Before this re-write, some portions of code ignored ID for comparison and some didn't. I don't recall why that was the case, git spelunking might help here.

Do we use Equal() in practice, and if so, how/why? Do we need to modify it to also create copies and sort their tags?

We don't but these functions are now diverging. Order of tags should be ignored no matter what. I'd recommend the following refactor as part of this change:

fn Equal() {
  return EqualWithOpt(with, default, arguments)
}

Ditto for EqualWithOpts(): do we ever use that without ignoring IDs and timestamps?

A quick grep says no.

@hbagdi
Copy link
Member

hbagdi commented Dec 3, 2020

Consider the PR approved once you add in tests and refactor the Equal fns.

Travis Raines added 2 commits December 3, 2020 13:34
Ignore tag order for object comparisons by sorting the string slices
that contain them before checking object equality. This avoids issues
with spurious diffs resulting in unnecessary changes when the tag order
constructed by deck from dump YAML and the tag order reported by admin
API JSON differ.

Because Kong object tags are logical string sets, their order does not
matter: it is only a perceived difference because JSON and YAML lack set
types and represent them with ordered arrays.

Fix #239
* Add a function to generate a set of test tags with different orders.
* Add test assertions that confirm objects are considered equal when
  they have the same set of tags, even when the tag order differs.
@rainest rainest force-pushed the fix/sort-tags branch 2 times, most recently from f8771a7 to c2efe87 Compare December 3, 2020 23:38
@rainest
Copy link
Contributor Author

rainest commented Dec 3, 2020

Double-checking on the refactor, but I think we're good to go here. Grepping shows that we actually do have ID checks enabled in current calls, though presumably most users aren't using --with-id when dumping and/or aren't changing object IDs between runs, so that doesn't really matter in practical usage.

The complete signatures follow a func (c1 *Object) EqualWithOpts(c2 *Object, ignoreID bool, ignoreTS bool, ...) bool pattern, e.g. for aclGroup, and across the codebase:

$ grep -Ir EqualWithOpts | egrep -ce "\(\w*, false" 
32
$ grep -Ir EqualWithOpts | egrep -ce "\(\w*, true"  
0

Have made that the default in the Equal() refactor as such. Would we want to flip that for any reason?

Not sure if there's some more idiomatic way to handle the tag test without creating the test Tags slice in each test function--doesn't appear to be any way to create constant *string slices, even if you make them fixed-sized arrays, so the generator function works around that.

@rainest rainest marked this pull request as ready for review December 3, 2020 23:55
state/types.go Outdated Show resolved Hide resolved
Make object Equal() checks a passthrough to EqualWithOpts() with default
options (ignore nothing). This does ignore tag order, since tag order
comparisons aren't actually desirable but do happen as a consequence of
the data structure we use for them.

Passing through allows us to reuse the object copy and modify logic in
EqualWithOpts(). Previously Equal() immediately returned
reflect.DeepEqual() and we'd need to duplicate copy/modify logic in
Equal() to ensure tag order invariance otherwise.
Copy link
Member

@hbagdi hbagdi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a thought that there might be a way to DRY this out a little but I ignored it because I expect this code to not be touched enough.

@hbagdi hbagdi merged commit 3bfaf79 into main Dec 7, 2020
@hbagdi hbagdi deleted the fix/sort-tags branch January 12, 2021 04:30
rainest pushed a commit that referenced this pull request Apr 21, 2021
Ignore tag order for object comparisons by sorting the string slices
that contain them before checking object equality. This avoids issues
with spurious diffs resulting in unnecessary changes when the tag order
constructed by deck from dump YAML and the tag order reported by admin
API JSON differ.

Because Kong object tags are logical string sets, their order does not
matter: it is only a perceived difference because JSON and YAML lack set
types and represent them with ordered arrays.

Fix #239
From #240
AntoineJac pushed a commit that referenced this pull request Jan 23, 2024
Ignore tag order for object comparisons by sorting the string slices
that contain them before checking object equality. This avoids issues
with spurious diffs resulting in unnecessary changes when the tag order
constructed by deck from dump YAML and the tag order reported by admin
API JSON differ.

Because Kong object tags are logical string sets, their order does not
matter: it is only a perceived difference because JSON and YAML lack set
types and represent them with ordered arrays.

Fix #239
From #240
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ordered tag comparisons result in spurious diff reports
2 participants