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 }