Skip to content

Commit 87cedb8

Browse files
committed
MachinePool: populate providerIDList
In order for nodes to be associated to the MachinePool, we need to populate the spec.providerIDList field. This field is known to the MachinePool controller.
1 parent 181c78f commit 87cedb8

File tree

7 files changed

+94
-18
lines changed

7 files changed

+94
-18
lines changed

cloud/services/compute/instancegroupmanagers/instancegroupmanagers_reconcile.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package instancegroupmanagers
1919
import (
2020
"context"
2121
"fmt"
22+
"strings"
2223

24+
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/filter"
2325
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
2426
"google.golang.org/api/compute/v1"
2527
"sigs.k8s.io/cluster-api-provider-gcp/cloud/gcperrors"
@@ -28,16 +30,16 @@ import (
2830
)
2931

3032
// Reconcile reconcile machine instance.
31-
func (s *Service) Reconcile(ctx context.Context, instanceTemplateKey *meta.Key) error {
33+
func (s *Service) Reconcile(ctx context.Context, instanceTemplateKey *meta.Key) (*compute.InstanceGroupManager, error) {
3234
log := log.FromContext(ctx)
3335
log.Info("Reconciling instanceGroupManager resources")
3436
igm, err := s.createOrGet(ctx, instanceTemplateKey)
3537
if err != nil {
36-
return err
38+
return nil, err
3739
}
3840
log.V(2).Info("Reconciled instanceGroupManager", "selfLink", igm.SelfLink)
3941

40-
return nil
42+
return igm, nil
4143
}
4244

4345
// Delete deletes the GCP instanceGroupManager resource.
@@ -131,3 +133,33 @@ func (s *Service) createOrGet(ctx context.Context, instanceTemplateKey *meta.Key
131133

132134
return actual, nil
133135
}
136+
137+
// ListInstances lists instances in the the instanceGroup linked to the passed instanceGroupManager.
138+
func (s *Service) ListInstances(ctx context.Context, instanceGroupManager *compute.InstanceGroupManager) ([]*compute.InstanceWithNamedPorts, error) {
139+
log := log.FromContext(ctx)
140+
141+
var igKey *meta.Key
142+
{
143+
instanceGroup := instanceGroupManager.InstanceGroup
144+
instanceGroup = strings.TrimPrefix(instanceGroup, "https://www.googleapis.com/")
145+
instanceGroup = strings.TrimPrefix(instanceGroup, "compute/v1/")
146+
tokens := strings.Split(instanceGroup, "/")
147+
if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "zones" && tokens[4] == "instanceGroups" {
148+
igKey = meta.ZonalKey(tokens[5], tokens[3])
149+
} else {
150+
return nil, fmt.Errorf("unexpected format for instanceGroup: %q", instanceGroup)
151+
}
152+
}
153+
154+
log.Info("Listing instances in instanceGroup", "instanceGroup", instanceGroupManager.InstanceGroup)
155+
listInstancesRequest := &compute.InstanceGroupsListInstancesRequest{
156+
InstanceState: "ALL",
157+
}
158+
instances, err := s.instanceGroups.ListInstances(ctx, igKey, listInstancesRequest, filter.None)
159+
if err != nil {
160+
log.Error(err, "Error listing instances in instanceGroup", "instanceGroup", instanceGroupManager.InstanceGroup)
161+
return nil, fmt.Errorf("listing instances in instanceGroup %q: %w", instanceGroupManager.InstanceGroup, err)
162+
}
163+
164+
return instances, nil
165+
}

cloud/services/compute/instancegroupmanagers/service.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type Scope interface {
5050
type Service struct {
5151
scope Scope
5252
instanceGroupManagers instanceGroupManagersClient
53+
instanceGroups k8scloud.InstanceGroups
5354
}
5455

5556
// var _ cloud.Reconciler = &Service{}
@@ -61,5 +62,6 @@ func New(scope Scope) *Service {
6162
return &Service{
6263
scope: scope,
6364
instanceGroupManagers: cloudScope.InstanceGroupManagers(),
65+
instanceGroups: cloudScope.InstanceGroups(),
6466
}
6567
}

config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinepools.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,13 @@ spec:
256256
preemptible:
257257
description: Preemptible defines if instance is preemptible
258258
type: boolean
259+
providerIDList:
260+
description: |-
261+
ProviderIDList are the identification IDs of machine instances provided by the provider.
262+
This field must match the provider IDs as seen on the node objects corresponding to a machine pool's machine instances.
263+
items:
264+
type: string
265+
type: array
259266
provisioningModel:
260267
description: |-
261268
ProvisioningModel defines if instance is spot.

exp/api/v1beta1/gcpmachinepool_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ const (
3232

3333
// GCPMachinePoolSpec defines the desired state of GCPMachinePool.
3434
type GCPMachinePoolSpec struct {
35+
// ProviderIDList are the identification IDs of machine instances provided by the provider.
36+
// This field must match the provider IDs as seen on the node objects corresponding to a machine pool's machine instances.
37+
// +optional
38+
ProviderIDList []string `json:"providerIDList,omitempty"`
39+
3540
// InstanceType is the type of instance to create. Example: n1.standard-2
3641
InstanceType string `json:"instanceType"`
3742

exp/api/v1beta1/gcpmachinepool_webhook.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package v1beta1
1919
import (
2020
"context"
2121
"fmt"
22-
"reflect"
2322

2423
apierrors "k8s.io/apimachinery/pkg/api/errors"
2524
"k8s.io/apimachinery/pkg/runtime"
@@ -60,23 +59,14 @@ func (*gcpMachinePoolWebhook) ValidateCreate(_ context.Context, obj runtime.Obje
6059
}
6160

6261
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
63-
func (*gcpMachinePoolWebhook) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
64-
old, ok := oldObj.(*GCPMachinePool)
65-
if !ok {
66-
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a GCPMachinePool but got a %T", oldObj))
67-
}
68-
62+
func (*gcpMachinePoolWebhook) ValidateUpdate(_ context.Context, _, newObj runtime.Object) (admission.Warnings, error) {
6963
r, ok := newObj.(*GCPMachinePool)
7064
if !ok {
71-
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a GCPMachinePool but got a %T", newObj))
65+
return nil, fmt.Errorf("expected a GCPMachinePool object but got %T", r)
7266
}
7367

7468
gcpMachinePoolLog.Info("Validating GCPMachinePool update", "name", r.Name)
7569

76-
if !reflect.DeepEqual(r.Spec, old.Spec) {
77-
return nil, apierrors.NewBadRequest("GCPMachinePool.Spec is immutable")
78-
}
79-
8070
return nil, nil
8171
}
8272

exp/api/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

exp/controllers/gcpmachinepool_controller.go

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ package controllers
2020
import (
2121
"context"
2222
"fmt"
23+
"strings"
24+
"time"
2325

2426
"github.com/google/go-cmp/cmp"
2527
corev1 "k8s.io/api/core/v1"
@@ -180,7 +182,7 @@ func (r *GCPMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ctr
180182
Complete(r)
181183
}
182184

183-
func (r *GCPMachinePoolReconciler) reconcile(ctx context.Context, machinePoolScope *scope.MachinePoolScope) (ctrl.Result, error) { //nolint:unparam
185+
func (r *GCPMachinePoolReconciler) reconcile(ctx context.Context, machinePoolScope *scope.MachinePoolScope) (ctrl.Result, error) {
184186
log := logger.FromContext(ctx)
185187

186188
log.Info("Reconciling GCPMachinePool")
@@ -226,7 +228,8 @@ func (r *GCPMachinePoolReconciler) reconcile(ctx context.Context, machinePoolSco
226228
// set the InstanceTemplateReadyCondition condition
227229
conditions.MarkTrue(machinePoolScope.GCPMachinePool, expinfrav1.InstanceTemplateReadyCondition)
228230

229-
if err := instancegroupmanagers.New(machinePoolScope).Reconcile(ctx, instanceTemplateKey); err != nil {
231+
igm, err := instancegroupmanagers.New(machinePoolScope).Reconcile(ctx, instanceTemplateKey)
232+
if err != nil {
230233
log.Error(err, "Error reconciling instanceGroupManager")
231234
// record.Warnf(machineScope.GCPMachine, "GCPMachineReconcile", "Reconcile error - %v", err)
232235
conditions.MarkUnknown(machinePoolScope.GCPMachinePool, expinfrav1.MIGReadyCondition, expinfrav1.MIGNotFoundReason, "%s", err.Error())
@@ -236,7 +239,39 @@ func (r *GCPMachinePoolReconciler) reconcile(ctx context.Context, machinePoolSco
236239
// set the MIGReadyCondition condition
237240
conditions.MarkTrue(machinePoolScope.GCPMachinePool, expinfrav1.MIGReadyCondition)
238241

239-
return ctrl.Result{}, nil
242+
igmInstances, err := instancegroupmanagers.New(machinePoolScope).ListInstances(ctx, igm)
243+
if err != nil {
244+
log.Error(err, "Error listing instances in instanceGroupManager")
245+
return ctrl.Result{}, err
246+
}
247+
248+
providerIDList := make([]string, len(igmInstances))
249+
250+
for i, instance := range igmInstances {
251+
var providerID string
252+
253+
// Convert instance URL to providerID format
254+
u := instance.Instance
255+
u = strings.TrimPrefix(u, "https://www.googleapis.com/compute/v1/")
256+
tokens := strings.Split(u, "/")
257+
if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "zones" && tokens[4] == "instances" {
258+
providerID = fmt.Sprintf("gce://%s/%s/%s", tokens[1], tokens[3], tokens[5])
259+
} else {
260+
return ctrl.Result{}, fmt.Errorf("unexpected instance URL format: %s", instance.Instance)
261+
}
262+
263+
providerIDList[i] = providerID
264+
}
265+
266+
// FUTURE: do we need to verify that the instances are actually running?
267+
machinePoolScope.GCPMachinePool.Spec.ProviderIDList = providerIDList
268+
machinePoolScope.GCPMachinePool.Status.Replicas = int32(len(providerIDList))
269+
machinePoolScope.GCPMachinePool.Status.Ready = true
270+
271+
// Requeue so that we can keep the spec.providerIDList and status in sync with the MIG.
272+
// This is important for scaling up and down, as the CAPI MachinePool controller relies on
273+
// the providerIDList to determine which machines belong to the MachinePool.
274+
return ctrl.Result{RequeueAfter: 1 * time.Minute}, nil
240275
}
241276

242277
func (r *GCPMachinePoolReconciler) reconcileDelete(ctx context.Context, machinePoolScope *scope.MachinePoolScope) error {

0 commit comments

Comments
 (0)