diff --git a/.ci/test b/.ci/test index 960dff0514..2bcca1427e 100755 --- a/.ci/test +++ b/.ci/test @@ -36,7 +36,10 @@ function test_with_coverage() { local output_dir=test/output local coverprofile_file=coverprofile.out mkdir -p test/output - ginkgo $GINKGO_COMMON_FLAGS --coverprofile ${coverprofile_file} -covermode=set -output-dir ${output_dir} ${TEST_PACKAGES} + ginkgo $GINKGO_COMMON_FLAGS \ + --coverprofile ${coverprofile_file} -covermode=set \ + --skip-package ${SKIP_PACKAGES} \ + -output-dir ${output_dir} ${TEST_PACKAGES} sed -i -e '/mode: set/d' ${output_dir}/${coverprofile_file} {( echo "mode: set"; cat ${output_dir}/${coverprofile_file} )} > ${output_dir}/${coverprofile_file}.temp @@ -47,18 +50,22 @@ function test_with_coverage() { ############################################################################### if [[ "${SKIP_UNIT_TESTS}" != "" ]]; then - echo ">>>>>Skipping unit tests" + echo ">>>>> Skipping unit tests" else echo ">>>>> Invoking unit tests" TEST_PACKAGES="cmd pkg" - GINKGO_COMMON_FLAGS="-r -timeout=1h0m0s --randomize-all --randomize-suites --fail-on-pending --show-node-events" + SKIP_PACKAGES="simulatedprovider/test/integration" + GINKGO_COMMON_FLAGS="-r -timeout=1h0m0s --randomize-all --randomize-suites --fail-on-pending --show-node-events --poll-progress-after=300s --poll-progress-interval=60s" test_with_coverage echo ">>>>> Finished executing unit tests" fi -# Integration tests here will always be skipped. if [[ "${SKIP_INTEGRATION_TESTS}" != "" ]]; then echo ">>>>> Skipping integration tests" +else + echo ">>>>> Invoking integration tests with the simulated provider" + ginkgo -v --show-node-events --poll-progress-after=300s --poll-progress-interval=60s pkg/simulatedprovider/test/integration/controller + echo ">>>>> Finished executing integration tests" fi #TODO: return success failure properly diff --git a/.gitignore b/.gitignore index 740e09f804..c083001fad 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /test /logs /hack/tools/bin +/pkg/simulatedprovider/bin *.html .vscode @@ -20,12 +21,17 @@ kubectl .cache_ggshield # Binary files of MCM +./machine-controller ./machine-controller-manager ./managevm # Output of the go coverage tool, specifically when used with LiteIDE *coverprofile.out* +# Output of MCM IT logs +**/*.log +**/*.log.* + # Autogen tags tags diff --git a/docs/development/integration_tests.md b/docs/development/integration_tests.md index 394c885e96..3b558900a6 100644 --- a/docs/development/integration_tests.md +++ b/docs/development/integration_tests.md @@ -21,7 +21,19 @@ Integration tests for `machine-controller-manager-provider-{provider-name}` can - In case of non-gardener setup (control cluster is not a gardener seed), the name of the machineclass must be `test-mc-v1` and the value of `providerSpec.secretRef.name` should be `test-mc-secret`. - In case of azure, `TARGET_CLUSTER_NAME` must be same as the name of the Azure ResourceGroup for the cluster. - If you are deploying the secret manually, a `Secret` named `test-mc-secret` (that contains the provider secret and cloud-config) in the `default` namespace of the Control Cluster should be created. -3. The controllers log files (`mcm_process.log` and `mc_process.log`) are stored in `.ci/controllers-test/logs` repo and can be used later. +3. The controllers log files (`mcm_process.log` and `mc_process.log`) are stored in `.ci/controllers-test/logs` directory and can be used later. + +### With the Simulated Provider + +Before running the simulated provider, please ensure that there's no old `kwokctl` versions present on your system since the cluster creation installs the required version. But that only happens if there's no existing installation present. + +To run the integration tests against the simulated provider, rather than running the `test-integration` target from the provider repository, just run it on the project root. +That is, `machine-controller-manager/Makefile` provides the same helper target that runs IT with the simulated provider. +``` +make test-integration +``` +No additional inputs are required. The logs are stored in `pkg/simulatedprovider/test/integration/controller/logs` directory. + ## Adding Integration Tests for new providers For a new provider, Running Integration tests works with no changes. But for the orphan resource test cases to work correctly, the provider-specific API calls and the Resource Tracker Interface (RTI) should be implemented. Please check [`machine-controller-manager-provider-aws`](https://github.com/gardener/machine-controller-manager-provider-aws/blob/master/test/integration/provider/) for reference. diff --git a/go.mod b/go.mod index c19c526240..f3bed8503c 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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 diff --git a/go.sum b/go.sum index 06ef17e92a..9b3bd9bdfe 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,9 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= diff --git a/kubernetes/crds.go b/kubernetes/crds.go new file mode 100644 index 0000000000..c9cb4ac9b4 --- /dev/null +++ b/kubernetes/crds.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package kubernetes + +import ( + _ "embed" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + sigyaml "sigs.k8s.io/yaml" +) + +func mustUnmarshal(raw []byte) *apiextensionsv1.CustomResourceDefinition { + t := new(apiextensionsv1.CustomResourceDefinition) + err := sigyaml.Unmarshal(raw, &t) + if err != nil { + panic(err) + } + return t +} + +var ( + //go:embed crds/machine.sapcloud.io_machineclasses.yaml + // MachineClassCRD represents the raw bytes for MachineClass resource. + MachineClassCRD []byte + //go:embed crds/machine.sapcloud.io_machinedeployments.yaml + // MachineDeploymentCRD represents the raw bytes for MachineDeployment resource. + MachineDeploymentCRD []byte + //go:embed crds/machine.sapcloud.io_machinesets.yaml + // MachineSetCRD represents the raw bytes for MachineSet resource. + MachineSetCRD []byte + //go:embed crds/machine.sapcloud.io_machines.yaml + // MachineCRD represents the raw bytes for Machine resource. + MachineCRD []byte + + // CRDs is a list of all machine-controller-manager CRDs. + CRDs = []*apiextensionsv1.CustomResourceDefinition{ + mustUnmarshal(MachineClassCRD), + mustUnmarshal(MachineDeploymentCRD), + mustUnmarshal(MachineSetCRD), + mustUnmarshal(MachineCRD), + } +) diff --git a/pkg/simulatedprovider/Makefile b/pkg/simulatedprovider/Makefile new file mode 100644 index 0000000000..f6040dac0a --- /dev/null +++ b/pkg/simulatedprovider/Makefile @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +# +# SPDX-License-Identifier: Apache-2.0 + +PKG_ROOT := $(shell dirname "$(realpath $(lastword $(MAKEFILE_LIST)))") +PKG_DIR := $(shell dirname "$(PKG_ROOT)") +REPO_ROOT := $(shell dirname "$(PKG_DIR)") +TOOLS_BIN_DIR := $(REPO_ROOT)/hack/tools/bin + +KWOKCTL ?= $(TOOLS_BIN_DIR)/kwokctl +KWOK_VERSION ?= v0.8.0 + +$(KWOKCTL): $(TOOLS_BIN_DIR) + GOBIN=$(abspath $(TOOLS_BIN_DIR)) go install sigs.k8s.io/kwok/cmd/{kwok,kwokctl}@$(KWOK_VERSION) + + +.PHONY: build +build: + @mkdir -p bin + go build -v -o bin/machine-controller cmd/machine-controller/main.go + +.PHONY: start +start: + @GO111MODULE=on go run \ + cmd/machine-controller/main.go \ + --control-kubeconfig=$(CONTROL_KUBECONFIG) \ + --target-kubeconfig=$(TARGET_KUBECONFIG) \ + --namespace=$(CONTROL_NAMESPACE) \ + --machine-creation-timeout=20m \ + --machine-drain-timeout=5m \ + --machine-health-timeout=10m \ + --machine-pv-detach-timeout=2m \ + --machine-safety-apiserver-statuscheck-timeout=30s \ + --machine-safety-apiserver-statuscheck-period=1m \ + --machine-safety-orphan-vms-period=30m \ + --leader-elect=$(LEADER_ELECT) \ + --v=3 + +.PHONY: start-simulated-cluster +start-simulated-cluster: $(KWOKCTL) + $(KWOKCTL) create cluster --name "test-cluster" --config ./cluster/kwok-config.yaml + +.PHONY: stop-simulated-cluster +stop-simulated-cluster: $(KWOKCTL) + $(KWOKCTL) delete cluster --name "test-cluster" + +.PHONY: test-integration +test-integration: + @(cd test/integration/controller; ginkgo -v --show-node-events --poll-progress-after=300s --poll-progress-interval=60s) diff --git a/pkg/simulatedprovider/cluster/cluster.go b/pkg/simulatedprovider/cluster/cluster.go new file mode 100644 index 0000000000..dc567ecf67 --- /dev/null +++ b/pkg/simulatedprovider/cluster/cluster.go @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package cluster + +import ( + "context" + "fmt" + "os" + "time" + + _ "embed" + + crd "github.com/gardener/machine-controller-manager/kubernetes" + "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" + controller "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecontroller" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + e2efwkenv "sigs.k8s.io/e2e-framework/pkg/env" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/envfuncs" + "sigs.k8s.io/e2e-framework/support" + "sigs.k8s.io/e2e-framework/support/kwok" +) + +const kwokVersion = "v0.8.0" + +//go:embed kwok-config.yaml +var kwokctlConfig []byte + +// Env specifies the virtual cluster details/configuration. +type Env struct { + Name string + Namespace string + Ctx context.Context + Cfg *envconf.Config +} + +// New returns a kwokctl created cluster's Environment. +// Name is used as cluster name and optionally a namespace can +// be specified to be created as part of the cluster creation. +func New(name, namespace string) Env { + // We create an environment and populate its context with the required + // provider details and cluster name, without this the delete call + // fails because it expects this information to be present in the passed + // context. + kwokProvider := kwok.NewProvider().SetDefaults().WithName(name) + ctx := context.WithValue( + context.Background(), support.ClusterNameContextKey(name), kwokProvider, + ) + + return Env{ + Name: name, + Namespace: namespace, + Ctx: ctx, + Cfg: e2efwkenv.New().EnvConf(), + } +} + +// SetupCluster creates a cluster containing MCM CRDs and starts a watch for MachineClass +// objects to create fake secrets for the class' SecretRef and CredentialsSecretRef. +func (env *Env) SetupCluster() (err error) { + if err = env.createCluster(); err != nil { + return + } + + scheme := env.Cfg.Client().Resources().GetScheme() + if err = apiextensionsv1.AddToScheme(scheme); err != nil { + return + } + + if err = env.deployCRDs(); err != nil { + return + } + + // Register MCM API objects with the cluster scheme + if err = v1alpha1.AddToScheme(scheme); err != nil { + return + } + + // For every MachineClass that's added, create fake secrets that + // are part of its SecretRef and CredentialsSecretRef. + // This is needed for the testing framework as well as for MCM + // machine reconciliation, wherein it passes the secret when + // issuing a CreateMachine() call. Ref triggerCreationFlow() + return env.watchMCCForSecretRefs() +} + +// DeleteCluster removes the cluster corresponding to the Env. +func (env *Env) DeleteCluster() (err error) { + destroyClusterFunc := envfuncs.DestroyCluster(env.Name) + _, err = destroyClusterFunc(env.Ctx, env.Cfg) + return +} + +// ExportLogs saves the control plane logs in the specified directory. +func (env *Env) ExportLogs(dir string) (err error) { + exportLogsFunc := envfuncs.ExportClusterLogs(env.Name, dir) + _, err = exportLogsFunc(env.Ctx, env.Cfg) + return +} + +func (env *Env) createCluster() (err error) { + // Using the direct path doesn't work since it looks for the file + // relative to the caller, so the config file is embedded and a + // temporary file path is passed in order to create the cluster. + configFile, err := os.CreateTemp("", "kwokctl-config-*") + if err = os.WriteFile(configFile.Name(), kwokctlConfig, 0600); err != nil { + return + } + defer os.Remove(configFile.Name()) + + // Cluster creation is done manually rather than using `CreateClusterWithConfig` helper + // from e2e-fwk since we wish to override the specified kwokVersion with our own. + kwokProvider := kwok.NewProvider() + k := kwokProvider.SetDefaults().WithName(env.Name).WithVersion(kwokVersion) + kubecfg, err := k.CreateWithConfig(env.Ctx, configFile.Name()) + if err != nil { + return + } + // update envconfig with kubeconfig + env.Cfg.WithKubeconfigFile(kubecfg) + // stall, wait for pods initializations + err = k.WaitForControlPlane(env.Ctx, env.Cfg.Client()) + if err != nil { + return + } + // store entire cluster value in ctx for future access using the cluster name + env.Ctx = context.WithValue(env.Ctx, support.ClusterNameContextKey(env.Name), k) + + createNamespaceFunc := envfuncs.CreateNamespace(env.Namespace) + env.Ctx, err = createNamespaceFunc(env.Ctx, env.Cfg) + if err != nil && !apierrors.IsAlreadyExists(err) { + return + } + return nil +} + +func (env *Env) deployCRDs() (err error) { + for _, crd := range crd.CRDs { + err = env.Cfg.Client().Resources().Create(env.Ctx, crd) + if err != nil && !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("failed to create CRD %q: %w", crd.Name, err) + } + + // This checks if the CRDs are registered before any MCM watches are + // started or objects are deployed. + err = wait.PollUntilContextTimeout(env.Ctx, 100*time.Millisecond, 1*time.Second, false, + func(ctx context.Context) (bool, error) { + crdObj := &apiextensionsv1.CustomResourceDefinition{} + err := env.Cfg.Client().Resources().Get(ctx, crd.Name, "", crdObj) + if err != nil { + return false, err + } + return true, nil + }, + ) + } + + return +} + +func (env *Env) watchMCCForSecretRefs() error { + return env.Cfg.Client().Resources(). + Watch(&v1alpha1.MachineClassList{}, func(listOpts *metav1.ListOptions) { + // Create Secrets for 'any' observed MCCs. Ref: + // https://kubernetes.io/docs/reference/using-api/api-concepts/#semantics-for-watch + listOpts.ResourceVersion = "0" + }). + WithAddFunc(func(obj any) { + mcc, ok := obj.(*v1alpha1.MachineClass) + if !ok || mcc.CredentialsSecretRef == nil || mcc.SecretRef == nil { + return + } + err := env.createFakeSecret(mcc.CredentialsSecretRef.Name, mcc.CredentialsSecretRef.Namespace) + if err != nil && !apierrors.IsAlreadyExists(err) { + fmt.Printf("ERR: Creating secret %q for %q: %v\n", + mcc.CredentialsSecretRef.Name, mcc.Name, err, + ) + return + } + err = env.createFakeSecret(mcc.SecretRef.Name, mcc.SecretRef.Namespace) + if err != nil && !apierrors.IsAlreadyExists(err) { + fmt.Printf("ERR: Creating secret %q for %q: %v\n", + mcc.SecretRef.Name, mcc.Name, err, + ) + return + } + }). + Start(env.Ctx) +} + +func (env *Env) createFakeSecret(name, namespace string) (err error) { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Data: map[string][]byte{}, + StringData: map[string]string{ + "userData": fmt.Sprintf( + "fake-data%s%s", + controller.BootstrapTokenPlaceholder, + controller.MachineNamePlaceholder, + ), + }, + Type: corev1.SecretTypeOpaque, + } + err = env.Cfg.Client().Resources().Create(env.Ctx, &secret) + if err != nil && !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("error creating secret %s: %w", name, err) + } + return +} diff --git a/pkg/simulatedprovider/cluster/kwok-config.yaml b/pkg/simulatedprovider/cluster/kwok-config.yaml new file mode 100644 index 0000000000..e3661bcdda --- /dev/null +++ b/pkg/simulatedprovider/cluster/kwok-config.yaml @@ -0,0 +1,13 @@ +apiVersion: config.kwok.x-k8s.io/v1alpha1 +kind: KwokctlConfiguration +options: + runtime: binary + kubeVersion: v1.35.0 + disableKubeControllerManager: true + manageAllNodes: true +componentsPatches: + - name: kube-apiserver + # This is needed to ensure apiserver doesn't scan n/w interfaces + extraArgs: + - key: advertise-address + value: "127.0.0.1" diff --git a/pkg/simulatedprovider/cmd/machine-controller/main.go b/pkg/simulatedprovider/cmd/machine-controller/main.go new file mode 100644 index 0000000000..f5d1276cde --- /dev/null +++ b/pkg/simulatedprovider/cmd/machine-controller/main.go @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "os" + + simprovider "github.com/gardener/machine-controller-manager/pkg/simulatedprovider/provider" + + _ "github.com/gardener/machine-controller-manager/pkg/util/client/metrics/prometheus" // for client metric registration + "github.com/gardener/machine-controller-manager/pkg/util/provider/app" + "github.com/gardener/machine-controller-manager/pkg/util/provider/app/options" + _ "github.com/gardener/machine-controller-manager/pkg/util/reflector/prometheus" // for reflector metric registration + _ "github.com/gardener/machine-controller-manager/pkg/util/workqueue/prometheus" // for workqueue metric registration + "github.com/spf13/pflag" + "k8s.io/component-base/cli/flag" + "k8s.io/component-base/logs" +) + +func main() { + + s := options.NewMCServer() + s.AddFlags(pflag.CommandLine) + + flag.InitFlags() + logs.InitLogs() + defer logs.FlushLogs() + + if s.TargetKubeconfig == "" { + fmt.Fprintf(os.Stderr, "--target-kubeconfig must be provided\n") + os.Exit(1) + } + + driver, err := simprovider.NewDriver(context.Background(), s.TargetKubeconfig) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(2) + } + + if err := app.Run(s, driver); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + +} diff --git a/pkg/simulatedprovider/go.mod b/pkg/simulatedprovider/go.mod new file mode 100644 index 0000000000..95dbf9299a --- /dev/null +++ b/pkg/simulatedprovider/go.mod @@ -0,0 +1,88 @@ +module github.com/gardener/machine-controller-manager/pkg/simulatedprovider + +// simulatedprovider is its own module to prevent the main MCM project +// from having a dependency on 'e2e-framework' package. + +go 1.26.2 + +require ( + github.com/gardener/machine-controller-manager v0.0.0 + github.com/onsi/ginkgo/v2 v2.27.2 + github.com/onsi/gomega v1.38.2 + github.com/spf13/pflag v1.0.9 + k8s.io/api v0.34.0 + k8s.io/apiextensions-apiserver v0.34.0 + k8s.io/apimachinery v0.34.0 + k8s.io/client-go v0.34.0 + k8s.io/component-base v0.34.0 + k8s.io/klog/v2 v2.130.1 + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + sigs.k8s.io/e2e-framework v0.6.0 +) + +replace github.com/gardener/machine-controller-manager => ../../ + +require ( + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/spdystream v0.5.0 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + 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/spf13/cobra v1.10.0 // indirect + github.com/vladimirvivien/gexe v0.4.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.38.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiserver v0.34.0 // indirect + k8s.io/cluster-bootstrap v0.34.0 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + sigs.k8s.io/controller-runtime v0.20.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/pkg/simulatedprovider/go.sum b/pkg/simulatedprovider/go.sum new file mode 100644 index 0000000000..0211d2da4d --- /dev/null +++ b/pkg/simulatedprovider/go.sum @@ -0,0 +1,247 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= +github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= +github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= +github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/vladimirvivien/gexe v0.4.1 h1:W9gWkp8vSPjDoXDu04Yp4KljpVMaSt8IQuHswLDd5LY= +github.com/vladimirvivien/gexe v0.4.1/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= +k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= +k8s.io/cluster-bootstrap v0.34.0 h1:fWH6cUXbocLYMtWuONVwQ8ayqdEWlyvu25gedMTYTDk= +k8s.io/cluster-bootstrap v0.34.0/go.mod h1:ZpbQwB+CDTYZIjDKM6Hnt081s0xswcFrlhW7mHVNc7k= +k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= +k8s.io/component-base v0.34.0/go.mod h1:RSCqUdvIjjrEm81epPcjQ/DS+49fADvGSCkIP3IC6vg= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.20.0 h1:jjkMo29xEXH+02Md9qaVXfEIaMESSpy3TBWPrsfQkQs= +sigs.k8s.io/controller-runtime v0.20.0/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU= +sigs.k8s.io/e2e-framework v0.6.0 h1:p7hFzHnLKO7eNsWGI2AbC1Mo2IYxidg49BiT4njxkrM= +sigs.k8s.io/e2e-framework v0.6.0/go.mod h1:IREnCHnKgRCioLRmNi0hxSJ1kJ+aAdjEKK/gokcZu4k= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/pkg/simulatedprovider/provider/driver.go b/pkg/simulatedprovider/provider/driver.go new file mode 100644 index 0000000000..41c06f22df --- /dev/null +++ b/pkg/simulatedprovider/provider/driver.go @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "context" + "fmt" + "sync" + "time" + + machineclientset "github.com/gardener/machine-controller-manager/pkg/client/clientset/versioned" + machineclientbuilder "github.com/gardener/machine-controller-manager/pkg/util/clientbuilder/machine" + "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/klog/v2" +) + +const ( + clientNameMachineController = "machine-controller" + + labelCreateMachine = "create_machine" + labelInitializeMachine = "initialize_machine" + labelDeleteMachine = "delete_machine" + labelListMachines = "list_machine" + labelGetMachineStatus = "get_machine_status" + labelGetVolumeIDs = "get_volume_ids" +) + +// Ref: ../../../docs/development/machine_error_codes.md +// Ref: ../../util/provider/driver/driver.go +var _ driver.Driver = &DriverImpl{} + +// DriverImpl is the struct that implements the MCM driver.Driver interface +// It also maintains a map `managedNodes` of all the nodes currently managed by MCM +type DriverImpl struct { + client *kubernetes.Clientset + machineClient machineclientset.Interface + // Map from node.Name to managedNodeInfo + managedNodes sync.Map +} + +type managedNodeInfo struct { + ProviderID string + MachineClassName string +} + +// NewDriver returns a DriverImpl object containing the client, machineclient and a map +// of nodes managed by MCM. +func NewDriver(ctx context.Context, kubecfg string) (driver.Driver, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubecfg) + if err != nil { + return nil, fmt.Errorf("cannot create rest.Config from kubeconfig %q: %w", kubecfg, err) + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("cannot create clientset from kubeconfig %q: %w", kubecfg, err) + } + + mcb := machineclientbuilder.SimpleClientBuilder{ + ClientConfig: config, + } + machineClient, err := mcb.Client(clientNameMachineController) + if err != nil { + return nil, fmt.Errorf("cannot create machine client: %w", err) + } + + d := &DriverImpl{ + client: clientset, + machineClient: machineClient, + } + + err = d.initializeMCMManagedNodes(ctx) + if err != nil { + return nil, err + } + return d, nil +} + +// CreateMachine handles a machine creation request. For the simulated provider, it creates +// a node and attempts to transition it to 'Ready' state. +func (d *DriverImpl) CreateMachine(ctx context.Context, req *driver.CreateMachineRequest) (resp *driver.CreateMachineResponse, err error) { + defer driverAPIMetricRecorderFn(labelCreateMachine, &err)() + + var node *corev1.Node + if _, found := d.managedNodes.Load(req.Machine.Name); !found { + if req.MachineClass.NodeTemplate == nil { + err = fmt.Errorf("cannot build a node as `machineClass.NodeTemplate` is nil") + return + } + node = d.buildNode(req.Machine, req.MachineClass) + _, err = d.client.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + klog.Infof("Created node %q", node.Name) + // The node is considered 'managed' even when it hasn't transitioned to 'Ready'. + d.managedNodes.Store(node.Name, managedNodeInfo{ + ProviderID: node.Spec.ProviderID, + MachineClassName: req.MachineClass.Name, + }) + } + + err = d.transitionNodeToReady(ctx, req.Machine.Name) + if err != nil { + return + } + + resp = &driver.CreateMachineResponse{ + ProviderID: node.Spec.ProviderID, + NodeName: node.Name, + LastKnownState: fmt.Sprintf("Instance %q created at %q", node.Name, time.Now()), + } + return +} + +// InitializeMachine handles VM initialization. Currently, un-implemented. +func (d *DriverImpl) InitializeMachine(_ context.Context, _ *driver.InitializeMachineRequest) (resp *driver.InitializeMachineResponse, err error) { + defer driverAPIMetricRecorderFn(labelInitializeMachine, &err)() + err = status.Error( + codes.Unimplemented, "simulation provider does not implement InitializeMachine", + ) + return +} + +// DeleteMachine handles a machine deletion request. For the simulated provider, it just +// removes the corresponding node from the managedNodes map. +func (d *DriverImpl) DeleteMachine(_ context.Context, req *driver.DeleteMachineRequest) (resp *driver.DeleteMachineResponse, err error) { + defer driverAPIMetricRecorderFn(labelDeleteMachine, &err)() + + d.managedNodes.Delete(req.Machine.Name) + + resp = &driver.DeleteMachineResponse{} + return +} + +// GetMachineStatus handles a machine get status request. +func (d *DriverImpl) GetMachineStatus(_ context.Context, req *driver.GetMachineStatusRequest) (resp *driver.GetMachineStatusResponse, err error) { + defer driverAPIMetricRecorderFn(labelGetMachineStatus, &err)() + + nodeInfo, ok := d.managedNodes.Load(req.Machine.Name) + if !ok { + err = status.Error( + codes.NotFound, fmt.Sprintf("instance %q not found", req.Machine.Name), + ) + return + } + + resp = &driver.GetMachineStatusResponse{ + NodeName: req.Machine.Name, + ProviderID: nodeInfo.(managedNodeInfo).ProviderID, + } + return +} + +// ListMachines lists all the machines possibly created by a machineClass. +// Returns a `map[providerID]machineName` for the specified `MachineClass`. +func (d *DriverImpl) ListMachines(_ context.Context, req *driver.ListMachinesRequest) (resp *driver.ListMachinesResponse, err error) { + defer driverAPIMetricRecorderFn(labelListMachines, &err)() + + resp = &driver.ListMachinesResponse{ + MachineList: make(map[string]string), + } + + d.managedNodes.Range(func(nodeName, nodeInfoObj any) bool { + nodeInfo, ok := nodeInfoObj.(managedNodeInfo) + if ok && nodeInfo.MachineClassName == req.MachineClass.Name { + resp.MachineList[nodeInfo.ProviderID] = nodeName.(string) + } + return true + }) + return +} + +// GetVolumeIDs returns a list of Volume IDs for all PV Specs for whom a provider volume was found. Currently, un-implemented. +func (d *DriverImpl) GetVolumeIDs(_ context.Context, _ *driver.GetVolumeIDsRequest) (resp *driver.GetVolumeIDsResponse, err error) { + defer driverAPIMetricRecorderFn(labelGetVolumeIDs, &err)() + err = status.Error( + codes.Unimplemented, "simulation provider does not implement GetVolumeIDs", + ) + return +} diff --git a/pkg/simulatedprovider/provider/metrics.go b/pkg/simulatedprovider/provider/metrics.go new file mode 100644 index 0000000000..157aa82245 --- /dev/null +++ b/pkg/simulatedprovider/provider/metrics.go @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "errors" + "strconv" + "time" + + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" + "github.com/gardener/machine-controller-manager/pkg/util/provider/metrics" +) + +const prometheusProviderLabelValue = "simulation" + +// recordDriverAPIMetric records a prometheus metric capturing the total duration of a successful execution for +// any driver method (e.g. CreateMachine, DeleteMachine etc.). In case an error is returned then a failed counter +// metric is recorded. +func recordDriverAPIMetric(err error, operation string, invocationTime time.Time) { + if err != nil { + var ( + statusErr *status.Status + labels = []string{prometheusProviderLabelValue, operation} + ) + if errors.As(err, &statusErr) { + labels = append(labels, strconv.Itoa(int(statusErr.Code()))) + } else { + labels = append(labels, strconv.Itoa(int(codes.Internal))) + } + metrics.DriverFailedAPIRequests. + WithLabelValues(labels...). + Inc() + return + } + // compute the time taken to complete the call and record it as a metric + elapsed := time.Since(invocationTime) + metrics.DriverAPIRequestDuration.WithLabelValues( + prometheusProviderLabelValue, + operation, + ).Observe(elapsed.Seconds()) +} + +// driverAPIMetricRecorderFn returns a function that can be used to record a prometheus metric for driver API calls. +// NOTE: a pointer to an error (which itself is a fat interface pointer) is necessary to enable the callers of this function to enclose this call into a `defer` statement. +func driverAPIMetricRecorderFn(operation string, err *error) func() { + invocationTime := time.Now() + return func() { + recordDriverAPIMetric(*err, operation, invocationTime) + } +} diff --git a/pkg/simulatedprovider/provider/node.go b/pkg/simulatedprovider/provider/node.go new file mode 100644 index 0000000000..0e3b6ff8a8 --- /dev/null +++ b/pkg/simulatedprovider/provider/node.go @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "context" + "fmt" + "maps" + "time" + + "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" + controller "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecontroller" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machineutils" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/util/retry" + "k8s.io/klog/v2" + "k8s.io/utils/ptr" +) + +const labelMachineClassName = "machine-class" + +func (d *DriverImpl) initializeMCMManagedNodes(ctx context.Context) error { + // Add only existing nodes having a non-empty 'machine-name' label on them + nodeList, err := d.client.CoreV1().Nodes().List(ctx, metav1.ListOptions{ + LabelSelector: fmt.Sprintf( + "%s,%s!=", machineutils.MachineLabelKey, machineutils.MachineLabelKey, + ), + }) + if err != nil { + return err + } + for _, n := range nodeList.Items { + d.managedNodes.Store(n.Name, managedNodeInfo{ + ProviderID: n.Spec.ProviderID, + MachineClassName: n.Labels[labelMachineClassName], + }) + } + return nil +} + +func (d *DriverImpl) buildNode(mc *v1alpha1.Machine, mcc *v1alpha1.MachineClass) *corev1.Node { + var node corev1.Node + node.Name = mc.Name + node.Finalizers = []string{controller.NodeFinalizerName} + node.Labels = addNodeLabels(mc, mcc) + node.Status = buildNodeStatus(mcc) + node.Spec.ProviderID = fmt.Sprintf("fake://%s:%s", mcc.NodeTemplate.Region, mc.Name) + return &node +} + +func (d *DriverImpl) transitionNodeToReady(ctx context.Context, name string) (err error) { + backoff := wait.Backoff{ + Duration: 500 * time.Millisecond, + Factor: 1.5, + Jitter: 0.1, + Steps: 10, + Cap: 10 * time.Second, + } + + var node *corev1.Node + err = retry.OnError(backoff, func(error) bool { return true }, func() error { + node, err = d.client.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("cannot get node with name %q: %w", name, err) + + } + for i, cond := range node.Status.Conditions { + if cond.Type == corev1.NodeReady { + node.Status.Conditions[i].Status = corev1.ConditionTrue + } + } + node.Status.Phase = corev1.NodeRunning + + _, err = d.client.CoreV1().Nodes().UpdateStatus(ctx, node, metav1.UpdateOptions{}) + return err + }) + + if err != nil { + // If the node cannot be transitioned to 'Ready' state, return an 'Internal' error. + return status.Error(codes.Internal, err.Error()) + } + + klog.Infof("Node %q is ready", node.Name) + return +} + +func addNodeLabels(mc *v1alpha1.Machine, mcc *v1alpha1.MachineClass) map[string]string { + labels := map[string]string{ + machineutils.MachineLabelKey: mc.Name, + corev1.LabelHostname: mc.Name, + corev1.LabelOSStable: "linux", + corev1.LabelInstanceTypeStable: mcc.NodeTemplate.InstanceType, + corev1.LabelArchStable: ptr.Deref(mcc.NodeTemplate.Architecture, ""), + corev1.LabelTopologyRegion: mcc.NodeTemplate.Region, + // This is required so that `ListMachines` can filter the tracked machines based on + // requested `MachineClass` + labelMachineClassName: mcc.Name, + } + + maps.Copy(labels, mc.Spec.NodeTemplateSpec.Labels) + return labels +} + +func buildNodeStatus(mcc *v1alpha1.MachineClass) corev1.NodeStatus { + status := corev1.NodeStatus{ + Capacity: maps.Clone(mcc.NodeTemplate.Capacity), + } + // Prevents the scheduler from throwing `NodeResourcesFit` scheduling failures + status.Capacity[corev1.ResourcePods] = resource.MustParse("110") + + status.Allocatable = maps.Clone(status.Capacity) + + // TODO: (takoverflow) when simulation failures are introduced, this would be initially + // False (i.e. node is NotReady) and depending on the scenario may/may not become True. + status.Conditions = buildNodeReadyConditions(corev1.ConditionFalse) + status.Phase = corev1.NodePending + + return status +} + +func buildNodeReadyConditions(readyConditionStatus corev1.ConditionStatus) []corev1.NodeCondition { + currentTime := metav1.NewTime(time.Now()) + return []corev1.NodeCondition{ + { + Type: corev1.NodeReady, + Status: readyConditionStatus, + LastTransitionTime: currentTime, + LastHeartbeatTime: currentTime, + }, + { + Type: corev1.NodeNetworkUnavailable, + Status: corev1.ConditionFalse, + LastTransitionTime: currentTime, + LastHeartbeatTime: currentTime, + }, + { + Type: corev1.NodeDiskPressure, + Status: corev1.ConditionFalse, + LastTransitionTime: currentTime, + LastHeartbeatTime: currentTime, + }, + { + Type: corev1.NodeMemoryPressure, + Status: corev1.ConditionFalse, + LastTransitionTime: currentTime, + LastHeartbeatTime: currentTime, + }, + } +} diff --git a/pkg/simulatedprovider/test/integration/controller/controller_suite_test.go b/pkg/simulatedprovider/test/integration/controller/controller_suite_test.go new file mode 100644 index 0000000000..5be2b7eb57 --- /dev/null +++ b/pkg/simulatedprovider/test/integration/controller/controller_suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestController(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Controller Suite") +} diff --git a/pkg/simulatedprovider/test/integration/controller/controller_test.go b/pkg/simulatedprovider/test/integration/controller/controller_test.go new file mode 100644 index 0000000000..574fa1cf26 --- /dev/null +++ b/pkg/simulatedprovider/test/integration/controller/controller_test.go @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package test + +import ( + "os" + "path/filepath" + + "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" + "github.com/gardener/machine-controller-manager/pkg/simulatedprovider/cluster" + "github.com/gardener/machine-controller-manager/pkg/test/integration/common" + "github.com/gardener/machine-controller-manager/pkg/test/integration/common/helpers" + ginkgo "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +const ( + testClusterName = "test-cluster" + testNamespace = "test-ns" +) + +var _ helpers.ResourcesTrackerInterface = &resourcesTrackerImpl{} + +var ( + commons = common.NewIntegrationTestFramework(&resourcesTrackerImpl{}, 600) + testClusterEnv = cluster.New(testClusterName, testNamespace) + _ = ginkgo.BeforeSuite(setup) + _ = ginkgo.AfterSuite(cleanup) + _ = ginkgo.Describe("Machine controllers test", func() { + commons.BeforeEachCheck() + commons.ControllerTests() + }) +) + +func setup() { + gomega.Expect(testClusterEnv.SetupCluster()).To(gomega.Succeed()) + gomega.Expect(setEnvVarsForFramework()).To(gomega.Succeed()) + commons.SetupBeforeSuite() +} + +func cleanup() { + commons.Cleanup() + gomega.Expect(testClusterEnv.DeleteCluster()).To(gomega.Succeed()) +} + +func setEnvVarsForFramework() (err error) { + vars := map[string]string{ + "KUBECONFIG": testClusterEnv.Cfg.KubeconfigFile(), + "CONTROL_KUBECONFIG": testClusterEnv.Cfg.KubeconfigFile(), + "CONTROL_NAMESPACE": testClusterEnv.Namespace, + "CONTROL_CLUSTER_NAMESPACE": testClusterEnv.Namespace, + "IS_CONTROL_CLUSTER_SEED": "true", + "SIM_PROVIDER": "true", + "LEADER_ELECT": "false", + "MACHINECLASS_V1": filepath.Join("test-mcc.yaml"), + } + + for k, v := range vars { + if err = os.Setenv(k, v); err != nil { + return + } + } + return +} + +// ResourceTracker is not really useful for the simulated provider, since +// there's no real resources that require tracking and cleanup if left orphan. +// So this just satisfies the ResourceTrackerInterface in order to construct +// test integration testing framework. + +type resourcesTrackerImpl struct { + MachineClass *v1alpha1.MachineClass + SecretData map[string][]byte + ClusterName string +} + +func (r *resourcesTrackerImpl) InitializeResourcesTracker(machineClass *v1alpha1.MachineClass, secretData map[string][]byte, clusterName string) error { + r.MachineClass = machineClass + r.SecretData = secretData + r.ClusterName = clusterName + return nil +} + +func (r *resourcesTrackerImpl) IsOrphanedResourcesAvailable() bool { + return false +} diff --git a/pkg/simulatedprovider/test/integration/controller/logs/.gitkeep b/pkg/simulatedprovider/test/integration/controller/logs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/simulatedprovider/test/integration/controller/test-mcc.yaml b/pkg/simulatedprovider/test/integration/controller/test-mcc.yaml new file mode 100644 index 0000000000..b3f2ecb3c8 --- /dev/null +++ b/pkg/simulatedprovider/test/integration/controller/test-mcc.yaml @@ -0,0 +1,52 @@ +apiVersion: machine.sapcloud.io/v1alpha1 +credentialsSecretRef: + name: cloudprovider + namespace: test-ns +kind: MachineClass +metadata: + finalizers: + - machine.sapcloud.io/machine-controller-manager + generation: 1 + labels: + failure-domain.beta.kubernetes.io/zone: eu-west-1a + operatingSystemName: gardenlinux + name: test-mc-v1 + namespace: test-ns +nodeTemplate: + architecture: amd64 + capacity: + cpu: "2" + gpu: "0" + memory: 8Gi + instanceType: m5.large + region: eu-west-1 + zone: eu-west-1a +provider: AWS +providerSpec: + ami: ami-0xxxx + blockDevices: + - ebs: + deleteOnTermination: true + encrypted: true + volumeSize: 50 + volumeType: gp2 + machineType: m5.large + networkInterfaces: + - securityGroupIDs: + - sg-0xxxx + subnetID: subnet-0xxxx + region: eu-west-1 + srcAndDstChecksEnabled: false + tags: + kubernetes.io/arch: amd64 + kubernetes.io/role/integration-test: "1" + networking.gardener.cloud/node-local-dns-enabled: "true" + node.kubernetes.io/role: node + worker.garden.sapcloud.io/group: worker-1 + worker.gardener.cloud/cri-name: containerd + worker.gardener.cloud/gardener-node-agent-secret-name: gardener-node-agent-worker-1xxxx + worker.gardener.cloud/pool: worker-1 + worker.gardener.cloud/system-components: "true" +secretRef: + name: test-secret + namespace: test-ns diff --git a/pkg/test/integration/common/framework.go b/pkg/test/integration/common/framework.go index 25eb3c6ee3..7d53369567 100644 --- a/pkg/test/integration/common/framework.go +++ b/pkg/test/integration/common/framework.go @@ -25,7 +25,6 @@ import ( "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" - appsV1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" @@ -45,36 +44,25 @@ const ( var ( // path for storing log files (mcm & mc processes) targetDir = filepath.Join("..", "..", "..", ".ci", "controllers-test", "logs") - // Suffix for the`kubernetes-io-cluster` tag and cluster name for the orphan resource tracker. Used as ResourceGroupName for Azure clusters - targetClusterName = os.Getenv("TARGET_CLUSTER_NAME") // machine-controller-manager log file mcmLogFile = filepath.Join(targetDir, "mcm_process.log") - // machine-controller log file mcLogFile = filepath.Join(targetDir, "mc_process.log") + // Suffix for the `kubernetes-io-cluster` tag and cluster name for the orphan resource tracker. Used as ResourceGroupName for Azure clusters + targetClusterName string + // relative path to clone machine-controller-manager repo. // used when control cluster is shoot cluster mcmRepoPath = filepath.Join("..", "..", "..", "dev", "mcm") // control cluster namespace to create resources. // ignored if the target cluster is a shoot of the control cluster - controlClusterNamespace = os.Getenv("CONTROL_CLUSTER_NAMESPACE") - - // mcContainerPrefix is the prefix used for the container name of - // machine controller in the MCM deployment - // eg: machine-controller-manager-provider- - mcContainerPrefix = "machine-controller-manager-provider-" + controlClusterNamespace string // make processes/sessions started by gexec. available only if the controllers are running in local setup. updated during runtime mcmsession, mcsession *gexec.Session - // mcmDeploymentOrigObj a placeholder for mcm deployment object running in seed cluster. - // it will be scaled down to 0 before test starts. - // also used in cleanup to restore the controllers to its original state. - // used only if control cluster is seed - mcmDeploymentOrigObj *appsV1.Deployment - // machineControllerManagerDeploymentName specifies the name of the deployment // running the mc and mcm containers in it. machineControllerManagerDeploymentName = os.Getenv("MACHINE_CONTROLLER_MANAGER_DEPLOYMENT_NAME") @@ -85,28 +73,33 @@ var ( // path for v1machineclass yaml file to be used while creating machine resources // name of the machineclass will always be test-mc-v1. overriding the name of machineclass in yaml file // ignored if control cluster is seed cluster - v1MachineClassPath = os.Getenv("MACHINECLASS_V1") + v1MachineClassPath string // path for v1machineclass yaml file to be used while upgrading machine deployment // if machineClassV2 is not set then v1MachineClassPath will be used intead for creating test-mc-v2 class // ignored if control cluster if seed cluster - v2MachineClassPath = os.Getenv("MACHINECLASS_V2") + v2MachineClassPath string // if true, means that the tags present on VM are strings not key-value pairs - isTagsStrings = os.Getenv("TAGS_ARE_STRINGS") + isTagsStrings string // if true, control cluster is a seed // only set this variable if operating in gardener context - isControlSeed = os.Getenv("IS_CONTROL_CLUSTER_SEED") + isControlSeed string //values for gardener-node-agent-secret-name - gnaSecretNameLabelValue = os.Getenv("GNA_SECRET_NAME") + gnaSecretNameLabelValue string // Specifies whether the CRDs should be preserved during cleanup at the end - preserveCRDDuringCleanup = os.Getenv("PRESERVE_CRD_AT_END") + preserveCRDDuringCleanup string + + // Are the tests running for the simulated provider + isSimulatedProvider bool - // Are the tests running for the virtual provider - isVirtualProvider = os.Getenv("IS_VIRTUAL_PROVIDER") + // Path to the control plane cluster's kubeconfig + controlKubeConfigPath string + // Path to the target cluster's kubeconfig + targetKubeConfigPath string ) // ProviderSpecPatch struct holds tags for provider, which we want to patch the machineclass with @@ -176,12 +169,31 @@ func NewIntegrationTestFramework( } } -func (c *IntegrationTestFramework) initalizeClusters() error { +func setGlobalsFromEnvVars() { + controlKubeConfigPath = os.Getenv("CONTROL_KUBECONFIG") + targetKubeConfigPath = os.Getenv("TARGET_KUBECONFIG") + targetClusterName = os.Getenv("TARGET_CLUSTER_NAME") + controlClusterNamespace = os.Getenv("CONTROL_CLUSTER_NAMESPACE") + isControlSeed = os.Getenv("IS_CONTROL_CLUSTER_SEED") + v1MachineClassPath = os.Getenv("MACHINECLASS_V1") + v2MachineClassPath = os.Getenv("MACHINECLASS_V2") + if os.Getenv("SIM_PROVIDER") == "true" { + isSimulatedProvider = true + // This repo path is relative to where the test is being invoked from, + // for the simulated provider, the mcmRepo lies at the root. + mcmRepoPath = filepath.Join("..", "..", "..", "..", "..") + // Logs are saved in 'pkg/simulatedprovider/test/controller/integration/logs' + mcmLogFile = filepath.Join("logs", "mcm_process.log") + mcLogFile = filepath.Join("logs", "mc_process.log") + } + isTagsStrings = os.Getenv("TAGS_ARE_STRINGS") + gnaSecretNameLabelValue = os.Getenv("GNA_SECRET_NAME") + preserveCRDDuringCleanup = os.Getenv("PRESERVE_CRD_AT_END") +} + +func (c *IntegrationTestFramework) initializeClusters() error { // checks for the validity of controlKubeConfig and targetKubeConfig clusters // and intializes clientsets - controlKubeConfigPath := os.Getenv("CONTROL_KUBECONFIG") - targetKubeConfigPath := os.Getenv("TARGET_KUBECONFIG") - if len(controlKubeConfigPath) != 0 { controlKubeConfigPath, _ = filepath.Abs(controlKubeConfigPath) @@ -457,6 +469,7 @@ func (c *IntegrationTestFramework) setupMachineClass() error { Annotations: machineClass.ObjectMeta.Annotations, }, ProviderSpec: machineClass.ProviderSpec, + NodeTemplate: machineClass.NodeTemplate, SecretRef: machineClass.SecretRef, CredentialsSecretRef: machineClass.CredentialsSecretRef, Provider: machineClass.Provider, @@ -469,6 +482,10 @@ func (c *IntegrationTestFramework) setupMachineClass() error { } } + // No mcc patching necessary for the simulated provider + if isSimulatedProvider { + return nil + } //patching the integration test machineclasses with IT role tag if err := c.patchIntegrationTestMachineClasses(); err != nil { return err @@ -556,20 +573,23 @@ func (c *IntegrationTestFramework) SetupBeforeSuite() { ctx := context.Background() log.SetOutput(ginkgo.GinkgoWriter) + ginkgo.By("Setting global values using the passed environment variables") + setGlobalsFromEnvVars() + ginkgo.By("Checking for the clusters if provided are available") - gomega.Expect(c.initalizeClusters()).To(gomega.BeNil()) + gomega.Expect(c.initializeClusters()).To(gomega.BeNil()) ginkgo.By("Killing any existing processes") stopMCM(ctx) - //setting up MCM either locally or by deploying after checking conditions + //setting up MCM either locally or by deploying after checking conditions checkMcmRepoAvailable() - // When running the IT with virtual provider, there's no need to - // scale-down MC processes since none are running in the virtual cluster. - // Additionally, currently whenever running IT with the virtual provider, + // When running the IT with simulated provider, there's no need to + // scale-down MC processes since none are running in the simulated cluster. + // Additionally, currently whenever running IT with the simulated provider, // ControlCluster is assumed to be a seed since that simplifies the setup. - if isControlSeed == "true" && isVirtualProvider != "true" { + if isControlSeed == "true" && !isSimulatedProvider { ginkgo.By("Scaledown existing machine controllers") gomega.Expect(c.scaleMcmDeployment(0)).To(gomega.BeNil()) } else if isControlSeed != "true" { @@ -606,13 +626,17 @@ func (c *IntegrationTestFramework) SetupBeforeSuite() { clusterName := targetClusterName ginkgo.By("Looking for secrets refered in machineclass in the control cluster") - secretData, err := c.ControlCluster. - GetSecretData( - machineClass.Name, - machineClass.SecretRef, - machineClass.CredentialsSecretRef, - ) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) + var secretData map[string][]byte + gomega.Eventually(func() error { + var err error + secretData, err = c.ControlCluster. + GetSecretData( + machineClass.Name, + machineClass.SecretRef, + machineClass.CredentialsSecretRef, + ) + return err + }, c.timeout, c.pollingInterval).ShouldNot(gomega.HaveOccurred()) ginkgo.By("Initializing orphan resource tracker") err = c.resourcesTracker.InitializeResourcesTracker(machineClass, secretData, clusterName) @@ -647,10 +671,10 @@ func (c *IntegrationTestFramework) ControllerTests() { var initialNodes int16 ginkgo.Context("creation", func() { ginkgo.It("should not lead to any errors and add 1 more node in target cluster", func() { - // In case of existing deployments creating nodes when starting virtual + // In case of existing deployments creating nodes when starting simulated // provider, the change in node count can be >1, this delay prevents // checking node count immediately to allow for a correct initial count - if isVirtualProvider == "true" { + if isSimulatedProvider { time.Sleep(2 * time.Second) } // Probe nodes currently available in target cluster @@ -989,6 +1013,14 @@ func (c *IntegrationTestFramework) Cleanup() { ctx := context.Background() ginkgo.By("Running Cleanup") + // No other cleanup is needed when running with the virtual cluster + // only stop the MCM and the MC processes + if isSimulatedProvider { + ginkgo.By("Killing any existing processes") + stopMCM(ctx) + return + } + //running locally, means none of the image is specified for range 5 { if mcsession.ExitCode() != -1 {