diff --git a/cloud/services/container/clusters/reconcile.go b/cloud/services/container/clusters/reconcile.go index 4317e058b..cc10444d6 100644 --- a/cloud/services/container/clusters/reconcile.go +++ b/cloud/services/container/clusters/reconcile.go @@ -20,20 +20,21 @@ import ( "context" "fmt" - "sigs.k8s.io/cluster-api-provider-gcp/cloud/scope" - "sigs.k8s.io/cluster-api-provider-gcp/cloud/services/shared" - "cloud.google.com/go/container/apiv1/containerpb" "github.com/go-logr/logr" "github.com/googleapis/gax-go/v2/apierror" "github.com/pkg/errors" "google.golang.org/grpc/codes" - infrav1exp "sigs.k8s.io/cluster-api-provider-gcp/exp/api/v1beta1" - "sigs.k8s.io/cluster-api-provider-gcp/util/reconciler" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/conditions" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + + "sigs.k8s.io/cluster-api-provider-gcp/cloud/scope" + "sigs.k8s.io/cluster-api-provider-gcp/cloud/services/shared" + infrav1exp "sigs.k8s.io/cluster-api-provider-gcp/exp/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-gcp/util/reconciler" ) // Reconcile reconcile GKE cluster. @@ -93,6 +94,11 @@ func (s *Service) Reconcile(ctx context.Context) (ctrl.Result, error) { } log.V(2).Info("gke cluster found", "status", cluster.Status) + if err = s.deleteUnmanagedNodePools(ctx); err != nil { + log.Error(err, "failed to delete unmanaged node pools", "cluster", s.scope.ClusterName()) + return ctrl.Result{}, err + } + s.scope.GCPManagedControlPlane.Status.CurrentVersion = cluster.CurrentMasterVersion switch cluster.Status { @@ -342,3 +348,45 @@ func (s *Service) checkDiffAndPrepareUpdate(existingCluster *containerpb.Cluster } return needUpdate, &updateClusterRequest } + +func (s *Service) deleteUnmanagedNodePools(ctx context.Context) error { + log := log.FromContext(ctx).WithValues("controller", "container.clusters", "action", "deleteUnmanagedNodePools") + log.Info("Delete unmanaged node pools") + clusterName, namespace := s.scope.ClusterName(), s.scope.Cluster.Namespace + listOptions := []client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels(map[string]string{clusterv1.ClusterNameLabel: clusterName}), + } + managedMachinePools := &infrav1exp.GCPManagedMachinePoolList{} + if err := s.scope.Client().List(ctx, managedMachinePools, listOptions...); err != nil { + return fmt.Errorf("failed to list managed machine pools for cluster %s/%s: %w", namespace, clusterName, err) + } + poolMap := make(map[string]struct{}) + for _, pool := range managedMachinePools.Items { + poolMap[pool.Spec.NodePoolName] = struct{}{} + } + + list, err := s.scope.ManagedControlPlaneClient().ListNodePools(ctx, &containerpb.ListNodePoolsRequest{ + Parent: fmt.Sprintf("projects/%s/locations/%s/clusters/%s", + s.scope.GCPManagedCluster.Spec.Project, s.scope.Region(), clusterName), + }) + if err != nil { + return errors.Wrapf(err, "failed to list node groups") + } + for _, pool := range list.NodePools { + if pool == nil { + continue + } + if _, ok := poolMap[pool.Name]; ok { + continue + } + _, err = s.scope.ManagedControlPlaneClient().DeleteNodePool(ctx, &containerpb.DeleteNodePoolRequest{ + Name: fmt.Sprintf("projects/%s/locations/%s/clusters/%s/nodePools/%s", + s.scope.GCPManagedCluster.Spec.Project, s.scope.Region(), clusterName, pool.Name), + }) + if err != nil { + return errors.Wrapf(err, "failed to delete node pool %s in cluster %s/%s", pool.Name, namespace, clusterName) + } + } + return nil +} diff --git a/exp/controllers/gcpmanagedcontrolplane_controller.go b/exp/controllers/gcpmanagedcontrolplane_controller.go index 0e88d7e78..d6ef1356a 100644 --- a/exp/controllers/gcpmanagedcontrolplane_controller.go +++ b/exp/controllers/gcpmanagedcontrolplane_controller.go @@ -21,28 +21,32 @@ import ( "fmt" "time" - "sigs.k8s.io/cluster-api/util/annotations" - "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/cluster-api-provider-gcp/cloud" - "sigs.k8s.io/cluster-api-provider-gcp/cloud/scope" - "sigs.k8s.io/cluster-api-provider-gcp/cloud/services/container/clusters" - infrav1exp "sigs.k8s.io/cluster-api-provider-gcp/exp/api/v1beta1" - "sigs.k8s.io/cluster-api-provider-gcp/util/reconciler" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util" + "sigs.k8s.io/cluster-api/util/annotations" "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/cluster-api/util/predicates" "sigs.k8s.io/cluster-api/util/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/source" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/cluster-api-provider-gcp/cloud" + "sigs.k8s.io/cluster-api-provider-gcp/cloud/scope" + "sigs.k8s.io/cluster-api-provider-gcp/cloud/services/container/clusters" + infrav1exp "sigs.k8s.io/cluster-api-provider-gcp/exp/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-gcp/util/reconciler" +) + +const ( + ReconcileAfterDurationForHeath = time.Second * 60 + ReconcileAfterDurationForError = time.Second * 10 ) // GCPManagedControlPlaneReconciler reconciles a GCPManagedControlPlane object. @@ -84,12 +88,21 @@ func (r *GCPManagedControlPlaneReconciler) SetupWithManager(ctx context.Context, return nil } -func (r *GCPManagedControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { +func (r *GCPManagedControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, reterr error) { ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultedLoopTimeout(r.ReconcileTimeout)) defer cancel() log := ctrl.LoggerFrom(ctx) + defer func() { + if reterr != nil { + log.Error(reterr, "reconcile control plane failed") + res.RequeueAfter = ReconcileAfterDurationForError + } else { + res.RequeueAfter = ReconcileAfterDurationForHeath + } + }() + // Get the control plane instance gcpManagedControlPlane := &infrav1exp.GCPManagedControlPlane{} if err := r.Client.Get(ctx, req.NamespacedName, gcpManagedControlPlane); err != nil {