sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/scalesetvms/scalesetvms.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package scalesetvms
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    25  	"github.com/pkg/errors"
    26  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    27  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    28  	"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
    29  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/async"
    30  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualmachines"
    31  	azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure"
    32  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    33  )
    34  
    35  const serviceName = "scalesetvms"
    36  
    37  type (
    38  	// ScaleSetVMScope defines the scope interface for a scale sets service.
    39  	ScaleSetVMScope interface {
    40  		azure.ClusterDescriber
    41  		azure.AsyncStatusUpdater
    42  		ScaleSetVMSpec() azure.ResourceSpecGetter
    43  		SetVMSSVM(vmssvm *azure.VMSSVM)
    44  		SetVMSSVMState(state infrav1.ProvisioningState)
    45  	}
    46  
    47  	// Service provides operations on Azure resources.
    48  	Service struct {
    49  		Scope ScaleSetVMScope
    50  		async.Reconciler
    51  		VMReconciler async.Reconciler
    52  	}
    53  )
    54  
    55  // NewService creates a new service.
    56  func NewService(scope ScaleSetVMScope) (*Service, error) {
    57  	client, err := newClient(scope, scope.DefaultedAzureCallTimeout())
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	vmClient, err := virtualmachines.NewClient(scope, scope.DefaultedAzureCallTimeout())
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	return &Service{
    66  		Reconciler: async.New[armcompute.VirtualMachineScaleSetVMsClientUpdateResponse,
    67  			armcompute.VirtualMachineScaleSetVMsClientDeleteResponse](scope, client, client),
    68  		VMReconciler: async.New[armcompute.VirtualMachinesClientCreateOrUpdateResponse,
    69  			armcompute.VirtualMachinesClientDeleteResponse](scope, vmClient, vmClient),
    70  		Scope: scope,
    71  	}, nil
    72  }
    73  
    74  // Name returns the service name.
    75  func (s *Service) Name() string {
    76  	return serviceName
    77  }
    78  
    79  // Reconcile idempotently gets, creates, and updates a scale set.
    80  func (s *Service) Reconcile(ctx context.Context) error {
    81  	ctx, log, done := tele.StartSpanWithLogger(ctx, "scalesetvms.Service.Reconcile")
    82  	defer done()
    83  
    84  	spec := s.Scope.ScaleSetVMSpec()
    85  	scaleSetVMSpec, ok := spec.(*ScaleSetVMSpec)
    86  	if !ok {
    87  		return errors.Errorf("%T is not of type ScaleSetVMSpec", spec)
    88  	}
    89  
    90  	reconciler := s.Reconciler
    91  	var getter azure.ResourceSpecGetter = scaleSetVMSpec
    92  	var result interface{}
    93  	var err error
    94  	// Fetch the latest instance or VM data. AzureMachinePoolReconciler handles model mutations.
    95  	if scaleSetVMSpec.IsFlex {
    96  		log.V(4).Info("VMSS is flex", "vmssName", scaleSetVMSpec.Name, "providerID", scaleSetVMSpec.ProviderID, "resourceID", scaleSetVMSpec.ResourceID)
    97  		getter, err = scaleSetVMSpecToVMSpec(*scaleSetVMSpec)
    98  		if err != nil {
    99  			return errors.Wrap(err, "failed to convert scaleSetVMSpec to vmSpec")
   100  		}
   101  		reconciler = s.VMReconciler
   102  	} else {
   103  		log.V(4).Info("VMSS is uniform", "vmssName", scaleSetVMSpec.Name, "providerID", scaleSetVMSpec.ProviderID, "instanceID", scaleSetVMSpec.InstanceID)
   104  	}
   105  
   106  	// We only want to get the resource if it exists and handle the not found error.
   107  	// We're using CreateOrUpdateResource() to do so but it doesn't actually create or update anything since getter.Parameters() always returns nil.
   108  	result, err = reconciler.CreateOrUpdateResource(ctx, getter, serviceName)
   109  	if err != nil {
   110  		return err
   111  	} else if result == nil {
   112  		return azure.WithTransientError(fmt.Errorf("instance does not exist yet"), time.Second*30)
   113  	}
   114  
   115  	if scaleSetVMSpec.IsFlex {
   116  		vm, ok := result.(armcompute.VirtualMachine)
   117  		if !ok {
   118  			return errors.Errorf("%T is not of type armcompute.VirtualMachine", result)
   119  		}
   120  		s.Scope.SetVMSSVM(converters.SDKVMToVMSSVM(vm, infrav1.FlexibleOrchestrationMode))
   121  	} else {
   122  		instance, ok := result.(armcompute.VirtualMachineScaleSetVM)
   123  		if !ok {
   124  			return errors.Errorf("%T is not of type armcompute.VirtualMachineScaleSetVM", result)
   125  		}
   126  		s.Scope.SetVMSSVM(converters.SDKToVMSSVM(instance))
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  // Delete deletes a scaleset instance asynchronously returning a future which encapsulates the long-running operation.
   133  func (s *Service) Delete(ctx context.Context) error {
   134  	ctx, _, done := tele.StartSpanWithLogger(ctx, "scalesetvms.Service.Delete")
   135  
   136  	defer done()
   137  
   138  	spec := s.Scope.ScaleSetVMSpec()
   139  	scaleSetVMSpec, ok := spec.(*ScaleSetVMSpec)
   140  	if !ok {
   141  		return errors.Errorf("%T is not of type ScaleSetVMSpec", spec)
   142  	}
   143  
   144  	reconciler := s.Reconciler
   145  	var getter azure.ResourceSpecGetter = scaleSetVMSpec
   146  	var err error
   147  	if scaleSetVMSpec.IsFlex {
   148  		getter, err = scaleSetVMSpecToVMSpec(*scaleSetVMSpec)
   149  		if err != nil {
   150  			return errors.Wrap(err, "failed to convert scaleSetVMSpec to vmSpec")
   151  		}
   152  		reconciler = s.VMReconciler
   153  	}
   154  
   155  	err = reconciler.DeleteResource(ctx, getter, serviceName)
   156  	if err != nil {
   157  		s.Scope.SetVMSSVMState(infrav1.Deleting)
   158  	} else {
   159  		s.Scope.SetVMSSVMState(infrav1.Deleted)
   160  	}
   161  
   162  	return err
   163  }
   164  
   165  func scaleSetVMSpecToVMSpec(scaleSetVMSpec ScaleSetVMSpec) (*VMSSFlexGetter, error) {
   166  	parsed, err := azureutil.ParseResourceID(scaleSetVMSpec.ResourceID)
   167  	if err != nil {
   168  		return nil, errors.Wrap(err, fmt.Sprintf("failed to parse resource id %q", scaleSetVMSpec.ResourceID))
   169  	}
   170  	resourceGroup, resourceName := parsed.ResourceGroupName, parsed.Name
   171  
   172  	return &VMSSFlexGetter{
   173  		Name:          resourceName,
   174  		ResourceGroup: resourceGroup,
   175  	}, nil
   176  }