feat: reusable e2e test setups#73
Conversation
christophrj
left a comment
There was a problem hiding this comment.
@dattito Thanks a lot for your contribution and the detailed description. This already looks great. Please have a look at my comments and let me know your thoughts on this.
| // The intent is to make install paths idempotent so that the test harness can | ||
| // be re-run against an existing cluster (see E2E_REUSE_CLUSTER) and propagate | ||
| // spec changes (e.g. a new image) without erroring on AlreadyExists. | ||
| func CreateOrUpdate(ctx context.Context, cfg *envconf.Config, obj k8s.Object) error { |
There was a problem hiding this comment.
have you tried using controller-runtime createOrUpdate instead? I mean by creating a client.Client based on the klient.Client cfg. It might look a little strange to but would save you the custom implementation.
There was a problem hiding this comment.
Yes that is a good idea. I was worried about the "merge" part there, but in the end I also have implemented my self too.
I will replace it.
There was a problem hiding this comment.
Maybe controller-runtime's createOrUpdate is just not a good fit with the e2e decoder/klient and untyped/unstructured objects. At least the way it is implemented right now is pretty confusing and uncommon imo.
But having looked at it again I was wondering if it could be simplified with the existing decoder functions by just replacing the decoder.CreateIgnoreAlreadyExists with a delegate to either decoder.UpdateHandler or decoder.CreateHanlder? What do you think?
| Setup(s.installServiceProviders()). | ||
| Finish(s.cleanup(kindConfig, operatorTemplate)). | ||
| Finish(envfuncs.DestroyCluster(platformClusterName)) | ||
| Setup(s.rolloutOnReuseDeployments()) |
There was a problem hiding this comment.
I'm not sure if I totally understand or missed the reason that it has to be this way but let's discuss :) I am wondering if it wouldn't be easier to reuse the existing LoadImageToCluster flag of each *Setup struct instead of adding another RolloutOnReuse flag.
My sugesstion is that you don't have this additional Setup(s.rolloutOnReuseDeployment()) step but rather adjust the install* steps to be a installOrRedeploy similar to your createNamespace adjustment. Inside the install* functions: If LoadImageToCluster is true and the related ServiceProvider/ClusterProvider/PlatformService resource already exists, delete the resource, wait and recreate it. If LoadImageToCluster is false and the resource already exists, just update it in case the provided image has been changed. This scenario should trigger a regular update that is picked up the openmcp-operator similar to what we do in a real environment.
This way the deployment is still managed by the openmcp-operator with init jobs and run deployments being created properly. This would also make sure that any updated init (job) functionality of that provider is applied as part of the reload.
There was a problem hiding this comment.
Yes that sounds good. I was worried about this needing a lot of time, but I tested it and it doesn't use much time (+ ~5 seconds per run).
There was a problem hiding this comment.
You introduced all the required parts but didn't change the orchestration. Can't you drop rollout.go and just use CreateOrUpdate inside the already existing functions (installOpenMCPOperator, installClusterProvider, installPlatformServices and installServiceProviders)?
On-behalf-of: David Siregar <david.siregar@sap.com> Signed-off-by: David Siregar <david.siregar@sap.com>
…ReuseMode) On-behalf-of: David Siregar <david.siregar@sap.com> Signed-off-by: David Siregar <david.siregar@sap.com>
On-behalf-of: David Siregar <david.siregar@sap.com> Signed-off-by: David Siregar <david.siregar@sap.com>
Signed-off-by: David Siregar <david.siregar@sap.com>
…e SP Signed-off-by: David Siregar <david.siregar@sap.com>
4e9ca8a to
76f8523
Compare
|
Thanks for the review @christophrj. I adjusted the code based on your comments, and I also rebased on main. |
| { | ||
| Name: "crossplane", | ||
| Image: "ghcr.io/openmcp-project/images/service-provider-crossplane:v0.4.1", | ||
| // To iterate on a locally-built same-tag image of this SP, |
There was a problem hiding this comment.
I would prefer to not add the documentation here but where it is actually happening which is in the bootstrap functions. From a user/export perspective the LoadImageToCluster has not changed.
| // additionally deletes and re-creates this PlatformService CR so the | ||
| // operator runs its full reconciliation again (init job + run deployment | ||
| // + Ready), picking up the freshly-loaded image. This flag is the | ||
| // entry point for the same-tag local-rebuild dev loop. |
There was a problem hiding this comment.
see my earlier comment: I would prefer to not add the documentation here but where it is actually happening which is in the bootstrap functions. From a user/export perspective the LoadImageToCluster has not changed.
| Image string | ||
| WaitOpts []wait.Option | ||
| // LoadImageToCluster allows using local images that have to be loaded into the kind cluster | ||
| // LoadImageToCluster, when true, loads the local image into the kind |
There was a problem hiding this comment.
see my earlier comment: I would prefer to not add the documentation here but where it is actually happening which is in the bootstrap functions. From a user/export perspective the LoadImageToCluster has not changed.
| Image string | ||
| WaitOpts []wait.Option | ||
| // LoadImageToCluster allows using local images that have to be loaded into the kind cluster | ||
| // LoadImageToCluster, when true, loads the local image into the kind |
There was a problem hiding this comment.
see my earlier comment: I would prefer to not add the documentation here but where it is actually happening which is in the bootstrap functions. From a user/export perspective the LoadImageToCluster has not changed.
| PlatformName string | ||
| WaitOpts []wait.Option | ||
| // LoadImageToCluster allows using local images that have to be loaded into the kind cluster | ||
| // LoadImageToCluster, when true, loads the local image into the kind |
There was a problem hiding this comment.
same as the earlier documentation comments
| Setup(s.installServiceProviders()). | ||
| Finish(s.cleanup(kindConfig, operatorTemplate)). | ||
| Finish(envfuncs.DestroyCluster(platformClusterName)) | ||
| Setup(s.rolloutOnReuseDeployments()) |
There was a problem hiding this comment.
You introduced all the required parts but didn't change the orchestration. Can't you drop rollout.go and just use CreateOrUpdate inside the already existing functions (installOpenMCPOperator, installClusterProvider, installPlatformServices and installServiceProviders)?
| // The intent is to make install paths idempotent so that the test harness can | ||
| // be re-run against an existing cluster (see E2E_REUSE_CLUSTER) and propagate | ||
| // spec changes (e.g. a new image) without erroring on AlreadyExists. | ||
| func CreateOrUpdate(ctx context.Context, cfg *envconf.Config, obj k8s.Object) error { |
There was a problem hiding this comment.
Maybe controller-runtime's createOrUpdate is just not a good fit with the e2e decoder/klient and untyped/unstructured objects. At least the way it is implemented right now is pretty confusing and uncommon imo.
But having looked at it again I was wondering if it could be simplified with the existing decoder functions by just replacing the decoder.CreateIgnoreAlreadyExists with a delegate to either decoder.UpdateHandler or decoder.CreateHanlder? What do you think?
What this PR does / why we need it:
This PR adds the possibility to reuse platform-, onboarding- and MCP-clusters created during local e2e tests.
This should enhance the developer experience with faster iteration cycles.
Some design-decisions / information:
CreateIgnoreAlreadyExists()toCreateOrUpdate(). I don't expect this to be breaking in any setups, but it is not guaranteed. It is however important as my design expects a complete idempotentSetup()of the test-environment and the individual tests (more in the next bullet point)Teardown(). And on the next run, the idempotentSetup()step will just pick up the still-existing cluster, and will directly fulfill it'sassesssteps.Setup()to be idempotent, the initial cluster name of the platform cluster needs to be hardcoded instead of randomly generated as it is now. I set it per default to "e2e-platform", and it is adjustable. Not that this way multiple e2e-tests cannot run in parallel. I don't think that is a use-case anyway.main_test.gocan use the new "re-use" logic without any code changes. It is activated purely with theE2E_REUSE_CLUSTER=trueenv variable.Setup()part of the individual tests, as I noticed that often e.g. ManagedControlPlane resources are spawned here (and deleted again inTeardown()). With the correct setup (which most tests should already have, but it is not guaranteed: At the end of the test, the MCP has to be as pure as it was after the creation!), these ManagedControlPlanes are also reusable by just not calling theseTeardown()parts when reuse mode is active. However, this has to be implemented in every test itself and cannot be passed down to this library, as it doesn't have control of theTeardown()function here. However, aif !setup.IsReuseMode() {helper has been created to make this easy. An example has been added to the "demo" e2e test of this repo.Which issue(s) this PR fixes:
Fixes #36 .
Special notes for your reviewer:
Teardown()function has to be adjusted. This has to be done in a separate PR.Release note: