From 5b0105778f2ab9b33a2d0831a7c7424d6e052e31 Mon Sep 17 00:00:00 2001 From: Christopher J Schaefer Date: Mon, 15 Jul 2024 07:27:09 -0500 Subject: [PATCH] VPC: Create v2 path for new Infrastructure implementation (#1858) Create a v2 path that will be used for the new Infrastructure implementation for VPC Clusters. All new functionality will be placed in these new v2 paths, based on the new Network field, to prevent breaking existing implementation. --- cloud/scope/vpc_cluster.go | 168 ++++++++++++++++++++++++ controllers/ibmvpccluster_controller.go | 64 +++++++++ 2 files changed, 232 insertions(+) create mode 100644 cloud/scope/vpc_cluster.go diff --git a/cloud/scope/vpc_cluster.go b/cloud/scope/vpc_cluster.go new file mode 100644 index 000000000..37d6f9b38 --- /dev/null +++ b/cloud/scope/vpc_cluster.go @@ -0,0 +1,168 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scope + +import ( + "context" + "errors" + "fmt" + + "github.com/go-logr/logr" + + "github.com/IBM/go-sdk-core/v5/core" + "github.com/IBM/platform-services-go-sdk/resourcecontrollerv2" + + "k8s.io/klog/v2/textlogger" + + "sigs.k8s.io/controller-runtime/pkg/client" + + capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util/patch" + + infrav1beta2 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/authenticator" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/cos" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcecontroller" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcemanager" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/vpc" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/endpoints" +) + +const ( + // LOGDEBUGLEVEL indicates the debug level of the logs. + LOGDEBUGLEVEL = 5 +) + +// VPCClusterScopeParams defines the input parameters used to create a new VPCClusterScope. +type VPCClusterScopeParams struct { + Client client.Client + Cluster *capiv1beta1.Cluster + IBMVPCCluster *infrav1beta2.IBMVPCCluster + Logger logr.Logger + ServiceEndpoint []endpoints.ServiceEndpoint + + IBMVPCClient vpc.Vpc +} + +// VPCClusterScope defines a scope defined around a VPC Cluster. +type VPCClusterScope struct { + logr.Logger + Client client.Client + patchHelper *patch.Helper + + COSClient cos.Cos + ResourceControllerClient resourcecontroller.ResourceController + ResourceManagerClient resourcemanager.ResourceManager + VPCClient vpc.Vpc + + Cluster *capiv1beta1.Cluster + IBMVPCCluster *infrav1beta2.IBMVPCCluster + ServiceEndpoint []endpoints.ServiceEndpoint +} + +// NewVPCClusterScope creates a new VPCClusterScope from the supplied parameters. +func NewVPCClusterScope(params VPCClusterScopeParams) (*VPCClusterScope, error) { + if params.Client == nil { + err := errors.New("error failed to generate new scope from nil Client") + return nil, err + } + if params.Cluster == nil { + err := errors.New("error failed to generate new scope from nil Cluster") + return nil, err + } + if params.IBMVPCCluster == nil { + err := errors.New("error failed to generate new scope from nil IBMVPCCluster") + return nil, err + } + if params.Logger == (logr.Logger{}) { + params.Logger = textlogger.NewLogger(textlogger.NewConfig()) + } + + helper, err := patch.NewHelper(params.IBMVPCCluster, params.Client) + if err != nil { + return nil, fmt.Errorf("error failed to init patch helper: %w", err) + } + + vpcEndpoint := endpoints.FetchVPCEndpoint(params.IBMVPCCluster.Spec.Region, params.ServiceEndpoint) + vpcClient, err := vpc.NewService(vpcEndpoint) + if err != nil { + return nil, fmt.Errorf("error failed to create IBM VPC client: %w", err) + } + + if params.IBMVPCCluster.Spec.Network == nil || params.IBMVPCCluster.Spec.Region == "" { + return nil, fmt.Errorf("error failed to generate vpc client as Network or Region is nil") + } + + if params.Logger.V(LOGDEBUGLEVEL).Enabled() { + core.SetLoggingLevel(core.LevelDebug) + } + + auth, err := authenticator.GetAuthenticator() + if err != nil { + return nil, fmt.Errorf("error failed to create authenticator: %w", err) + } + + // Create Global Tagging client. + // TODO(cjschaef): need service support. + + // Create Resource Controller client. + rcOptions := resourcecontroller.ServiceOptions{ + ResourceControllerV2Options: &resourcecontrollerv2.ResourceControllerV2Options{ + Authenticator: auth, + }, + } + // Fetch the resource controller endpoint. + rcEndpoint := endpoints.FetchEndpoints(string(endpoints.RC), params.ServiceEndpoint) + if rcEndpoint != "" { + rcOptions.URL = rcEndpoint + params.Logger.V(3).Info("Overriding the default resource controller endpoint", "ResourceControllerEndpoint", rcEndpoint) + } + resourceControllerClient, err := resourcecontroller.NewService(rcOptions) + if err != nil { + return nil, fmt.Errorf("error failed to create resource controller client: %w", err) + } + + // Create Resource Manager client. + // TODO(cjschaef): Need to extend ResourceManager service and endpoint support to add properly. + + clusterScope := &VPCClusterScope{ + Logger: params.Logger, + Client: params.Client, + patchHelper: helper, + Cluster: params.Cluster, + IBMVPCCluster: params.IBMVPCCluster, + ServiceEndpoint: params.ServiceEndpoint, + ResourceControllerClient: resourceControllerClient, + VPCClient: vpcClient, + } + return clusterScope, nil +} + +// PatchObject persists the cluster configuration and status. +func (s *VPCClusterScope) PatchObject() error { + return s.patchHelper.Patch(context.TODO(), s.IBMVPCCluster) +} + +// Close closes the current scope persisting the cluster configuration and status. +func (s *VPCClusterScope) Close() error { + return s.PatchObject() +} + +// Name returns the CAPI cluster name. +func (s *VPCClusterScope) Name() string { + return s.Cluster.Name +} diff --git a/controllers/ibmvpccluster_controller.go b/controllers/ibmvpccluster_controller.go index 066736d57..fe72ba047 100644 --- a/controllers/ibmvpccluster_controller.go +++ b/controllers/ibmvpccluster_controller.go @@ -71,6 +71,11 @@ func (r *IBMVPCClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } + // Determine whether the Cluster is designed for extended Infrastructure support, implemented in a separate path. + if ibmCluster.Spec.Network != nil { + return r.reconcileV2(ctx, req) + } + // Fetch the Cluster. cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmCluster.ObjectMeta) if err != nil { @@ -109,6 +114,57 @@ func (r *IBMVPCClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques return r.reconcile(clusterScope) } +func (r *IBMVPCClusterReconciler) reconcileV2(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { + log := r.Log.WithValues("ibmvpccluster", req.NamespacedName) + + // Fetch the IBMVPCCluster instance. + ibmCluster := &infrav1beta2.IBMVPCCluster{} + err := r.Get(ctx, req.NamespacedName, ibmCluster) + if err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Fetch the Cluster. + cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmCluster.ObjectMeta) + if err != nil { + return ctrl.Result{}, err + } + if cluster == nil { + log.Info("Cluster Controller has not yet set OwnerRef") + return ctrl.Result{}, nil + } + + clusterScope, err := scope.NewVPCClusterScope(scope.VPCClusterScopeParams{ + Client: r.Client, + Logger: log, + Cluster: cluster, + IBMVPCCluster: ibmCluster, + ServiceEndpoint: r.ServiceEndpoint, + }) + + // Always close the scope when exiting this function so we can persist any IBMVPCCluster changes. + defer func() { + if clusterScope != nil { + if err := clusterScope.Close(); err != nil && reterr == nil { + reterr = err + } + } + }() + + // Handle deleted clusters. + if !ibmCluster.DeletionTimestamp.IsZero() { + return r.reconcileDeleteV2(clusterScope) + } + + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to create scope: %w", err) + } + return r.reconcileCluster(clusterScope) +} + func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (ctrl.Result, error) { // If the IBMVPCCluster doesn't have our finalizer, add it. if controllerutil.AddFinalizer(clusterScope.IBMVPCCluster, infrav1beta2.ClusterFinalizer) { @@ -173,6 +229,10 @@ func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (c return ctrl.Result{}, nil } +func (r *IBMVPCClusterReconciler) reconcileCluster(_ *scope.VPCClusterScope) (ctrl.Result, error) { + return ctrl.Result{}, fmt.Errorf("not implemented") +} + func (r *IBMVPCClusterReconciler) reconcileDelete(clusterScope *scope.ClusterScope) (ctrl.Result, error) { // check if still have existing VSIs. listVSIOpts := &vpcv1.ListInstancesOptions{ @@ -227,6 +287,10 @@ func (r *IBMVPCClusterReconciler) reconcileDelete(clusterScope *scope.ClusterSco return handleFinalizerRemoval(clusterScope) } +func (r *IBMVPCClusterReconciler) reconcileDeleteV2(_ *scope.VPCClusterScope) (ctrl.Result, error) { + return ctrl.Result{}, fmt.Errorf("not implemented") +} + func (r *IBMVPCClusterReconciler) getOrCreate(clusterScope *scope.ClusterScope) (*vpcv1.LoadBalancer, error) { loadBalancer, err := clusterScope.CreateLoadBalancer() return loadBalancer, err