-
Notifications
You must be signed in to change notification settings - Fork 94
feat: utility to verify sync behaviour correctness #1066
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| sync-consistency-util | ||
| vendor |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
|
|
||
| # Application Synchronization Simulation and Verification Utility | ||
|
|
||
| The primary goal of this utility is to verify the correctness of Application synchronization behaviour between agent and principal. | ||
|
|
||
| This utility simulates Application resource events: | ||
| - Randomly creates/modifies/deletes Argo CD Application resources on control plane cluster | ||
| - Verifies that the corresponding creation/modification/delete was made on managed-agent, in order and in a timely fashion (via K8s watch API) | ||
| - Each time Application is modified, the `.spec.source.repoURL` field is modified with a unique value, to track `.spec` synchronization to agent | ||
| - Randomly modifies `.status` field of Argo CD Applications on workload cluster | ||
| - Verifies that the corresponding `.status` update is broadcast back to Application on control plane cluster | ||
| - Each time Application status is modified, the `.status.sync.revision` field is modified with a unique value, allowing us to track `.status` synchronization back to principal | ||
| - `.spec` writer and `.status` writer run concurrently, from a single OS process | ||
| - `.spec` watcher and `.status` watcher (responsible for observing events before verification) likewise run concurrently | ||
|
|
||
| However, the implementation details of this utility are slightly more complex, due to Argo CD agent de-duplication behaviour: | ||
| - Event writer (`event_writer.go`) will de-dupe '.status update' and '.spec update' events | ||
| - Event writer will also de-dupe delete events: if an application is deleted, all previously unsent events (.spec modifications, etc) will be discarded (as they are necessarily stale) | ||
| - If informer event buffer (`informer_event_buffer.go`) feature is enabled, updates to Application `.spec` that occur within X seconds will be de-duplicated | ||
|
|
||
| This complex behaviour requires that this utility uses an algorithm that checks for eventual consistency, rather than an algorithm that looks for exact match between source/destination events. | ||
|
|
||
|
|
||
| ## How to run | ||
|
|
||
|
|
||
| #### Setup | ||
|
|
||
| Test utility currently assumes standard vcluster dev-env. | ||
|
|
||
| ``` | ||
| make setup-e2e | ||
| ``` | ||
|
|
||
| #### From window A: start agent processes | ||
| Start principal/agents on vcluster dev-env, running locally: | ||
| ``` | ||
| make start-e2e | ||
|
|
||
| # If you want to write the screen output to a file for debugging purposes, use `script (filename)`. | ||
|
|
||
| # or, if debugging an eventual consistency problem, you can enable full logging on agent via: | ||
| ARGOCD_AGENT_FULL_DETAIL=all ARGOCD_AGENT_LOG_LEVEL=trace ARGOCD_PRINCIPAL_FULL_DETAIL=all ARGOCD_PRINCIPAL_LOG_LEVEL=trace make start-e2e | ||
| ``` | ||
|
|
||
| #### From window B: start utility | ||
|
|
||
| In a separate window, run the utility: | ||
| ``` | ||
| cd hack/sync-consistency-util | ||
|
|
||
| # Modify constants in `main.go` to fit your needs, or just run default. | ||
|
|
||
| go run . | ||
|
|
||
| # Or, if you want to keep running it over and over until it fails (useful for finding bugs), see the 'until-fail.sh' utility (from https://gist.github.com/jgwest/7048a765d398519837f990120cf3fdd0) and use: | ||
|
|
||
| until-fail.sh go run . | ||
| ``` | ||
|
|
||
| Note: | ||
| - Running this utility will delete all Applications on all vclusters at startup. | ||
| - This will set replicas to 0 on application controller on managed agent (to avoid generating extra events). | ||
|
|
||
|
|
||
| ## Further details | ||
|
|
||
| See source code for implementation details of eventual consistency checking. | ||
|
|
||
| Principles of eventual consistency: | ||
| - There is a non-trivial delay between when a create/update/delete is made on source (of truth), to when it is transmitted, processed, and applied on destination peer | ||
| - Within the utility codebase we refer to this non-trivial delay as propagation delay. | ||
| - The more overloaded the K8s API server/etcd, and agents, the longer this propagation delay can take. | ||
| - We thus allow configuration of a 'max propagation delay' constant, which is the max expected value before we assert an event has been incorrectly, permanently lost | ||
| - Add/delete events (lifecycle events) must always be transmitted from source/destination, as there should be no-dedupe of these: | ||
| - With one exception: if an application is created then quickly deleted, e.g. the outbox will contain: '1) create Application event 2) delete Application event' | ||
| - In this case, when the outbox still contains both, neither will be sent. (There is no point to sending a create if you already know it will be deleted immediately) | ||
| - However, not all update events will be transmitted from source to destination: some deduplication occurs within agent code at various points | ||
| - BUT, even though we are de-duplicating spec-update and status-updates, we should never permanently MISS an Application resource update: | ||
| - If | ||
| - Event 'A' occurs on source of truth (SoT) and is processed on peer (non-SoT) at time t=X | ||
| - And, one (or more) event 'B'-'Z' occur (on SoT) at T=X+Y, for some Y > 0 | ||
| - Then | ||
| - At least one of the events from 'B'-'Z' should be processed on peer by X+'PD' (where PD is expected propagation, e.g. 15 seconds). | ||
| - We should never receive events out of the order the event occurred on source of truth: | ||
| - A delete sent before a create | ||
| - Updates received in a different order on peer, from the order they were sent from source of truth | ||
|
|
||
| ## Current Limitations | ||
|
|
||
| These are not design limitations: these are just features not yet implemented. | ||
|
|
||
| - `Application` resources only (no other resource types are validated via create/modify/delete) | ||
| - Non-destination-based-mapping case only (consistent with `make setup-e2e` default, as of this writing) | ||
| - Assumes/requires that we are running on local dev-env/vcluster configuration (via `make setup-e2e`) | ||
| - Only 1 writer goroutine per writer type (one for spec, one for status) -- spec-writes and status-writes run concurrently, but there are no parallel writers of the same type | ||
| - Currently only verifies principal <-> managed-agent. Does not verify principal <-> autonomous-agent. | ||
| - This tool may also be useful for benchmarking, BUT, at present we are blocked on vcluster etcd CPU usage | ||
| - Simulate creation of a new `Application` with name of a previously deleted `Application:` The mechanism I am using to create Applications will never create an application which had the name of a previously deleted application. | ||
| - Currently this utility works end to end: a single OS process uses client-go to both watch Application CR and also create/update/delete Application CR. Instead, this could be split into two separate utilities, with a third utility to check the results. This would be useful for large scale multi-cluster (not just vcluster) checking. | ||
|
|
||
| ## Outstanding improvements to Argo CD Agent | ||
| - As of this writing, boundedQueue [will silently drop events](https://github.com/argoproj-labs/argocd-agent/blob/8241768ae7a0ed5d110187af83d40b2fbf988f81/internal/queue/queue.go#L71) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| module github.com/argoproj-labs/argocd-agent/hack/sync-consistency-util | ||
|
|
||
| go 1.26.0 | ||
|
jgwest marked this conversation as resolved.
|
||
|
|
||
| require ( | ||
| github.com/argoproj/argo-cd/v3 v3.4.3 | ||
| k8s.io/api v0.34.0 | ||
| k8s.io/apimachinery v0.34.0 | ||
| k8s.io/client-go v0.34.0 | ||
| sigs.k8s.io/controller-runtime v0.21.0 | ||
| ) | ||
|
|
||
| require ( | ||
| cloud.google.com/go/compute/metadata v0.9.0 // indirect | ||
| cyphar.com/go-pathrs v0.2.1 // indirect | ||
| dario.cat/mergo v1.0.2 // indirect | ||
| github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect | ||
| github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect | ||
| github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect | ||
| github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect | ||
| github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect | ||
| github.com/MakeNowJust/heredoc v1.0.0 // indirect | ||
| github.com/Masterminds/semver/v3 v3.4.0 // indirect | ||
| github.com/Microsoft/go-winio v0.6.2 // indirect | ||
| github.com/ProtonMail/go-crypto v1.1.6 // indirect | ||
| github.com/argoproj/argo-cd/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627 // indirect | ||
| github.com/argoproj/pkg v0.13.6 // indirect | ||
| github.com/argoproj/pkg/v2 v2.0.1 // indirect | ||
| github.com/beorn7/perks v1.0.1 // indirect | ||
| github.com/blang/semver/v4 v4.0.0 // indirect | ||
| github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect | ||
| github.com/bombsimon/logrusr/v4 v4.1.0 // indirect | ||
| github.com/bradleyfalzon/ghinstallation/v2 v2.18.0 // indirect | ||
| github.com/casbin/casbin/v2 v2.135.0 // indirect | ||
| github.com/casbin/govaluate v1.10.0 // indirect | ||
| github.com/cenkalti/backoff/v5 v5.0.3 // indirect | ||
| github.com/cespare/xxhash/v2 v2.3.0 // indirect | ||
| github.com/chai2010/gettext-go v1.0.3 // indirect | ||
| github.com/chainguard-dev/git-urls v1.0.2 // indirect | ||
| github.com/cloudflare/circl v1.6.3 // indirect | ||
| github.com/cyphar/filepath-securejoin v0.6.1 // indirect | ||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect | ||
| github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect | ||
| github.com/distribution/reference v0.6.0 // indirect | ||
| github.com/dlclark/regexp2 v1.11.5 // indirect | ||
| github.com/emicklei/go-restful/v3 v3.12.2 // indirect | ||
| github.com/emirpasic/gods v1.18.1 // indirect | ||
| github.com/evanphx/json-patch/v5 v5.9.11 // indirect | ||
| github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect | ||
| github.com/fatih/camelcase v1.0.0 // indirect | ||
| github.com/fxamacker/cbor/v2 v2.9.0 // indirect | ||
| github.com/go-errors/errors v1.5.1 // indirect | ||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect | ||
| github.com/go-git/go-billy/v5 v5.6.2 // indirect | ||
| github.com/go-git/go-git/v5 v5.14.0 // indirect | ||
| github.com/go-logr/logr v1.4.3 // indirect | ||
| github.com/go-openapi/jsonpointer v0.22.5 // indirect | ||
| github.com/go-openapi/jsonreference v0.21.5 // indirect | ||
| github.com/go-openapi/swag v0.23.1 // indirect | ||
| github.com/go-openapi/swag/jsonname v0.25.5 // indirect | ||
| github.com/go-redis/cache/v9 v9.0.0 // indirect | ||
| github.com/gobwas/glob v0.2.3 // indirect | ||
| github.com/gogo/protobuf v1.3.2 // indirect | ||
| github.com/golang-jwt/jwt/v4 v4.5.2 // indirect | ||
| github.com/golang-jwt/jwt/v5 v5.3.1 // indirect | ||
| github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect | ||
| github.com/google/btree v1.1.3 // indirect | ||
| github.com/google/gnostic-models v0.7.0 // indirect | ||
| github.com/google/go-cmp v0.7.0 // indirect | ||
| github.com/google/go-github/v69 v69.2.0 // indirect | ||
| github.com/google/go-github/v84 v84.0.0 // indirect | ||
| github.com/google/go-querystring v1.2.0 // indirect | ||
| github.com/google/uuid v1.6.1-0.20241114170450-2d3c2a9cc518 // indirect | ||
| github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect | ||
| github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect | ||
| github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
| github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||
| github.com/jonboulle/clockwork v0.5.0 // indirect | ||
| github.com/josharian/intern v1.0.0 // indirect | ||
| github.com/json-iterator/go v1.1.12 // indirect | ||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||
| github.com/kevinburke/ssh_config v1.2.0 // indirect | ||
| github.com/klauspost/compress v1.18.0 // indirect | ||
| github.com/kylelemons/godebug v1.1.0 // indirect | ||
| github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect | ||
| github.com/mailru/easyjson v0.9.0 // indirect | ||
| github.com/mitchellh/go-wordwrap v1.0.1 // indirect | ||
| github.com/moby/spdystream v0.5.1 // indirect | ||
| github.com/moby/term v0.5.2 // indirect | ||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
| github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect | ||
| github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect | ||
| github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||
| github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect | ||
| github.com/opencontainers/go-digest v1.0.0 // indirect | ||
| github.com/opencontainers/image-spec v1.1.1 // indirect | ||
| github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible // indirect | ||
| github.com/peterbourgon/diskv v2.0.1+incompatible // indirect | ||
| github.com/pjbgf/sha1cd v0.3.2 // indirect | ||
| github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect | ||
|
jgwest marked this conversation as resolved.
|
||
| github.com/pkg/errors v0.9.1 // indirect | ||
| github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect | ||
| github.com/prometheus/client_golang v1.23.2 // indirect | ||
| github.com/prometheus/client_model v0.6.2 // indirect | ||
| github.com/prometheus/common v0.66.1 // indirect | ||
| github.com/prometheus/procfs v0.16.1 // indirect | ||
| github.com/redis/go-redis/v9 v9.18.0 // indirect | ||
| github.com/robfig/cron/v3 v3.0.2-0.20210106135023-bc59245fe10e // indirect | ||
| github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||
| github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect | ||
| github.com/sirupsen/logrus v1.9.4 // indirect | ||
| github.com/skeema/knownhosts v1.3.1 // indirect | ||
| github.com/spf13/cobra v1.10.2 // indirect | ||
| github.com/spf13/pflag v1.0.10 // indirect | ||
| github.com/vmihailenco/go-tinylfu v0.2.2 // indirect | ||
| github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect | ||
| github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||
| github.com/x448/float16 v0.8.4 // indirect | ||
| github.com/xanzy/ssh-agent v0.3.3 // indirect | ||
| github.com/xlab/treeprint v1.2.0 // indirect | ||
| go.opentelemetry.io/otel v1.43.0 // indirect | ||
| go.opentelemetry.io/otel/trace v1.43.0 // indirect | ||
| go.uber.org/atomic v1.11.0 // indirect | ||
| go.yaml.in/yaml/v2 v2.4.2 // indirect | ||
| go.yaml.in/yaml/v3 v3.0.4 // indirect | ||
| golang.org/x/crypto v0.49.0 // indirect | ||
| golang.org/x/net v0.52.0 // indirect | ||
| golang.org/x/oauth2 v0.36.0 // indirect | ||
| golang.org/x/sync v0.20.0 // indirect | ||
| golang.org/x/sys v0.42.0 // indirect | ||
| golang.org/x/term v0.41.0 // indirect | ||
| golang.org/x/text v0.35.0 // indirect | ||
| golang.org/x/time v0.15.0 // indirect | ||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect | ||
| google.golang.org/grpc v1.79.3 // indirect | ||
| google.golang.org/protobuf v1.36.11 // indirect | ||
| gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect | ||
| gopkg.in/inf.v0 v0.9.1 // indirect | ||
| gopkg.in/warnings.v0 v0.1.2 // indirect | ||
| gopkg.in/yaml.v2 v2.4.0 // indirect | ||
| gopkg.in/yaml.v3 v3.0.1 // indirect | ||
| k8s.io/apiextensions-apiserver v0.34.0 // indirect | ||
| k8s.io/apiserver v0.34.0 // indirect | ||
| k8s.io/cli-runtime v0.34.0 // indirect | ||
| k8s.io/component-base v0.34.0 // indirect | ||
| k8s.io/component-helpers v0.34.0 // indirect | ||
| k8s.io/controller-manager v0.34.0 // indirect | ||
| k8s.io/klog/v2 v2.130.1 // indirect | ||
| k8s.io/kube-aggregator v0.34.0 // indirect | ||
| k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect | ||
| k8s.io/kubectl v0.34.0 // indirect | ||
| k8s.io/kubernetes v1.34.2 // indirect | ||
| k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect | ||
| oras.land/oras-go/v2 v2.6.0 // indirect | ||
| sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect | ||
| sigs.k8s.io/kustomize/api v0.20.1 // indirect | ||
| sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect | ||
| sigs.k8s.io/randfill v1.0.0 // indirect | ||
| sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect | ||
| sigs.k8s.io/yaml v1.6.0 // indirect | ||
| ) | ||
|
|
||
| replace ( | ||
| github.com/argoproj/argo-cd/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627 => github.com/argoproj/argo-cd/gitops-engine v0.0.0-20260527205206-ce55c857b5e5 | ||
| github.com/golang/glog => github.com/golang/glog v1.2.4 | ||
| k8s.io/api => k8s.io/api v0.34.0 | ||
| k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.34.0 | ||
| k8s.io/apimachinery => k8s.io/apimachinery v0.34.0 | ||
| k8s.io/apiserver => k8s.io/apiserver v0.34.0 | ||
| k8s.io/cli-runtime => k8s.io/cli-runtime v0.34.0 | ||
| k8s.io/client-go => k8s.io/client-go v0.34.0 | ||
| k8s.io/cloud-provider => k8s.io/cloud-provider v0.34.0 | ||
| k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.34.0 | ||
| k8s.io/code-generator => k8s.io/code-generator v0.34.0 | ||
| k8s.io/component-base => k8s.io/component-base v0.34.0 | ||
| k8s.io/component-helpers => k8s.io/component-helpers v0.34.0 | ||
| k8s.io/controller-manager => k8s.io/controller-manager v0.34.0 | ||
| k8s.io/cri-api => k8s.io/cri-api v0.34.0 | ||
| k8s.io/cri-client => k8s.io/cri-client v0.34.0 | ||
| k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.34.0 | ||
| k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.34.0 | ||
| k8s.io/endpointslice => k8s.io/endpointslice v0.34.4 | ||
| k8s.io/externaljwt => k8s.io/externaljwt v0.34.4 | ||
| k8s.io/kms => k8s.io/kms v0.34.0 | ||
| k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.34.0 | ||
| k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.34.0 | ||
| k8s.io/kube-proxy => k8s.io/kube-proxy v0.34.0 | ||
| k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.34.0 | ||
| k8s.io/kubectl => k8s.io/kubectl v0.34.0 | ||
| k8s.io/kubelet => k8s.io/kubelet v0.34.0 | ||
| k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.34.0 | ||
| k8s.io/metrics => k8s.io/metrics v0.34.0 | ||
| k8s.io/mount-utils => k8s.io/mount-utils v0.34.0 | ||
| k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.34.0 | ||
| k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.34.0 | ||
| k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.34.0 | ||
| k8s.io/sample-controller => k8s.io/sample-controller v0.34.0 | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.