From 05bc6d8057009a5a6399cd67f88ba6c9bd69eb98 Mon Sep 17 00:00:00 2001 From: Paul Maidment Date: Thu, 7 Sep 2023 00:52:15 +0300 Subject: [PATCH] MGMT-15704: Assisted service should create Day2 import CR for hub cluster. (#5459) When using assisted-service in KubeAPI mode, we want to import the local cluster automatically, allowing it to be managed in a similar fashion to the spoke cluster. The intent is to allow the user to perform day2 operations on the local cluster, to allow the addition of workers and so on. Presently this is only possible via manual efforts and is not very customer friendly. This PR aims to resolve this by adding functionality to import the local cluster as described above. --- cmd/main.go | 24 + config/rbac/role.yaml | 7 + ...ervice-operator.clusterserviceversion.yaml | 7 + go.mod | 1 + .../clusterdeployments_controller.go | 3 +- .../import_local_cluster.go | 276 ++++++++ .../import_local_cluster_test.go | 609 ++++++++++++++++++ .../local_cluster_import_operations.go | 203 ++++++ .../local_cluster_import_operations_mocks.go | 228 +++++++ 9 files changed, 1357 insertions(+), 1 deletion(-) create mode 100644 pkg/localclusterimport/import_local_cluster.go create mode 100644 pkg/localclusterimport/import_local_cluster_test.go create mode 100644 pkg/localclusterimport/local_cluster_import_operations.go create mode 100644 pkg/localclusterimport/local_cluster_import_operations_mocks.go diff --git a/cmd/main.go b/cmd/main.go index 80bcff04e1..de328fde5b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -63,6 +63,7 @@ import ( "github.com/openshift/assisted-service/pkg/generator" "github.com/openshift/assisted-service/pkg/k8sclient" "github.com/openshift/assisted-service/pkg/leader" + "github.com/openshift/assisted-service/pkg/localclusterimport" logconfig "github.com/openshift/assisted-service/pkg/log" "github.com/openshift/assisted-service/pkg/mirrorregistries" "github.com/openshift/assisted-service/pkg/ocm" @@ -162,6 +163,8 @@ var Options struct { AllowConvergedFlow bool `envconfig:"ALLOW_CONVERGED_FLOW" default:"true"` PreprovisioningImageControllerConfig controllers.PreprovisioningImageControllerConfig BMACConfig controllers.BMACConfig + EnableLocalClusterImport bool `envconfig:"ENABLE_LOCAL_CLUSTER_IMPORT" default:"true"` + LocalClusterImportNamespace string `envconfig:"LOCAL_CLUSTER_IMPORT_NAMESPACE" defualt:"local-agent-cluster"` // Directory containing pre-generated TLS certs/keys for the ephemeral installer ClusterTLSCertOverrideDir string `envconfig:"EPHEMERAL_INSTALLER_CLUSTER_TLS_CERTS_OVERRIDE_DIR" default:""` @@ -205,6 +208,23 @@ func maxDuration(dur time.Duration, durations ...time.Duration) time.Duration { return ret } +func importLocalCluster(ctrlMgr manager.Manager, log *logrus.Logger) { + if !Options.EnableLocalClusterImport { + log.Info("EnableLocalClusterImport disabled in options, skipping...") + return + } + // Splitting into API reader and writer interfaces as the reader bypasses the cache settings that are on the regular client + // and we need to be able to read secrets that we cannot read with the client due to how the cache is configured. + // The cachedApiClient can be used for writes as these are unaffected by cache. + localClusterImportOperations := localclusterimport.NewLocalClusterImportOperations(ctrlMgr.GetAPIReader(), ctrlMgr.GetClient(), log) + localClusterImport := localclusterimport.NewLocalClusterImport(&localClusterImportOperations, Options.LocalClusterImportNamespace, log) + err := localClusterImport.ImportLocalCluster() + if err != nil { + // Failure to import the local cluster is not fatal but we should warn in the log. + log.Warnf("Could not import local cluster due to error %s", err.Error()) + } +} + func main() { err := envconfig.Process(common.EnvConfigPrefix, &Options) if err == nil { @@ -641,6 +661,10 @@ func main() { } }() + if Options.EnableKubeAPI { + importLocalCluster(ctrlMgr, log) + } + // Interrupt servers on SIGINT/SIGTERM stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index b8d57316d6..07cbffaa19 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -273,6 +273,12 @@ rules: - get - list - watch +- apiGroups: + - config.openshift.io + resources: + - dnses + verbs: + - get - apiGroups: - config.openshift.io resources: @@ -410,6 +416,7 @@ rules: resources: - clusterimagesets verbs: + - create - get - list - watch diff --git a/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml b/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml index e9a8b58e8c..aee4321a70 100644 --- a/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/manifests/assisted-service-operator.clusterserviceversion.yaml @@ -618,6 +618,12 @@ spec: - get - list - watch + - apiGroups: + - config.openshift.io + resources: + - dnses + verbs: + - get - apiGroups: - config.openshift.io resources: @@ -755,6 +761,7 @@ spec: resources: - clusterimagesets verbs: + - create - get - list - watch diff --git a/go.mod b/go.mod index b98e62f882..76f89ced1d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/openshift/assisted-service go 1.18 require ( + github.com/hashicorp/go-multierror v1.1.1 github.com/IBM/netaddr v1.5.0 github.com/NYTimes/gziphandler v1.1.1 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d diff --git a/internal/controller/controllers/clusterdeployments_controller.go b/internal/controller/controllers/clusterdeployments_controller.go index 7605c9f62f..11cb0eee81 100644 --- a/internal/controller/controllers/clusterdeployments_controller.go +++ b/internal/controller/controllers/clusterdeployments_controller.go @@ -111,10 +111,11 @@ const minimalOpenShiftVersionForDefaultNetworkTypeOVNKubernetes = "4.12.0-0.0" // +kubebuilder:rbac:groups=hive.openshift.io,resources=clusterdeployments,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=hive.openshift.io,resources=clusterdeployments/status,verbs=get;update;patch // +kubebuilder:rbac:groups=hive.openshift.io,resources=clusterdeployments/finalizers,verbs=update -// +kubebuilder:rbac:groups=hive.openshift.io,resources=clusterimagesets,verbs=get;list;watch +// +kubebuilder:rbac:groups=hive.openshift.io,resources=clusterimagesets,verbs=get;list;watch;create // +kubebuilder:rbac:groups=extensions.hive.openshift.io,resources=agentclusterinstalls,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=extensions.hive.openshift.io,resources=agentclusterinstalls/status,verbs=get;update;patch // +kubebuilder:rbac:groups=extensions.hive.openshift.io,resources=agentclusterinstalls/finalizers,verbs=update +// +kubebuilder:rbac:groups=config.openshift.io,resources=dnses,verbs=get func (r *ClusterDeploymentsReconciler) Reconcile(origCtx context.Context, req ctrl.Request) (ctrl.Result, error) { ctx := addRequestIdIfNeeded(origCtx) diff --git a/pkg/localclusterimport/import_local_cluster.go b/pkg/localclusterimport/import_local_cluster.go new file mode 100644 index 0000000000..fc18f372c6 --- /dev/null +++ b/pkg/localclusterimport/import_local_cluster.go @@ -0,0 +1,276 @@ +package localclusterimport + +import ( + "encoding/base64" + "errors" + "fmt" + + multierror "github.com/hashicorp/go-multierror" + configv1 "github.com/openshift/api/config/v1" + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/hive/apis/hive/v1/agent" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type LocalClusterImport struct { + clusterImportOperations ClusterImportOperations + localClusterNamespace string + log *logrus.Logger +} + +func NewLocalClusterImport(localClusterImportOperations ClusterImportOperations, localClusterNamespace string, log *logrus.Logger) LocalClusterImport { + return LocalClusterImport{clusterImportOperations: localClusterImportOperations, log: log, localClusterNamespace: localClusterNamespace} +} + +func (i *LocalClusterImport) createClusterImageSet(release_image string) error { + var err error + clusterImageSet := hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "local-cluster-image-set", + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: release_image, + }, + } + err = i.clusterImportOperations.CreateClusterImageSet(&clusterImageSet) + if err != nil && !k8serrors.IsAlreadyExists(err) { + i.log.Errorf("unable to create ClusterImageSet due to error: %s", err.Error()) + return err + } + return nil +} + +func (i *LocalClusterImport) createAdminKubeConfig(kubeConfigSecret *v1.Secret) error { + var err error + // Store the kubeconfig data in the local cluster namespace + localClusterSecret := v1.Secret{} + localClusterSecret.Name = fmt.Sprintf("%s-admin-kubeconfig", i.localClusterNamespace) + localClusterSecret.Namespace = i.localClusterNamespace + localClusterSecret.Data = make(map[string][]byte) + localClusterSecret.Data["kubeconfig"] = kubeConfigSecret.Data["lb-ext.kubeconfig"] + err = i.clusterImportOperations.CreateSecret(i.localClusterNamespace, &localClusterSecret) + if err != nil && !k8serrors.IsAlreadyExists(err) { + i.log.Errorf("to store secret due to error %s", err.Error()) + return err + } + return nil +} + +func (i *LocalClusterImport) createLocalClusterPullSecret(sourceSecret *v1.Secret) error { + var err error + // Store the pull secret in the local cluster namespace + hubPullSecret := v1.Secret{} + hubPullSecret.Name = sourceSecret.Name + hubPullSecret.Namespace = i.localClusterNamespace + hubPullSecret.Data = make(map[string][]byte) + // .dockerconfigjson is double base64 encoded for some reason. + // simply obtaining the secret above will perform one layer of decoding. + // we need to manually perform another to ensure that the data is correctly copied to the new secret. + hubPullSecret.Data[".dockerconfigjson"], err = base64.StdEncoding.DecodeString(string(sourceSecret.Data[".dockerconfigjson"])) + if err != nil { + i.log.Errorf("unable to decode base64 pull secret data due to error %s", err.Error()) + return err + } + hubPullSecret.OwnerReferences = []metav1.OwnerReference{} + err = i.clusterImportOperations.CreateSecret(i.localClusterNamespace, &hubPullSecret) + if err != nil && !k8serrors.IsAlreadyExists(err) { + i.log.Errorf("unable to store hub pull secret due to error %s", err.Error()) + return err + } + return nil +} + +func (i *LocalClusterImport) createAgentClusterInstall(numberOfControlPlaneNodes int) (*hiveext.AgentClusterInstall, error) { + //Create an AgentClusterInstall in the local cluster namespace + userManagedNetworkingActive := true + agentClusterInstall := &hiveext.AgentClusterInstall{ + Spec: hiveext.AgentClusterInstallSpec{ + Networking: hiveext.Networking{ + UserManagedNetworking: &userManagedNetworkingActive, + }, + ClusterDeploymentRef: v1.LocalObjectReference{ + Name: i.localClusterNamespace + "-cluster-deployment", + }, + ImageSetRef: &hivev1.ClusterImageSetReference{ + Name: "local-cluster-image-set", + }, + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: numberOfControlPlaneNodes, + }, + }, + } + agentClusterInstall.Namespace = i.localClusterNamespace + agentClusterInstall.Name = i.localClusterNamespace + "-cluster-install" + err := i.clusterImportOperations.CreateAgentClusterInstall(agentClusterInstall) + if err != nil && !k8serrors.IsAlreadyExists(err) { + i.log.Errorf("could not create AgentClusterInstall due to error %s", err.Error()) + return nil, err + } + // Fetch the recently stored AgentClusterInstall so that we can obtain the UID + aci, err := i.clusterImportOperations.GetAgentClusterInstall(i.localClusterNamespace, i.localClusterNamespace+"-cluster-install") + if err != nil { + i.log.Errorf("failed to fetch created AgentClusterInstall due to error %s", err.Error()) + return nil, err + } + return aci, nil +} + +func (i *LocalClusterImport) createClusterDeployment(pullSecret *v1.Secret, dns *configv1.DNS, kubeConfigSecret *v1.Secret, agentClusterInstall *hiveext.AgentClusterInstall) error { + if pullSecret == nil || dns == nil || kubeConfigSecret == nil || agentClusterInstall == nil { + return nil + } + // Create a cluster deployment in the local cluster namespace + clusterDeployment := &hivev1.ClusterDeployment{ + Spec: hivev1.ClusterDeploymentSpec{ + Installed: true, // Let assisted know to import this cluster + ClusterMetadata: &hivev1.ClusterMetadata{ + ClusterID: "", + InfraID: "", + AdminKubeconfigSecretRef: v1.LocalObjectReference{Name: fmt.Sprintf("%s-admin-kubeconfig", i.localClusterNamespace)}, + }, + ClusterInstallRef: &hivev1.ClusterInstallLocalReference{ + Name: i.localClusterNamespace + "-cluster-install", + Group: "extensions.hive.openshift.io", + Kind: "AgentClusterInstall", + Version: "v1beta1", + }, + Platform: hivev1.Platform{ + AgentBareMetal: &agent.BareMetalPlatform{ + AgentSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"infraenv": "local-cluster"}, + }, + }, + }, + PullSecretRef: &v1.LocalObjectReference{ + Name: pullSecret.Name, + }, + }, + } + clusterDeployment.Name = i.localClusterNamespace + "-cluster-deployment" + clusterDeployment.Namespace = i.localClusterNamespace + clusterDeployment.Spec.ClusterName = i.localClusterNamespace + "-cluster-deployment" + clusterDeployment.Spec.BaseDomain = dns.Spec.BaseDomain + // + // Adding this ownership reference to ensure we can submit clusterDeployment without ManagedClusterSet/join permission + // + // Must match https://github.com/stolostron/multicloud-operators-foundation/blob/0001f46a5115fe43c606a068e5e7ee00abec3b68/pkg/webhook/clusterset/validatingWebhook.go#L44 + // or the ClusterDeployment will be rejected during admission. + agentClusterInstallOwnerRef := metav1.OwnerReference{ + Kind: "AgentCluster", + APIVersion: "capi-provider.agent-install.openshift.io/v1alpha1", + Name: i.localClusterNamespace + "-cluster-install", + UID: agentClusterInstall.UID, + } + clusterDeployment.OwnerReferences = []metav1.OwnerReference{agentClusterInstallOwnerRef} + err := i.clusterImportOperations.CreateClusterDeployment(clusterDeployment) + if err != nil && !k8serrors.IsAlreadyExists(err) { + i.log.Errorf("could not create ClusterDeployment due to error %s", err.Error()) + return err + } + return nil +} + +func (i *LocalClusterImport) createNamespace(name string) error { + err := i.clusterImportOperations.CreateNamespace(name) + if err != nil && !k8serrors.IsAlreadyExists(err) { + i.log.Errorf("could not create Namespace due to error %s", err.Error()) + return err + } + return nil +} + +func (i *LocalClusterImport) ImportLocalCluster() error { + + var errorList error + + clusterVersion, err := i.clusterImportOperations.GetClusterVersion("version") + if err != nil { + i.log.Errorf("unable to find cluster version info due to error: %s", err.Error()) + errorList = multierror.Append(nil, err) + } + + release_image := "" + if clusterVersion != nil && clusterVersion.Status.History[0].State != configv1.CompletedUpdate { + message := "the release image in the cluster version is not ready yet, please wait for installation to complete and then restart the assisted-service" + i.log.Error(message) + errorList = multierror.Append(errorList, errors.New(message)) + } + + kubeConfigSecret, err := i.clusterImportOperations.GetSecret("openshift-kube-apiserver", "node-kubeconfigs") + if err != nil { + i.log.Errorf("unable to fetch local cluster kubeconfigs due to error %s", err.Error()) + errorList = multierror.Append(errorList, err) + } + + pullSecret, err := i.clusterImportOperations.GetSecret("openshift-machine-api", "pull-secret") + if err != nil { + i.log.Errorf("unable to fetch pull secret due to error %s", err.Error()) + errorList = multierror.Append(errorList, err) + } + + dns, err := i.clusterImportOperations.GetClusterDNS() + if err != nil { + i.log.Errorf("could not fetch DNS due to error %s", err.Error()) + errorList = multierror.Append(errorList, err) + } + + numberOfControlPlaneNodes, err := i.clusterImportOperations.GetNumberOfControlPlaneNodes() + if err != nil { + i.log.Errorf("unable to determine the number of control plane nodes due to error %s", err.Error()) + errorList = multierror.Append(errorList, err) + } + + // If we already have errors before we start writing things, then let's stop + // better to let the user fix the problems than have to delete things later. + if errorList != nil { + return errorList + } + + release_image = clusterVersion.Status.History[0].Image + + err = i.createClusterImageSet(release_image) + if err != nil { + errorList = multierror.Append(errorList, err) + } + + err = i.createNamespace(i.localClusterNamespace) + if err != nil && !k8serrors.IsAlreadyExists(err) { + errorList = multierror.Append(errorList, err) + } + + if kubeConfigSecret != nil { + err = i.createAdminKubeConfig(kubeConfigSecret) + if err != nil && !k8serrors.IsAlreadyExists(err) { + errorList = multierror.Append(errorList, err) + } + } + + if pullSecret != nil { + err = i.createLocalClusterPullSecret(pullSecret) + if err != nil && !k8serrors.IsAlreadyExists(err) { + errorList = multierror.Append(errorList, err) + } + } + + if numberOfControlPlaneNodes > 0 { + agentClusterInstall, err := i.createAgentClusterInstall(numberOfControlPlaneNodes) + if err != nil && !k8serrors.IsAlreadyExists(err) { + errorList = multierror.Append(errorList, err) + } + err = i.createClusterDeployment(pullSecret, dns, kubeConfigSecret, agentClusterInstall) + if err != nil && !k8serrors.IsAlreadyExists(err) { + errorList = multierror.Append(errorList, err) + } + } + + if errorList != nil { + return errorList + } + + i.log.Info("completed Day2 import of hub cluster") + return nil +} diff --git a/pkg/localclusterimport/import_local_cluster_test.go b/pkg/localclusterimport/import_local_cluster_test.go new file mode 100644 index 0000000000..26ca6b0d8e --- /dev/null +++ b/pkg/localclusterimport/import_local_cluster_test.go @@ -0,0 +1,609 @@ +package localclusterimport + +import ( + "encoding/base64" + "fmt" + "testing" + + gomock "github.com/golang/mock/gomock" + "github.com/google/uuid" + "github.com/hashicorp/go-multierror" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + configv1 "github.com/openshift/api/config/v1" + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/hive/apis/hive/v1/agent" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("ImportLocalCluster", func() { + + var ( + ctrl *gomock.Controller + clusterImportOperations *MockClusterImportOperations + logger *logrus.Logger + localClusterImport LocalClusterImport + nodeConfigsKubeConfig string + pullSecret []byte + pullSecretBase64Encoded []byte + agentClusterInstallUID types.UID + releaseImage string + localClusterNamespace string + ) + + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + clusterImportOperations = NewMockClusterImportOperations(ctrl) + logger = logrus.New() + localClusterNamespace = "some-cluster-namespace" + localClusterImport = NewLocalClusterImport(clusterImportOperations, localClusterNamespace, logger) + nodeConfigsKubeConfig = "someKubeConfig" + // The openshift-kube-apiserver pull-secret is double base64 encoded + // The first layer of encoding is transparent to us when using the client + // The final layer needs to be encoded/decoded + pullSecret = []byte("pullSecret") + pullSecretBase64Encoded = []byte(base64.StdEncoding.EncodeToString(pullSecret)) + agentClusterInstallUID = types.UID(uuid.NewString()) + releaseImage = "quay.io/openshift-release-dev/ocp-release@sha256:a266d3d65c433b460cdef7ab5d6531580f5391adbe85d9c475208a56452e4c0b" + + }) + + var mockCreateNamespace = func() *gomock.Call { + namespace := &v1.Namespace{} + namespace.Name = localClusterNamespace + return clusterImportOperations.EXPECT(). + CreateNamespace(localClusterNamespace). + Return(nil) + } + + var mockAgentClusterInstallPresent = func() *gomock.Call { + agentClusterInstall := &hiveext.AgentClusterInstall{ + ObjectMeta: metav1.ObjectMeta{ + UID: agentClusterInstallUID, + }, + } + return clusterImportOperations.EXPECT(). + GetAgentClusterInstall(localClusterNamespace, fmt.Sprintf("%s-cluster-install", localClusterNamespace)). + Return(agentClusterInstall, nil) + } + + var mockNoClusterVersionFound = func() *gomock.Call { + return clusterImportOperations.EXPECT(). + GetClusterVersion("version"). + Return(nil, k8serrors.NewNotFound( + schema.GroupResource{Group: "v1", Resource: "ClusterVersion"}, + "version", + )) + } + + var mockClusterVersionFound = func() *gomock.Call { + cv := &configv1.ClusterVersion{ + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + {Version: "4.13.0", Image: releaseImage, State: configv1.CompletedUpdate}, + }, + }, + } + return clusterImportOperations.EXPECT(). + GetClusterVersion("version"). + Return(cv, nil) + } + + var mockFailedToCreateClusterImageSet = func() *gomock.Call { + clusterImageSet := hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "local-cluster-image-set", + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: releaseImage, + }, + } + return clusterImportOperations.EXPECT(). + CreateClusterImageSet(&clusterImageSet). + Return(k8serrors.NewBadRequest("Invalid parameters")) + } + + var mockCreateClusterImageSet = func() *gomock.Call { + clusterImageSet := hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "local-cluster-image-set", + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: releaseImage, + }, + } + return clusterImportOperations.EXPECT(). + CreateClusterImageSet(&clusterImageSet). + Return(nil) + } + + var mockNodeKubeConfigsNotFound = func() *gomock.Call { + return clusterImportOperations.EXPECT(). + GetSecret("openshift-kube-apiserver", "node-kubeconfigs"). + Return(nil, k8serrors.NewNotFound( + schema.GroupResource{Group: "v1", Resource: "Secret"}, + "node-kubeconfigs", + )) + } + + var mockNodeKubeConfigsFound = func() *gomock.Call { + s := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "openshift-kube-apiserver", + Name: "node-kubeconfigs", + }, + Data: map[string][]byte{ + "lb-ext.kubeconfig": []byte(nodeConfigsKubeConfig), + }, + } + return clusterImportOperations.EXPECT(). + GetSecret("openshift-kube-apiserver", "node-kubeconfigs"). + Return(s, nil) + } + + var mockLocalClusterPullSecretNotFound = func() *gomock.Call { + return clusterImportOperations.EXPECT(). + GetSecret("openshift-machine-api", "pull-secret"). + Return(nil, k8serrors.NewNotFound( + schema.GroupResource{Group: "v1", Resource: "Secret"}, + "pull-secret", + )) + } + + var mockLocalClusterPullSecretFound = func() *gomock.Call { + s := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pull-secret", + Namespace: localClusterNamespace, + }, + Data: map[string][]byte{ + ".dockerconfigjson": pullSecretBase64Encoded, + }, + } + return clusterImportOperations.EXPECT(). + GetSecret("openshift-machine-api", "pull-secret"). + Return(s, nil) + } + + var mockClusterDNSNotFound = func() *gomock.Call { + return clusterImportOperations.EXPECT(). + GetClusterDNS(). + Return(nil, k8serrors.NewNotFound( + schema.GroupResource{Group: "config.openshift.io/v1", Resource: "DNS"}, + "cluster", + )) + } + + var mockClusterDNSFound = func() *gomock.Call { + dns := &configv1.DNS{ + Spec: configv1.DNSSpec{ + BaseDomain: "foobar.local", + }, + } + return clusterImportOperations.EXPECT(). + GetClusterDNS(). + Return(dns, nil) + } + + var mockNumberOfControlPlaneNodesFound = func() *gomock.Call { + return clusterImportOperations.EXPECT(). + GetNumberOfControlPlaneNodes(). + Return(3, nil) + } + + var mockFailedToCreateLocalClusterAdminKubeConfig = func() *gomock.Call { + localClusterSecret := v1.Secret{} + localClusterSecret.Name = fmt.Sprintf("%s-admin-kubeconfig", localClusterNamespace) + localClusterSecret.Namespace = localClusterNamespace + localClusterSecret.Data = make(map[string][]byte) + localClusterSecret.Data["kubeconfig"] = []byte(nodeConfigsKubeConfig) + return clusterImportOperations.EXPECT(). + CreateSecret(localClusterNamespace, &localClusterSecret). + Return(k8serrors.NewBadRequest("Invalid parameters")) + } + + var mockCreateLocalClusterAdminKubeConfig = func() *gomock.Call { + localClusterSecret := v1.Secret{} + localClusterSecret.Name = fmt.Sprintf("%s-admin-kubeconfig", localClusterNamespace) + localClusterSecret.Namespace = localClusterNamespace + localClusterSecret.Data = make(map[string][]byte) + localClusterSecret.Data["kubeconfig"] = []byte(nodeConfigsKubeConfig) + return clusterImportOperations.EXPECT(). + CreateSecret(localClusterNamespace, &localClusterSecret). + Return(nil) + } + + var mockFailedToCreateLocalClusterPullSecret = func() *gomock.Call { + hubPullSecret := v1.Secret{} + hubPullSecret.Name = "pull-secret" + hubPullSecret.Namespace = localClusterNamespace + hubPullSecret.Data = make(map[string][]byte) + hubPullSecret.OwnerReferences = []metav1.OwnerReference{} + // .dockerconfigjson is double base64 encoded for some reason. + // simply obtaining the secret above will perform one layer of decoding. + // we need to manually perform another to ensure that the data is correctly copied to the new secret. + hubPullSecret.Data[".dockerconfigjson"] = pullSecret + return clusterImportOperations.EXPECT(). + CreateSecret(localClusterNamespace, &hubPullSecret). + Return(k8serrors.NewBadRequest("Invalid parameters")) + } + + var mockCreateLocalClusterPullSecret = func() *gomock.Call { + hubPullSecret := v1.Secret{} + hubPullSecret.Name = "pull-secret" + hubPullSecret.Namespace = localClusterNamespace + hubPullSecret.Data = make(map[string][]byte) + hubPullSecret.OwnerReferences = []metav1.OwnerReference{} + // .dockerconfigjson is double base64 encoded for some reason. + // simply obtaining the secret above will perform one layer of decoding. + // we need to manually perform another to ensure that the data is correctly copied to the new secret. + hubPullSecret.Data[".dockerconfigjson"] = pullSecret + return clusterImportOperations.EXPECT(). + CreateSecret(localClusterNamespace, &hubPullSecret). + Return(nil) + } + + var mockAgentClusterInstall = func() hiveext.AgentClusterInstall { + userManagedNetworkingActive := true + agentClusterInstall := hiveext.AgentClusterInstall{ + Spec: hiveext.AgentClusterInstallSpec{ + Networking: hiveext.Networking{ + UserManagedNetworking: &userManagedNetworkingActive, + }, + ClusterDeploymentRef: v1.LocalObjectReference{ + Name: localClusterNamespace + "-cluster-deployment", + }, + ImageSetRef: &hivev1.ClusterImageSetReference{ + Name: "local-cluster-image-set", + }, + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: 3, + }, + }, + } + agentClusterInstall.Namespace = localClusterNamespace + agentClusterInstall.Name = localClusterNamespace + "-cluster-install" + return agentClusterInstall + } + + var mockFailedToCreateAgentClusterInstall = func() *gomock.Call { + agentClusterInstall := mockAgentClusterInstall() + return clusterImportOperations.EXPECT(). + CreateAgentClusterInstall(&agentClusterInstall). + Return(k8serrors.NewBadRequest("Invalid parameters")) + } + + var mockCreateAgentClusterInstall = func() *gomock.Call { + agentClusterInstall := mockAgentClusterInstall() + return clusterImportOperations.EXPECT(). + CreateAgentClusterInstall(&agentClusterInstall). + Return(nil) + } + + var mockClusterDeployment = func() *hivev1.ClusterDeployment { + clusterDeployment := &hivev1.ClusterDeployment{ + Spec: hivev1.ClusterDeploymentSpec{ + Installed: true, // Let assisted know to import this cluster + ClusterMetadata: &hivev1.ClusterMetadata{ + ClusterID: "", + InfraID: "", + AdminKubeconfigSecretRef: v1.LocalObjectReference{Name: fmt.Sprintf("%s-admin-kubeconfig", localClusterNamespace)}, + }, + ClusterInstallRef: &hivev1.ClusterInstallLocalReference{ + Name: localClusterNamespace + "-cluster-install", + Group: "extensions.hive.openshift.io", + Kind: "AgentClusterInstall", + Version: "v1beta1", + }, + Platform: hivev1.Platform{ + AgentBareMetal: &agent.BareMetalPlatform{ + AgentSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"infraenv": "local-cluster"}, + }, + }, + }, + PullSecretRef: &v1.LocalObjectReference{ + Name: "pull-secret", + }, + }, + } + clusterDeployment.Name = localClusterNamespace + "-cluster-deployment" + clusterDeployment.Namespace = localClusterNamespace + clusterDeployment.Spec.ClusterName = localClusterNamespace + "-cluster-deployment" + clusterDeployment.Spec.BaseDomain = "foobar.local" + // + // Adding this ownership reference to ensure we can submit clusterDeployment without ManagedClusterSet/join permission + // + clusterDeployment.OwnerReferences = []metav1.OwnerReference{{ + Kind: "AgentCluster", + APIVersion: "capi-provider.agent-install.openshift.io/v1alpha1", + Name: localClusterNamespace + "-cluster-install", + UID: agentClusterInstallUID, + }} + return clusterDeployment + } + + var mockFailedToCreateLocalClusterDeployment = func() *gomock.Call { + return clusterImportOperations.EXPECT(). + CreateClusterDeployment(mockClusterDeployment()). + Return(k8serrors.NewBadRequest("Invalid parameters")) + } + + var mockCreateLocalClusterDeployment = func() *gomock.Call { + clusterDeployment := mockClusterDeployment() + return clusterImportOperations.EXPECT(). + CreateClusterDeployment(clusterDeployment). + Return(nil) + } + + var mockAlreadyExistsOnCreateNamespace = func() *gomock.Call { + namespace := &v1.Namespace{} + namespace.Name = localClusterNamespace + return clusterImportOperations.EXPECT(). + CreateNamespace(localClusterNamespace). + Return( + k8serrors.NewAlreadyExists(schema.GroupResource{Group: "v1", Resource: "Namespace"}, + localClusterNamespace)) + } + + var mockAlreadyExistsOnCreateClusterImageSet = func() *gomock.Call { + clusterImageSet := hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "local-cluster-image-set", + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: releaseImage, + }, + } + return clusterImportOperations.EXPECT(). + CreateClusterImageSet(&clusterImageSet). + Return( + k8serrors.NewAlreadyExists(schema.GroupResource{Group: "hivev1", Resource: "ClusterImageSet"}, + "local-cluster-image-set")) + } + + var mockAlreadyExistsOnCreateLocalClusterAdminKubeConfig = func() *gomock.Call { + localClusterSecret := v1.Secret{} + localClusterSecret.Name = fmt.Sprintf("%s-admin-kubeconfig", localClusterNamespace) + localClusterSecret.Namespace = localClusterNamespace + localClusterSecret.Data = make(map[string][]byte) + localClusterSecret.Data["kubeconfig"] = []byte(nodeConfigsKubeConfig) + return clusterImportOperations.EXPECT(). + CreateSecret(localClusterNamespace, &localClusterSecret). + Return(k8serrors.NewAlreadyExists(schema.GroupResource{Group: "v1", Resource: "Secret"}, + localClusterSecret.Name)) + } + + var mockAlreadyExistsOnCreateLocalClusterPullSecret = func() *gomock.Call { + hubPullSecret := v1.Secret{} + hubPullSecret.Name = "pull-secret" + hubPullSecret.Namespace = localClusterNamespace + hubPullSecret.Data = make(map[string][]byte) + hubPullSecret.OwnerReferences = []metav1.OwnerReference{} + // .dockerconfigjson is double base64 encoded for some reason. + // simply obtaining the secret above will perform one layer of decoding. + // we need to manually perform another to ensure that the data is correctly copied to the new secret. + hubPullSecret.Data[".dockerconfigjson"] = pullSecret + return clusterImportOperations.EXPECT(). + CreateSecret(localClusterNamespace, &hubPullSecret). + Return(k8serrors.NewAlreadyExists(schema.GroupResource{Group: "v1", Resource: "Secret"}, + hubPullSecret.Name)) + } + + var mockAlreadyExistsOnCreateAgentClusterInstall = func() *gomock.Call { + agentClusterInstall := mockAgentClusterInstall() + return clusterImportOperations.EXPECT(). + CreateAgentClusterInstall(&agentClusterInstall). + Return(k8serrors.NewAlreadyExists(schema.GroupResource{Group: "hiveext", Resource: "AgentClusterInstall"}, + agentClusterInstall.Name)) + } + + var mockAlreadyExistsOnCreateLocalClusterDeployment = func() *gomock.Call { + clusterDeployment := mockClusterDeployment() + return clusterImportOperations.EXPECT(). + CreateClusterDeployment(clusterDeployment). + Return(k8serrors.NewAlreadyExists(schema.GroupResource{Group: "hivev1", Resource: "ClusterDeployment"}, + clusterDeployment.Name)) + } + + It("if no cluster version could be found then this should appear in multierrors", func() { + mockNoClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonNotFound)) + Expect(apiStatus.Status().Message).To(Equal(fmt.Sprintf("ClusterVersion.v1 \"%s\" not found", "version"))) + }) + + It("if node-kubeconfigs for the local cluster are not present then this should appear in multierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsNotFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonNotFound)) + Expect(apiStatus.Status().Message).To(Equal(fmt.Sprintf("Secret.v1 \"%s\" not found", "node-kubeconfigs"))) + }) + + It("if pull-secret for the local cluster is not present then this should appear in multierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretNotFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonNotFound)) + Expect(apiStatus.Status().Message).To(Equal(fmt.Sprintf("Secret.v1 \"%s\" not found", "pull-secret"))) + }) + + It("if dns for the local cluster is not present then this should appear in multierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSNotFound() + mockNumberOfControlPlaneNodesFound() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonNotFound)) + Expect(apiStatus.Status().Message).To(Equal(fmt.Sprintf("DNS.config.openshift.io/v1 \"%s\" not found", "cluster"))) + }) + + It("if unable to create image set, this should appear in multierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + mockFailedToCreateClusterImageSet() + mockCreateLocalClusterAdminKubeConfig() + mockCreateLocalClusterPullSecret() + mockCreateLocalClusterDeployment() + mockCreateAgentClusterInstall() + mockAgentClusterInstallPresent() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonBadRequest)) + Expect(apiStatus.Status().Message).To(Equal("Invalid parameters")) + }) + + It("if unable to create local cluster admin kubeconfig, this should appear in mutierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + mockCreateClusterImageSet() + mockFailedToCreateLocalClusterAdminKubeConfig() + mockCreateLocalClusterPullSecret() + mockCreateLocalClusterDeployment() + mockCreateAgentClusterInstall() + mockAgentClusterInstallPresent() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonBadRequest)) + Expect(apiStatus.Status().Message).To(Equal("Invalid parameters")) + }) + + It("if unable to create local cluster pull secret, this should appear in mutierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + mockCreateClusterImageSet() + mockCreateLocalClusterAdminKubeConfig() + mockFailedToCreateLocalClusterPullSecret() + mockCreateLocalClusterDeployment() + mockCreateAgentClusterInstall() + mockAgentClusterInstallPresent() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonBadRequest)) + Expect(apiStatus.Status().Message).To(Equal("Invalid parameters")) + }) + + It("if failed to create agent cluster install, this should appear in mutierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + mockCreateClusterImageSet() + mockCreateLocalClusterAdminKubeConfig() + mockCreateLocalClusterPullSecret() + mockCreateLocalClusterDeployment() + mockFailedToCreateAgentClusterInstall() + mockAgentClusterInstallPresent() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonBadRequest)) + Expect(apiStatus.Status().Message).To(Equal("Invalid parameters")) + }) + + It("if unable to create cluster deployment, this should appear in mutierrors", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + mockCreateClusterImageSet() + mockCreateLocalClusterAdminKubeConfig() + mockCreateLocalClusterPullSecret() + mockFailedToCreateLocalClusterDeployment() + mockCreateAgentClusterInstall() + mockAgentClusterInstallPresent() + result := localClusterImport.ImportLocalCluster() + multiErrors := result.(*multierror.Error) + apiStatus := multiErrors.Errors[0].(k8serrors.APIStatus) + Expect(apiStatus.Status().Reason).To(Equal(metav1.StatusReasonBadRequest)) + Expect(apiStatus.Status().Message).To(Equal("Invalid parameters")) + }) + + It("should have created all entities required to import local cluster", func() { + mockClusterVersionFound() + mockCreateNamespace() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + mockCreateClusterImageSet() + mockCreateLocalClusterAdminKubeConfig() + mockCreateLocalClusterPullSecret() + mockCreateAgentClusterInstall() + mockAgentClusterInstallPresent() + mockCreateLocalClusterDeployment() + result := localClusterImport.ImportLocalCluster() + Expect(result).To(BeNil()) + }) + + It("should not raise an error if entities already exist when attempting create", func() { + mockClusterVersionFound() + mockNodeKubeConfigsFound() + mockLocalClusterPullSecretFound() + mockClusterDNSFound() + mockNumberOfControlPlaneNodesFound() + mockAlreadyExistsOnCreateNamespace() + mockAlreadyExistsOnCreateClusterImageSet() + mockAlreadyExistsOnCreateLocalClusterAdminKubeConfig() + mockAlreadyExistsOnCreateLocalClusterPullSecret() + mockAlreadyExistsOnCreateAgentClusterInstall() + mockAlreadyExistsOnCreateLocalClusterDeployment() + mockAgentClusterInstallPresent() + result := localClusterImport.ImportLocalCluster() + Expect(result).To(BeNil()) + }) +}) + +func TestJob(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "ImportLocalCluster") +} diff --git a/pkg/localclusterimport/local_cluster_import_operations.go b/pkg/localclusterimport/local_cluster_import_operations.go new file mode 100644 index 0000000000..d7ca985205 --- /dev/null +++ b/pkg/localclusterimport/local_cluster_import_operations.go @@ -0,0 +1,203 @@ +package localclusterimport + +import ( + "context" + + configv1 "github.com/openshift/api/config/v1" + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +//go:generate mockgen -source=local_cluster_import_operations.go -package=localclusterimport -destination=local_cluster_import_operations_mocks.go +type ClusterImportOperations interface { + GetAgentClusterInstall(namespace string, name string) (*hiveext.AgentClusterInstall, error) + GetNamespace(name string) (*v1.Namespace, error) + GetSecret(namespace string, name string) (*v1.Secret, error) + GetClusterVersion(name string) (*configv1.ClusterVersion, error) + GetClusterImageSet(name string) (*hivev1.ClusterImageSet, error) + GetNodes() (*v1.NodeList, error) + GetNumberOfControlPlaneNodes() (int, error) + GetClusterDNS() (*configv1.DNS, error) + CreateAgentClusterInstall(agentClusterInstall *hiveext.AgentClusterInstall) error + CreateNamespace(name string) error + CreateSecret(namespace string, secret *v1.Secret) error + CreateClusterImageSet(clusterImageSet *hivev1.ClusterImageSet) error + CreateClusterDeployment(clusterDeployment *hivev1.ClusterDeployment) error +} + +type LocalClusterImportOperations struct { + context context.Context + apiReader client.Reader + cachedApiClient client.Writer + log *logrus.Logger +} + +func NewLocalClusterImportOperations(apiReader client.Reader, cachedApiClient client.Writer, log *logrus.Logger) LocalClusterImportOperations { + return LocalClusterImportOperations{context: context.TODO(), apiReader: apiReader, cachedApiClient: cachedApiClient, log: log} +} + +func (o *LocalClusterImportOperations) GetClusterDeployment(namespace string, name string) (*hivev1.ClusterDeployment, error) { + clusterDeployment := &hivev1.ClusterDeployment{} + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := o.apiReader.Get(o.context, namespacedName, clusterDeployment) + if err != nil { + return nil, err + } + return clusterDeployment, nil +} + +func (o *LocalClusterImportOperations) GetAgentClusterInstall(namespace string, name string) (*hiveext.AgentClusterInstall, error) { + agentClusterInstall := &hiveext.AgentClusterInstall{} + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := o.apiReader.Get(o.context, namespacedName, agentClusterInstall) + if err != nil { + return nil, err + } + return agentClusterInstall, nil +} + +func (o *LocalClusterImportOperations) GetNamespace(name string) (*v1.Namespace, error) { + ns := &v1.Namespace{} + namespacedName := types.NamespacedName{ + Namespace: "", + Name: name, + } + err := o.apiReader.Get(o.context, namespacedName, ns) + if err != nil { + return nil, err + } + return ns, nil +} + +func (o *LocalClusterImportOperations) CreateNamespace(name string) error { + ns := &v1.Namespace{} + ns.Name = name + err := o.cachedApiClient.Create(o.context, ns) + if err != nil { + return err + } + return nil +} + +func (o *LocalClusterImportOperations) GetSecret(namespace string, name string) (*v1.Secret, error) { + secret := &v1.Secret{} + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := o.apiReader.Get(o.context, namespacedName, secret) + if err != nil { + return nil, errors.Wrapf(err, "Unable to fetch secret %s from namespace %s", name, namespace) + } + return secret, nil +} + +func (o *LocalClusterImportOperations) GetClusterVersion(name string) (*configv1.ClusterVersion, error) { + clusterVersion := &configv1.ClusterVersion{} + namespacedName := types.NamespacedName{ + Namespace: "", + Name: name, + } + err := o.apiReader.Get(o.context, namespacedName, clusterVersion) + if err != nil { + return nil, err + } + return clusterVersion, nil +} + +func (o *LocalClusterImportOperations) GetClusterImageSet(name string) (*hivev1.ClusterImageSet, error) { + clusterImageSet := &hivev1.ClusterImageSet{} + namespacedName := types.NamespacedName{ + Namespace: "", + Name: name, + } + err := o.apiReader.Get(o.context, namespacedName, clusterImageSet) + if err != nil { + return nil, errors.Wrapf(err, "Unable to fetch cluster image set %s", name) + } + return clusterImageSet, nil +} + +func (o *LocalClusterImportOperations) CreateAgentClusterInstall(agentClusterInstall *hiveext.AgentClusterInstall) error { + err := o.cachedApiClient.Create(o.context, agentClusterInstall) + if err != nil { + return err + } + return nil +} + +func (o *LocalClusterImportOperations) CreateSecret(namespace string, secret *v1.Secret) error { + err := o.cachedApiClient.Create(o.context, secret) + if err != nil { + return err + } + return nil +} + +func (o *LocalClusterImportOperations) CreateClusterImageSet(clusterImageSet *hivev1.ClusterImageSet) error { + err := o.cachedApiClient.Create(o.context, clusterImageSet) + if err != nil { + return err + } + return nil +} + +func (o *LocalClusterImportOperations) CreateClusterDeployment(clusterDeployment *hivev1.ClusterDeployment) error { + err := o.cachedApiClient.Create(o.context, clusterDeployment) + if err != nil { + return err + } + return nil +} + +func (o *LocalClusterImportOperations) GetNodes() (*v1.NodeList, error) { + nodeList := &v1.NodeList{} + err := o.apiReader.List(o.context, nodeList) + if err != nil { + return nil, err + } + return nodeList, nil +} + +func (o *LocalClusterImportOperations) GetNumberOfControlPlaneNodes() (int, error) { + // Determine the number of control plane agents we have + // Control plane nodes have a specific label that we can look for., + numberOfControlPlaneNodes := 0 + nodeList, err := o.GetNodes() + o.log.Infof("Number of nodes in nodeList %d", len(nodeList.Items)) + if err != nil { + return 0, err + } + for _, node := range nodeList.Items { + for nodeLabelKey := range node.Labels { + if nodeLabelKey == "node-role.kubernetes.io/control-plane" { + numberOfControlPlaneNodes++ + } + } + } + return numberOfControlPlaneNodes, nil +} + +func (o *LocalClusterImportOperations) GetClusterDNS() (*configv1.DNS, error) { + dns := &configv1.DNS{} + namespacedName := types.NamespacedName{ + Namespace: "", + Name: "cluster", + } + err := o.apiReader.Get(o.context, namespacedName, dns) + if err != nil { + return nil, err + } + return dns, nil +} diff --git a/pkg/localclusterimport/local_cluster_import_operations_mocks.go b/pkg/localclusterimport/local_cluster_import_operations_mocks.go new file mode 100644 index 0000000000..29c7bd9f4d --- /dev/null +++ b/pkg/localclusterimport/local_cluster_import_operations_mocks.go @@ -0,0 +1,228 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: local_cluster_import_operations.go + +// Package localclusterimport is a generated GoMock package. +package localclusterimport + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1 "github.com/openshift/api/config/v1" + v1beta1 "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + v10 "github.com/openshift/hive/apis/hive/v1" + v11 "k8s.io/api/core/v1" +) + +// MockClusterImportOperations is a mock of ClusterImportOperations interface. +type MockClusterImportOperations struct { + ctrl *gomock.Controller + recorder *MockClusterImportOperationsMockRecorder +} + +// MockClusterImportOperationsMockRecorder is the mock recorder for MockClusterImportOperations. +type MockClusterImportOperationsMockRecorder struct { + mock *MockClusterImportOperations +} + +// NewMockClusterImportOperations creates a new mock instance. +func NewMockClusterImportOperations(ctrl *gomock.Controller) *MockClusterImportOperations { + mock := &MockClusterImportOperations{ctrl: ctrl} + mock.recorder = &MockClusterImportOperationsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClusterImportOperations) EXPECT() *MockClusterImportOperationsMockRecorder { + return m.recorder +} + +// CreateAgentClusterInstall mocks base method. +func (m *MockClusterImportOperations) CreateAgentClusterInstall(agentClusterInstall *v1beta1.AgentClusterInstall) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateAgentClusterInstall", agentClusterInstall) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateAgentClusterInstall indicates an expected call of CreateAgentClusterInstall. +func (mr *MockClusterImportOperationsMockRecorder) CreateAgentClusterInstall(agentClusterInstall interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAgentClusterInstall", reflect.TypeOf((*MockClusterImportOperations)(nil).CreateAgentClusterInstall), agentClusterInstall) +} + +// CreateClusterDeployment mocks base method. +func (m *MockClusterImportOperations) CreateClusterDeployment(clusterDeployment *v10.ClusterDeployment) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateClusterDeployment", clusterDeployment) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateClusterDeployment indicates an expected call of CreateClusterDeployment. +func (mr *MockClusterImportOperationsMockRecorder) CreateClusterDeployment(clusterDeployment interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateClusterDeployment", reflect.TypeOf((*MockClusterImportOperations)(nil).CreateClusterDeployment), clusterDeployment) +} + +// CreateClusterImageSet mocks base method. +func (m *MockClusterImportOperations) CreateClusterImageSet(clusterImageSet *v10.ClusterImageSet) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateClusterImageSet", clusterImageSet) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateClusterImageSet indicates an expected call of CreateClusterImageSet. +func (mr *MockClusterImportOperationsMockRecorder) CreateClusterImageSet(clusterImageSet interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateClusterImageSet", reflect.TypeOf((*MockClusterImportOperations)(nil).CreateClusterImageSet), clusterImageSet) +} + +// CreateNamespace mocks base method. +func (m *MockClusterImportOperations) CreateNamespace(name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNamespace", name) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNamespace indicates an expected call of CreateNamespace. +func (mr *MockClusterImportOperationsMockRecorder) CreateNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNamespace", reflect.TypeOf((*MockClusterImportOperations)(nil).CreateNamespace), name) +} + +// CreateSecret mocks base method. +func (m *MockClusterImportOperations) CreateSecret(namespace string, secret *v11.Secret) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSecret", namespace, secret) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateSecret indicates an expected call of CreateSecret. +func (mr *MockClusterImportOperationsMockRecorder) CreateSecret(namespace, secret interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockClusterImportOperations)(nil).CreateSecret), namespace, secret) +} + +// GetAgentClusterInstall mocks base method. +func (m *MockClusterImportOperations) GetAgentClusterInstall(namespace, name string) (*v1beta1.AgentClusterInstall, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAgentClusterInstall", namespace, name) + ret0, _ := ret[0].(*v1beta1.AgentClusterInstall) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAgentClusterInstall indicates an expected call of GetAgentClusterInstall. +func (mr *MockClusterImportOperationsMockRecorder) GetAgentClusterInstall(namespace, name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAgentClusterInstall", reflect.TypeOf((*MockClusterImportOperations)(nil).GetAgentClusterInstall), namespace, name) +} + +// GetClusterDNS mocks base method. +func (m *MockClusterImportOperations) GetClusterDNS() (*v1.DNS, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterDNS") + ret0, _ := ret[0].(*v1.DNS) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClusterDNS indicates an expected call of GetClusterDNS. +func (mr *MockClusterImportOperationsMockRecorder) GetClusterDNS() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterDNS", reflect.TypeOf((*MockClusterImportOperations)(nil).GetClusterDNS)) +} + +// GetClusterImageSet mocks base method. +func (m *MockClusterImportOperations) GetClusterImageSet(name string) (*v10.ClusterImageSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterImageSet", name) + ret0, _ := ret[0].(*v10.ClusterImageSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClusterImageSet indicates an expected call of GetClusterImageSet. +func (mr *MockClusterImportOperationsMockRecorder) GetClusterImageSet(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterImageSet", reflect.TypeOf((*MockClusterImportOperations)(nil).GetClusterImageSet), name) +} + +// GetClusterVersion mocks base method. +func (m *MockClusterImportOperations) GetClusterVersion(name string) (*v1.ClusterVersion, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterVersion", name) + ret0, _ := ret[0].(*v1.ClusterVersion) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClusterVersion indicates an expected call of GetClusterVersion. +func (mr *MockClusterImportOperationsMockRecorder) GetClusterVersion(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterVersion", reflect.TypeOf((*MockClusterImportOperations)(nil).GetClusterVersion), name) +} + +// GetNamespace mocks base method. +func (m *MockClusterImportOperations) GetNamespace(name string) (*v11.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNamespace", name) + ret0, _ := ret[0].(*v11.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNamespace indicates an expected call of GetNamespace. +func (mr *MockClusterImportOperationsMockRecorder) GetNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespace", reflect.TypeOf((*MockClusterImportOperations)(nil).GetNamespace), name) +} + +// GetNodes mocks base method. +func (m *MockClusterImportOperations) GetNodes() (*v11.NodeList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNodes") + ret0, _ := ret[0].(*v11.NodeList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNodes indicates an expected call of GetNodes. +func (mr *MockClusterImportOperationsMockRecorder) GetNodes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodes", reflect.TypeOf((*MockClusterImportOperations)(nil).GetNodes)) +} + +// GetNumberOfControlPlaneNodes mocks base method. +func (m *MockClusterImportOperations) GetNumberOfControlPlaneNodes() (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNumberOfControlPlaneNodes") + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNumberOfControlPlaneNodes indicates an expected call of GetNumberOfControlPlaneNodes. +func (mr *MockClusterImportOperationsMockRecorder) GetNumberOfControlPlaneNodes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNumberOfControlPlaneNodes", reflect.TypeOf((*MockClusterImportOperations)(nil).GetNumberOfControlPlaneNodes)) +} + +// GetSecret mocks base method. +func (m *MockClusterImportOperations) GetSecret(namespace, name string) (*v11.Secret, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSecret", namespace, name) + ret0, _ := ret[0].(*v11.Secret) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSecret indicates an expected call of GetSecret. +func (mr *MockClusterImportOperationsMockRecorder) GetSecret(namespace, name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecret", reflect.TypeOf((*MockClusterImportOperations)(nil).GetSecret), namespace, name) +}