diff --git a/pkg/frontend/admin_openshiftcluster_vmresize.go b/pkg/frontend/admin_openshiftcluster_vmresize.go index 19cd68cd17a..8d66b43f829 100644 --- a/pkg/frontend/admin_openshiftcluster_vmresize.go +++ b/pkg/frontend/admin_openshiftcluster_vmresize.go @@ -31,13 +31,22 @@ func (f *frontend) _postAdminOpenShiftClusterVMResize(log *logrus.Entry, ctx con resourceType := chi.URLParam(r, "resourceType") resourceGroupName := chi.URLParam(r, "resourceGroupName") vmSize := r.URL.Query().Get("vmSize") + useCapacityReservation := strings.EqualFold(r.URL.Query().Get("useCapacityReservation"), "true") - action, _, err := f.prepareAdminActions(log, ctx, vmName, strings.TrimPrefix(r.URL.Path, "/admin"), resourceType, resourceName, resourceGroupName) + err := validateAdminMasterVMSize(vmSize) if err != nil { return err } - err = validateAdminMasterVMSize(vmSize) + if useCapacityReservation { + action, _, err := f.prepareAdminActionsForCluster(log, ctx, strings.TrimPrefix(r.URL.Path, "/admin"), resourceType, resourceName, resourceGroupName) + if err != nil { + return err + } + return action.VMResizeWithCapacityReservation(ctx, vmSize) + } + + action, _, err := f.prepareAdminActions(log, ctx, vmName, strings.TrimPrefix(r.URL.Path, "/admin"), resourceType, resourceName, resourceGroupName) if err != nil { return err } diff --git a/pkg/frontend/admin_openshiftcluster_vmresize_test.go b/pkg/frontend/admin_openshiftcluster_vmresize_test.go index ad12cea1b68..79a5fb5164b 100644 --- a/pkg/frontend/admin_openshiftcluster_vmresize_test.go +++ b/pkg/frontend/admin_openshiftcluster_vmresize_test.go @@ -53,15 +53,16 @@ func TestAdminVMResize(t *testing.T) { } type test struct { - name string - resourceID string - vmName string - vmSize string - fixture func(f *testdatabase.Fixture) - azureActionsMocks func(*test, *mock_adminactions.MockAzureActions) - wantStatusCode int - wantResponse []byte - wantError string + name string + resourceID string + vmName string + vmSize string + useCapacityReservation bool + fixture func(f *testdatabase.Fixture) + azureActionsMocks func(*test, *mock_adminactions.MockAzureActions) + wantStatusCode int + wantResponse []byte + wantError string } for _, tt := range []*test{ @@ -80,6 +81,20 @@ func TestAdminVMResize(t *testing.T) { }, wantStatusCode: http.StatusOK, }, + { + name: "capacity reservation path", + vmSize: "Standard_D8s_v3", + useCapacityReservation: true, + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + addClusterDoc(f) + addSubscriptionDoc(f) + }, + azureActionsMocks: func(tt *test, a *mock_adminactions.MockAzureActions) { + a.EXPECT().VMResizeWithCapacityReservation(gomock.Any(), tt.vmSize).Return(nil) + }, + wantStatusCode: http.StatusOK, + }, { name: "cluster not found", vmName: "aro-fake-node-master-0", @@ -156,9 +171,11 @@ func TestAdminVMResize(t *testing.T) { go f.Run(ctx, nil, nil) - resp, b, err := ti.request(http.MethodPost, - fmt.Sprintf("https://server/admin%s/resize?vmName=%s&vmSize=%s", tt.resourceID, tt.vmName, tt.vmSize), - nil, nil) + url := fmt.Sprintf("https://server/admin%s/resize?vmName=%s&vmSize=%s", tt.resourceID, tt.vmName, tt.vmSize) + if tt.useCapacityReservation { + url += "&useCapacityReservation=true" + } + resp, b, err := ti.request(http.MethodPost, url, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_vm_actions.go b/pkg/frontend/admin_vm_actions.go index 4a300604382..5ec73bb349c 100644 --- a/pkg/frontend/admin_vm_actions.go +++ b/pkg/frontend/admin_vm_actions.go @@ -20,7 +20,12 @@ func (f *frontend) prepareAdminActions(log *logrus.Entry, ctx context.Context, v if err != nil { return nil, nil, err } + return f.prepareAdminActionsForCluster(log, ctx, resourceID, resourceType, resourceName, resourceGroupName) +} +// prepareAdminActionsForCluster is like prepareAdminActions but does not require or validate a VM name. +// Use this for operations that act on the whole cluster rather than a specific VM. +func (f *frontend) prepareAdminActionsForCluster(log *logrus.Entry, ctx context.Context, resourceID string, resourceType, resourceName, resourceGroupName string) (azureActions adminactions.AzureActions, doc *api.OpenShiftClusterDocument, err error) { dbOpenShiftClusters, err := f.dbGroup.OpenShiftClusters() if err != nil { return nil, nil, err diff --git a/pkg/frontend/adminactions/azureactions.go b/pkg/frontend/adminactions/azureactions.go index 1da776ad8ba..7dc45606e0d 100644 --- a/pkg/frontend/adminactions/azureactions.go +++ b/pkg/frontend/adminactions/azureactions.go @@ -36,6 +36,7 @@ type AzureActions interface { VMStopAndWait(ctx context.Context, vmName string, deallocateVM bool) error VMSizeList(ctx context.Context) ([]*sdkcompute.ResourceSKU, error) VMResize(ctx context.Context, vmName string, vmSize string) error + VMResizeWithCapacityReservation(ctx context.Context, targetVMSize string) error ResourceGroupHasVM(ctx context.Context, vmName string) (bool, error) VMSerialConsole(ctx context.Context, log *logrus.Entry, vmName string, target io.Writer) error ResourceDeleteAndWait(ctx context.Context, resourceID string) error @@ -47,16 +48,19 @@ type azureActions struct { env env.Interface oc *api.OpenShiftCluster - networkInterfaces armnetwork.InterfacesClient - diskEncryptionSets compute.DiskEncryptionSetsClient - loadBalancers armnetwork.LoadBalancersClient - resources features.ResourcesClient - resourceSkus armcompute.ResourceSKUsClient - routeTables armnetwork.RouteTablesClient - securityGroups armnetwork.SecurityGroupsClient - storageAccounts storage.AccountsClient - virtualMachines compute.VirtualMachinesClient - virtualNetworks armnetwork.VirtualNetworksClient + networkInterfaces armnetwork.InterfacesClient + diskEncryptionSets compute.DiskEncryptionSetsClient + loadBalancers armnetwork.LoadBalancersClient + resources features.ResourcesClient + resourceSkus armcompute.ResourceSKUsClient + routeTables armnetwork.RouteTablesClient + securityGroups armnetwork.SecurityGroupsClient + storageAccounts storage.AccountsClient + virtualMachines compute.VirtualMachinesClient + virtualNetworks armnetwork.VirtualNetworksClient + armVirtualMachines armcompute.VirtualMachinesClient + armCapacityReservationGroups armcompute.CapacityReservationGroupsClient + armCapacityReservations armcompute.CapacityReservationsClient } // NewAzureActions returns an azureActions @@ -106,21 +110,39 @@ func NewAzureActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftClus return nil, err } + armVMsClient, err := armcompute.NewVirtualMachinesClient(subscriptionDoc.ID, credential, options) + if err != nil { + return nil, err + } + + armCRGClient, err := armcompute.NewCapacityReservationGroupsClient(subscriptionDoc.ID, credential, options) + if err != nil { + return nil, err + } + + armCRClient, err := armcompute.NewCapacityReservationsClient(subscriptionDoc.ID, credential, options) + if err != nil { + return nil, err + } + return &azureActions{ log: log, env: env, oc: oc, - networkInterfaces: networkInterfaces, - diskEncryptionSets: compute.NewDiskEncryptionSetsClientWithAROEnvironment(env.Environment(), subscriptionDoc.ID, fpAuth), - loadBalancers: loadBalancers, - resources: features.NewResourcesClient(env.Environment(), subscriptionDoc.ID, fpAuth), - resourceSkus: armResourceSKUsClient, - routeTables: routeTables, - securityGroups: securityGroups, - storageAccounts: storage.NewAccountsClient(env.Environment(), subscriptionDoc.ID, fpAuth), - virtualMachines: compute.NewVirtualMachinesClient(env.Environment(), subscriptionDoc.ID, fpAuth), - virtualNetworks: virtualNetworks, + networkInterfaces: networkInterfaces, + diskEncryptionSets: compute.NewDiskEncryptionSetsClientWithAROEnvironment(env.Environment(), subscriptionDoc.ID, fpAuth), + loadBalancers: loadBalancers, + resources: features.NewResourcesClient(env.Environment(), subscriptionDoc.ID, fpAuth), + resourceSkus: armResourceSKUsClient, + routeTables: routeTables, + securityGroups: securityGroups, + storageAccounts: storage.NewAccountsClient(env.Environment(), subscriptionDoc.ID, fpAuth), + virtualMachines: compute.NewVirtualMachinesClient(env.Environment(), subscriptionDoc.ID, fpAuth), + virtualNetworks: virtualNetworks, + armVirtualMachines: armVMsClient, + armCapacityReservationGroups: armCRGClient, + armCapacityReservations: armCRClient, }, nil } diff --git a/pkg/frontend/adminactions/azureactions_vmresize_capacity.go b/pkg/frontend/adminactions/azureactions_vmresize_capacity.go new file mode 100644 index 00000000000..2e63472b039 --- /dev/null +++ b/pkg/frontend/adminactions/azureactions_vmresize_capacity.go @@ -0,0 +1,320 @@ +package adminactions + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "errors" + "fmt" + "strings" + + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" + + "github.com/Azure/ARO-RP/pkg/util/pointerutils" + "github.com/Azure/ARO-RP/pkg/util/stringutils" +) + +const ( + capacityReservationGroupName = "aro-resize-crg" + currentReservationNameFmt = "cr-current-z%s" + targetReservationNameFmt = "cr-target-z%s" +) + +// VMResizeWithCapacityReservation resizes all master VMs to the target SKU using +// Azure Capacity Reservation Groups to guarantee capacity in each availability zone. +// +// Flow: +// 1. List master VMs and record their zones. +// 2. Create a Capacity Reservation Group (CRG). +// 3. Create current-SKU and target-SKU reservations per zone before touching any VM. +// If target capacity is unavailable the rollback is simple: delete reservations + CRG. +// 4. Associate all master VMs with the CRG. +// 5. Resize each VM one at a time (deallocate → resize → start) to preserve quorum. +// 6. Cleanup: set target reservations to capacity 0, disassociate VMs, +// delete all reservations, delete the CRG. +// Cleanup errors are returned — lingering reservations incur ongoing Azure costs. +func (a *azureActions) VMResizeWithCapacityReservation(ctx context.Context, targetVMSize string) error { + clusterRG := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/') + location := a.oc.Location + + // Step 1: discover master VMs and their zones. + masterVMs, err := a.listMasterVMs(ctx, clusterRG) + if err != nil { + return fmt.Errorf("listing master VMs: %w", err) + } + if len(masterVMs) == 0 { + return fmt.Errorf("no master VMs found in resource group %s", clusterRG) + } + var zones []string + seenZones := map[string]bool{} + for _, vm := range masterVMs { + z := vmZone(vm) + if z == "" { + return fmt.Errorf("VM %s has no availability zone; capacity reservation resize requires zonal VMs", *vm.Name) + } + if !seenZones[z] { + seenZones[z] = true + zones = append(zones, z) + } + } + + // Step 2: create the Capacity Reservation Group with the same zones as the master VMs. + // Azure requires the CRG to declare all zones it will serve. + a.log.Infof("creating capacity reservation group %q in zones %v", capacityReservationGroupName, zones) + crg, err := a.armCapacityReservationGroups.CreateOrUpdate(ctx, clusterRG, capacityReservationGroupName, + armcompute.CapacityReservationGroup{ + Location: &location, + Zones: pointerutils.ToSlicePtr(zones), + }) + if err != nil { + return fmt.Errorf("creating capacity reservation group: %w", err) + } + + // Step 3a: create one current-SKU reservation per zone, using each VM's actual + // hardware SKU. This handles the case where a previous partial resize left one + // or more masters on a different family. + // Capture zoneCurrentSKU now — after resize the VMs will report the target SKU. + zoneCurrentSKU := make(map[string]string, len(masterVMs)) + a.log.Info("creating current-SKU capacity reservations") + for _, vm := range masterVMs { + zone := vmZone(vm) + if vm.Properties == nil || vm.Properties.HardwareProfile == nil || vm.Properties.HardwareProfile.VMSize == nil { + if cleanupErr := a.cleanupReservationsAndCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after SKU read failure also failed: %v", cleanupErr) + } + return fmt.Errorf("VM %s has no hardware profile SKU", *vm.Name) + } + actualVMSize := string(*vm.Properties.HardwareProfile.VMSize) + zoneCurrentSKU[zone] = actualVMSize + + crName := fmt.Sprintf(currentReservationNameFmt, zone) + a.log.Infof("creating current-SKU reservation %s (SKU %s) in zone %s", crName, actualVMSize, zone) + err = a.armCapacityReservations.CreateOrUpdateAndWait(ctx, clusterRG, capacityReservationGroupName, crName, + armcompute.CapacityReservation{ + Location: &location, + SKU: &armcompute.SKU{Name: &actualVMSize, Capacity: pointerutils.ToPtr(int64(1))}, + Zones: []*string{pointerutils.ToPtr(zone)}, + }) + if err != nil { + if cleanupErr := a.cleanupReservationsAndCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after current-SKU reservation failure also failed: %v", cleanupErr) + } + return fmt.Errorf("creating current-SKU reservation for VM %s in zone %s: %w", *vm.Name, zone, err) + } + } + + // Step 3b: create target-SKU reservations before associating any VM. + // Failing here means no VM has been touched — rollback is just delete reservations + CRG. + a.log.Infof("creating target-SKU capacity reservations for %s", targetVMSize) + for _, vm := range masterVMs { + zone := vmZone(vm) + crName := fmt.Sprintf(targetReservationNameFmt, zone) + err = a.armCapacityReservations.CreateOrUpdateAndWait(ctx, clusterRG, capacityReservationGroupName, crName, + armcompute.CapacityReservation{ + Location: &location, + SKU: &armcompute.SKU{Name: &targetVMSize, Capacity: pointerutils.ToPtr(int64(1))}, + Zones: []*string{pointerutils.ToPtr(zone)}, + }) + if err != nil { + if cleanupErr := a.cleanupReservationsAndCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after target-SKU reservation failure also failed: %v", cleanupErr) + } + return fmt.Errorf( + "target SKU %s has insufficient capacity in zone %s — consider choosing a different VM family: %w", + targetVMSize, zone, err) + } + } + + // Step 4: associate all master VMs with the CRG. + // From this point on, cleanup must disassociate VMs before deleting reservations. + a.log.Info("associating master VMs with capacity reservation group") + for i := range masterVMs { + masterVMs[i].Properties.CapacityReservation = &armcompute.CapacityReservationProfile{ + CapacityReservationGroup: &armcompute.SubResource{ID: crg.ID}, + } + if err = a.armVirtualMachines.CreateOrUpdateAndWait(ctx, clusterRG, *masterVMs[i].Name, masterVMs[i]); err != nil { + if cleanupErr := a.cleanupCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after association failure also failed: %v", cleanupErr) + } + return fmt.Errorf("associating VM %s with capacity reservation group: %w", *masterVMs[i].Name, err) + } + } + + // Step 5: resize each master VM one at a time to maintain etcd quorum. + for i := range masterVMs { + vmName := *masterVMs[i].Name + a.log.Infof("resizing VM %s to %s", vmName, targetVMSize) + + if err = a.armVirtualMachines.DeallocateAndWait(ctx, clusterRG, vmName); err != nil { + if cleanupErr := a.cleanupCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after deallocate failure also failed: %v", cleanupErr) + } + return fmt.Errorf("deallocating VM %s: %w", vmName, err) + } + + // Re-read to get the latest VM state after deallocate. + masterVMs[i], err = a.armVirtualMachines.Get(ctx, clusterRG, vmName) + if err != nil { + if cleanupErr := a.cleanupCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after VM read failure also failed: %v", cleanupErr) + } + return fmt.Errorf("reading VM %s after deallocate: %w", vmName, err) + } + masterVMs[i].Properties.HardwareProfile.VMSize = (*armcompute.VirtualMachineSizeTypes)(&targetVMSize) + + if err = a.armVirtualMachines.CreateOrUpdateAndWait(ctx, clusterRG, vmName, masterVMs[i]); err != nil { + if cleanupErr := a.cleanupCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after resize failure also failed: %v", cleanupErr) + } + return fmt.Errorf("resizing VM %s: %w", vmName, err) + } + + if err = a.armVirtualMachines.StartAndWait(ctx, clusterRG, vmName); err != nil { + if cleanupErr := a.cleanupCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); cleanupErr != nil { + a.log.Warnf("cleanup after start failure also failed: %v", cleanupErr) + } + return fmt.Errorf("starting VM %s after resize: %w", vmName, err) + } + } + + // Step 6: success — disassociate VMs and delete all reservation resources. + // Errors are returned: lingering reservations incur ongoing Azure costs. + a.log.Info("resize complete, cleaning up capacity reservation resources") + if err := a.cleanupCRG(ctx, location, clusterRG, targetVMSize, zoneCurrentSKU, masterVMs); err != nil { + return fmt.Errorf("resize succeeded but failed to clean up capacity reservation resources (manual cleanup required to avoid ongoing costs): %w", err) + } + return nil +} + +// listMasterVMs returns VMs in the cluster RG whose names contain "master". +func (a *azureActions) listMasterVMs(ctx context.Context, clusterRG string) ([]armcompute.VirtualMachine, error) { + allVMs, err := a.armVirtualMachines.List(ctx, clusterRG) + if err != nil { + return nil, err + } + var masters []armcompute.VirtualMachine + for _, vm := range allVMs { + if vm.Name != nil && strings.Contains(*vm.Name, "master") { + masters = append(masters, vm) + } + } + return masters, nil +} + +// vmZone returns the availability zone of a VM (e.g. "1", "2", "3"), or "" if the VM is non-zonal. +func vmZone(vm armcompute.VirtualMachine) string { + if len(vm.Zones) > 0 && vm.Zones[0] != nil { + return *vm.Zones[0] + } + return "" +} + +// setReservationCapacityZero updates a capacity reservation's capacity to 0. +// Azure requires this before a reservation can be deleted. +func (a *azureActions) setReservationCapacityZero(ctx context.Context, location, clusterRG, crName, zone, skuName string) error { + return a.armCapacityReservations.CreateOrUpdateAndWait(ctx, clusterRG, capacityReservationGroupName, crName, + armcompute.CapacityReservation{ + Location: &location, + SKU: &armcompute.SKU{Name: &skuName, Capacity: pointerutils.ToPtr(int64(0))}, + Zones: []*string{pointerutils.ToPtr(zone)}, + }) +} + +// cleanupReservationsAndCRG deletes all capacity reservations (current and target) and +// the CRG. Used when VMs have NOT been associated with the CRG — no VM disassociation needed. +// Each reservation's capacity is set to 0 before deletion as required by Azure. +// Returns a joined error of all failures. +func (a *azureActions) cleanupReservationsAndCRG(ctx context.Context, location, clusterRG, targetVMSize string, zoneCurrentSKU map[string]string, masterVMs []armcompute.VirtualMachine) error { + var errs []error + + for _, vm := range masterVMs { + zone := vmZone(vm) + + // Set target reservation capacity to 0 then delete. + targetCRName := fmt.Sprintf(targetReservationNameFmt, zone) + if err := a.setReservationCapacityZero(ctx, location, clusterRG, targetCRName, zone, targetVMSize); err != nil { + errs = append(errs, fmt.Errorf("set target reservation %s capacity to 0: %w", targetCRName, err)) + } + if err := a.armCapacityReservations.DeleteAndWait(ctx, clusterRG, capacityReservationGroupName, targetCRName); err != nil { + errs = append(errs, fmt.Errorf("delete target reservation %s: %w", targetCRName, err)) + } + + // Set current reservation capacity to 0 then delete. + currentCRName := fmt.Sprintf(currentReservationNameFmt, zone) + if currentSKU, ok := zoneCurrentSKU[zone]; ok { + if err := a.setReservationCapacityZero(ctx, location, clusterRG, currentCRName, zone, currentSKU); err != nil { + errs = append(errs, fmt.Errorf("set current reservation %s capacity to 0: %w", currentCRName, err)) + } + } + if err := a.armCapacityReservations.DeleteAndWait(ctx, clusterRG, capacityReservationGroupName, currentCRName); err != nil { + errs = append(errs, fmt.Errorf("delete current reservation %s: %w", currentCRName, err)) + } + } + + if err := a.armCapacityReservationGroups.Delete(ctx, clusterRG, capacityReservationGroupName); err != nil { + errs = append(errs, fmt.Errorf("delete capacity reservation group: %w", err)) + } + return errors.Join(errs...) +} + +// cleanupCRG handles cleanup when VMs are already associated with the CRG. +// Sequence per Azure requirements: +// 1. Set target reservation capacity to 0 (per zone). +// 2. Disassociate each VM from the CRG. +// 3. Delete target reservations. +// 4. Set current reservation capacity to 0 (per zone) and delete. +// 5. Delete the CRG. +// +// Returns a joined error of all failures. +func (a *azureActions) cleanupCRG(ctx context.Context, location, clusterRG, targetVMSize string, zoneCurrentSKU map[string]string, masterVMs []armcompute.VirtualMachine) error { + var errs []error + + // Step 1: set target reservation capacity to 0 before disassociating VMs. + for _, vm := range masterVMs { + zone := vmZone(vm) + crName := fmt.Sprintf(targetReservationNameFmt, zone) + if err := a.setReservationCapacityZero(ctx, location, clusterRG, crName, zone, targetVMSize); err != nil { + errs = append(errs, fmt.Errorf("set target reservation %s capacity to 0: %w", crName, err)) + } + } + + // Step 2: disassociate each VM from the CRG. + for i := range masterVMs { + vmName := *masterVMs[i].Name + masterVMs[i].Properties.CapacityReservation = nil + if err := a.armVirtualMachines.CreateOrUpdateAndWait(ctx, clusterRG, vmName, masterVMs[i]); err != nil { + errs = append(errs, fmt.Errorf("disassociate VM %s from CRG: %w", vmName, err)) + } + } + + // Step 3: delete target reservations (capacity is already 0). + for _, vm := range masterVMs { + zone := vmZone(vm) + crName := fmt.Sprintf(targetReservationNameFmt, zone) + if err := a.armCapacityReservations.DeleteAndWait(ctx, clusterRG, capacityReservationGroupName, crName); err != nil { + errs = append(errs, fmt.Errorf("delete target reservation %s: %w", crName, err)) + } + } + + // Step 4: set current reservation capacity to 0 then delete. + // No VMs are consuming these (all VMs were resized to the target SKU). + for _, vm := range masterVMs { + zone := vmZone(vm) + crName := fmt.Sprintf(currentReservationNameFmt, zone) + if currentSKU, ok := zoneCurrentSKU[zone]; ok { + if err := a.setReservationCapacityZero(ctx, location, clusterRG, crName, zone, currentSKU); err != nil { + errs = append(errs, fmt.Errorf("set current reservation %s capacity to 0: %w", crName, err)) + } + } + if err := a.armCapacityReservations.DeleteAndWait(ctx, clusterRG, capacityReservationGroupName, crName); err != nil { + errs = append(errs, fmt.Errorf("delete current reservation %s: %w", crName, err)) + } + } + + // Step 5: delete the CRG last. + if err := a.armCapacityReservationGroups.Delete(ctx, clusterRG, capacityReservationGroupName); err != nil { + errs = append(errs, fmt.Errorf("delete capacity reservation group: %w", err)) + } + return errors.Join(errs...) +} diff --git a/pkg/util/azureclient/azuresdk/armcompute/capacityreservationgroups.go b/pkg/util/azureclient/azuresdk/armcompute/capacityreservationgroups.go new file mode 100644 index 00000000000..c0731318685 --- /dev/null +++ b/pkg/util/azureclient/azuresdk/armcompute/capacityreservationgroups.go @@ -0,0 +1,45 @@ +package armcompute + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" + + "github.com/Azure/ARO-RP/pkg/util/azureclient" +) + +// CapacityReservationGroupsClient is a minimal interface for armcompute CapacityReservationGroupsClient +type CapacityReservationGroupsClient interface { + CapacityReservationGroupsClientAddons +} + +type capacityReservationGroupsClient struct { + *armcompute.CapacityReservationGroupsClient +} + +var _ CapacityReservationGroupsClient = &capacityReservationGroupsClient{} + +// NewDefaultCapacityReservationGroupsClient creates a new CapacityReservationGroupsClient with default options +func NewDefaultCapacityReservationGroupsClient(environment *azureclient.AROEnvironment, subscriptionID string, credential azcore.TokenCredential) (CapacityReservationGroupsClient, error) { + options := &arm.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: environment.Cloud, + }, + } + return NewCapacityReservationGroupsClient(subscriptionID, credential, options) +} + +// NewCapacityReservationGroupsClient creates a new CapacityReservationGroupsClient +func NewCapacityReservationGroupsClient(subscriptionID string, credential azcore.TokenCredential, options *arm.ClientOptions) (CapacityReservationGroupsClient, error) { + clientFactory, err := armcompute.NewClientFactory(subscriptionID, credential, options) + if err != nil { + return nil, err + } + + return &capacityReservationGroupsClient{ + CapacityReservationGroupsClient: clientFactory.NewCapacityReservationGroupsClient(), + }, nil +} diff --git a/pkg/util/azureclient/azuresdk/armcompute/capacityreservationgroups_addons.go b/pkg/util/azureclient/azuresdk/armcompute/capacityreservationgroups_addons.go new file mode 100644 index 00000000000..d8abd7c664f --- /dev/null +++ b/pkg/util/azureclient/azuresdk/armcompute/capacityreservationgroups_addons.go @@ -0,0 +1,29 @@ +package armcompute + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" +) + +// CapacityReservationGroupsClientAddons contains addons for CapacityReservationGroupsClient +type CapacityReservationGroupsClientAddons interface { + CreateOrUpdate(ctx context.Context, resourceGroupName, capacityReservationGroupName string, parameters armcompute.CapacityReservationGroup) (armcompute.CapacityReservationGroup, error) + Delete(ctx context.Context, resourceGroupName, capacityReservationGroupName string) error +} + +func (c *capacityReservationGroupsClient) CreateOrUpdate(ctx context.Context, resourceGroupName, capacityReservationGroupName string, parameters armcompute.CapacityReservationGroup) (armcompute.CapacityReservationGroup, error) { + resp, err := c.CapacityReservationGroupsClient.CreateOrUpdate(ctx, resourceGroupName, capacityReservationGroupName, parameters, nil) + if err != nil { + return armcompute.CapacityReservationGroup{}, err + } + return resp.CapacityReservationGroup, nil +} + +func (c *capacityReservationGroupsClient) Delete(ctx context.Context, resourceGroupName, capacityReservationGroupName string) error { + _, err := c.CapacityReservationGroupsClient.Delete(ctx, resourceGroupName, capacityReservationGroupName, nil) + return err +} diff --git a/pkg/util/azureclient/azuresdk/armcompute/capacityreservations.go b/pkg/util/azureclient/azuresdk/armcompute/capacityreservations.go new file mode 100644 index 00000000000..e6cc6ac9a69 --- /dev/null +++ b/pkg/util/azureclient/azuresdk/armcompute/capacityreservations.go @@ -0,0 +1,45 @@ +package armcompute + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" + + "github.com/Azure/ARO-RP/pkg/util/azureclient" +) + +// CapacityReservationsClient is a minimal interface for armcompute CapacityReservationsClient +type CapacityReservationsClient interface { + CapacityReservationsClientAddons +} + +type capacityReservationsClient struct { + *armcompute.CapacityReservationsClient +} + +var _ CapacityReservationsClient = &capacityReservationsClient{} + +// NewDefaultCapacityReservationsClient creates a new CapacityReservationsClient with default options +func NewDefaultCapacityReservationsClient(environment *azureclient.AROEnvironment, subscriptionID string, credential azcore.TokenCredential) (CapacityReservationsClient, error) { + options := &arm.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: environment.Cloud, + }, + } + return NewCapacityReservationsClient(subscriptionID, credential, options) +} + +// NewCapacityReservationsClient creates a new CapacityReservationsClient +func NewCapacityReservationsClient(subscriptionID string, credential azcore.TokenCredential, options *arm.ClientOptions) (CapacityReservationsClient, error) { + clientFactory, err := armcompute.NewClientFactory(subscriptionID, credential, options) + if err != nil { + return nil, err + } + + return &capacityReservationsClient{ + CapacityReservationsClient: clientFactory.NewCapacityReservationsClient(), + }, nil +} diff --git a/pkg/util/azureclient/azuresdk/armcompute/capacityreservations_addons.go b/pkg/util/azureclient/azuresdk/armcompute/capacityreservations_addons.go new file mode 100644 index 00000000000..e3065ebd8db --- /dev/null +++ b/pkg/util/azureclient/azuresdk/armcompute/capacityreservations_addons.go @@ -0,0 +1,34 @@ +package armcompute + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" +) + +// CapacityReservationsClientAddons contains addons for CapacityReservationsClient +type CapacityReservationsClientAddons interface { + CreateOrUpdateAndWait(ctx context.Context, resourceGroupName, capacityReservationGroupName, capacityReservationName string, parameters armcompute.CapacityReservation) error + DeleteAndWait(ctx context.Context, resourceGroupName, capacityReservationGroupName, capacityReservationName string) error +} + +func (c *capacityReservationsClient) CreateOrUpdateAndWait(ctx context.Context, resourceGroupName, capacityReservationGroupName, capacityReservationName string, parameters armcompute.CapacityReservation) error { + poller, err := c.BeginCreateOrUpdate(ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName, parameters, nil) + if err != nil { + return err + } + _, err = poller.PollUntilDone(ctx, nil) + return err +} + +func (c *capacityReservationsClient) DeleteAndWait(ctx context.Context, resourceGroupName, capacityReservationGroupName, capacityReservationName string) error { + poller, err := c.BeginDelete(ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName, nil) + if err != nil { + return err + } + _, err = poller.PollUntilDone(ctx, nil) + return err +} diff --git a/pkg/util/azureclient/azuresdk/armcompute/generate.go b/pkg/util/azureclient/azuresdk/armcompute/generate.go index 6907458944d..079c3acff89 100644 --- a/pkg/util/azureclient/azuresdk/armcompute/generate.go +++ b/pkg/util/azureclient/azuresdk/armcompute/generate.go @@ -5,3 +5,6 @@ package armcompute //go:generate rm -rf ../../../../util/mocks/$GOPACKAGE //go:generate mockgen -destination=../../../../util/mocks/azureclient/azuresdk/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/$GOPACKAGE ResourceSKUsClient +//go:generate mockgen -source ./capacityreservationgroups.go -destination=../../../../util/mocks/azureclient/azuresdk/$GOPACKAGE/capacityreservationgroups.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/$GOPACKAGE CapacityReservationGroupsClient +//go:generate mockgen -source ./capacityreservations.go -destination=../../../../util/mocks/azureclient/azuresdk/$GOPACKAGE/capacityreservations.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/$GOPACKAGE CapacityReservationsClient +//go:generate mockgen -source ./virtualmachines.go -destination=../../../../util/mocks/azureclient/azuresdk/$GOPACKAGE/virtualmachines.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/$GOPACKAGE VirtualMachinesClient diff --git a/pkg/util/azureclient/azuresdk/armcompute/virtualmachines.go b/pkg/util/azureclient/azuresdk/armcompute/virtualmachines.go new file mode 100644 index 00000000000..a12ad4fa4ec --- /dev/null +++ b/pkg/util/azureclient/azuresdk/armcompute/virtualmachines.go @@ -0,0 +1,46 @@ +package armcompute + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" + + "github.com/Azure/ARO-RP/pkg/util/azureclient" +) + +// VirtualMachinesClient is a minimal interface for armcompute VirtualMachinesClient, +// used for capacity-reservation-aware VM operations. +type VirtualMachinesClient interface { + VirtualMachinesClientAddons +} + +type virtualMachinesClient struct { + *armcompute.VirtualMachinesClient +} + +var _ VirtualMachinesClient = &virtualMachinesClient{} + +// NewDefaultVirtualMachinesClient creates a new VirtualMachinesClient with default options +func NewDefaultVirtualMachinesClient(environment *azureclient.AROEnvironment, subscriptionID string, credential azcore.TokenCredential) (VirtualMachinesClient, error) { + options := &arm.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: environment.Cloud, + }, + } + return NewVirtualMachinesClient(subscriptionID, credential, options) +} + +// NewVirtualMachinesClient creates a new VirtualMachinesClient +func NewVirtualMachinesClient(subscriptionID string, credential azcore.TokenCredential, options *arm.ClientOptions) (VirtualMachinesClient, error) { + clientFactory, err := armcompute.NewClientFactory(subscriptionID, credential, options) + if err != nil { + return nil, err + } + + return &virtualMachinesClient{ + VirtualMachinesClient: clientFactory.NewVirtualMachinesClient(), + }, nil +} diff --git a/pkg/util/azureclient/azuresdk/armcompute/virtualmachines_addons.go b/pkg/util/azureclient/azuresdk/armcompute/virtualmachines_addons.go new file mode 100644 index 00000000000..86c5a7974cf --- /dev/null +++ b/pkg/util/azureclient/azuresdk/armcompute/virtualmachines_addons.go @@ -0,0 +1,71 @@ +package armcompute + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" +) + +// VirtualMachinesClientAddons contains addons for VirtualMachinesClient +type VirtualMachinesClientAddons interface { + Get(ctx context.Context, resourceGroupName, vmName string) (armcompute.VirtualMachine, error) + List(ctx context.Context, resourceGroupName string) ([]armcompute.VirtualMachine, error) + CreateOrUpdateAndWait(ctx context.Context, resourceGroupName, vmName string, parameters armcompute.VirtualMachine) error + DeallocateAndWait(ctx context.Context, resourceGroupName, vmName string) error + StartAndWait(ctx context.Context, resourceGroupName, vmName string) error +} + +func (c *virtualMachinesClient) Get(ctx context.Context, resourceGroupName, vmName string) (armcompute.VirtualMachine, error) { + resp, err := c.VirtualMachinesClient.Get(ctx, resourceGroupName, vmName, nil) + if err != nil { + return armcompute.VirtualMachine{}, err + } + return resp.VirtualMachine, nil +} + +func (c *virtualMachinesClient) List(ctx context.Context, resourceGroupName string) ([]armcompute.VirtualMachine, error) { + var result []armcompute.VirtualMachine + pager := c.NewListPager(resourceGroupName, nil) + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, err + } + for _, vm := range page.Value { + if vm != nil { + result = append(result, *vm) + } + } + } + return result, nil +} + +func (c *virtualMachinesClient) CreateOrUpdateAndWait(ctx context.Context, resourceGroupName, vmName string, parameters armcompute.VirtualMachine) error { + poller, err := c.BeginCreateOrUpdate(ctx, resourceGroupName, vmName, parameters, nil) + if err != nil { + return err + } + _, err = poller.PollUntilDone(ctx, nil) + return err +} + +func (c *virtualMachinesClient) DeallocateAndWait(ctx context.Context, resourceGroupName, vmName string) error { + poller, err := c.BeginDeallocate(ctx, resourceGroupName, vmName, nil) + if err != nil { + return err + } + _, err = poller.PollUntilDone(ctx, nil) + return err +} + +func (c *virtualMachinesClient) StartAndWait(ctx context.Context, resourceGroupName, vmName string) error { + poller, err := c.BeginStart(ctx, resourceGroupName, vmName, nil) + if err != nil { + return err + } + _, err = poller.PollUntilDone(ctx, nil) + return err +} diff --git a/pkg/util/mocks/adminactions/azureactions.go b/pkg/util/mocks/adminactions/azureactions.go index 8ac80e6bfd0..c18cbbeca92 100644 --- a/pkg/util/mocks/adminactions/azureactions.go +++ b/pkg/util/mocks/adminactions/azureactions.go @@ -160,6 +160,20 @@ func (mr *MockAzureActionsMockRecorder) VMResize(ctx, vmName, vmSize any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMResize", reflect.TypeOf((*MockAzureActions)(nil).VMResize), ctx, vmName, vmSize) } +// VMResizeWithCapacityReservation mocks base method. +func (m *MockAzureActions) VMResizeWithCapacityReservation(ctx context.Context, targetVMSize string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VMResizeWithCapacityReservation", ctx, targetVMSize) + ret0, _ := ret[0].(error) + return ret0 +} + +// VMResizeWithCapacityReservation indicates an expected call of VMResizeWithCapacityReservation. +func (mr *MockAzureActionsMockRecorder) VMResizeWithCapacityReservation(ctx, targetVMSize any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMResizeWithCapacityReservation", reflect.TypeOf((*MockAzureActions)(nil).VMResizeWithCapacityReservation), ctx, targetVMSize) +} + // VMSerialConsole mocks base method. func (m *MockAzureActions) VMSerialConsole(ctx context.Context, log *logrus.Entry, vmName string, target io.Writer) error { m.ctrl.T.Helper() diff --git a/pkg/util/mocks/azureclient/azuresdk/armcompute/capacityreservationgroups.go b/pkg/util/mocks/azureclient/azuresdk/armcompute/capacityreservationgroups.go new file mode 100644 index 00000000000..76f7d41e611 --- /dev/null +++ b/pkg/util/mocks/azureclient/azuresdk/armcompute/capacityreservationgroups.go @@ -0,0 +1,71 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./capacityreservationgroups.go +// +// Generated by this command: +// +// mockgen -source ./capacityreservationgroups.go -destination=../../../../util/mocks/azureclient/azuresdk/armcompute/capacityreservationgroups.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armcompute CapacityReservationGroupsClient +// + +// Package mock_armcompute is a generated GoMock package. +package mock_armcompute + +import ( + context "context" + reflect "reflect" + + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" + gomock "go.uber.org/mock/gomock" +) + +// MockCapacityReservationGroupsClient is a mock of CapacityReservationGroupsClient interface. +type MockCapacityReservationGroupsClient struct { + ctrl *gomock.Controller + recorder *MockCapacityReservationGroupsClientMockRecorder + isgomock struct{} +} + +// MockCapacityReservationGroupsClientMockRecorder is the mock recorder for MockCapacityReservationGroupsClient. +type MockCapacityReservationGroupsClientMockRecorder struct { + mock *MockCapacityReservationGroupsClient +} + +// NewMockCapacityReservationGroupsClient creates a new mock instance. +func NewMockCapacityReservationGroupsClient(ctrl *gomock.Controller) *MockCapacityReservationGroupsClient { + mock := &MockCapacityReservationGroupsClient{ctrl: ctrl} + mock.recorder = &MockCapacityReservationGroupsClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCapacityReservationGroupsClient) EXPECT() *MockCapacityReservationGroupsClientMockRecorder { + return m.recorder +} + +// CreateOrUpdate mocks base method. +func (m *MockCapacityReservationGroupsClient) CreateOrUpdate(ctx context.Context, resourceGroupName, capacityReservationGroupName string, parameters armcompute.CapacityReservationGroup) (armcompute.CapacityReservationGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, capacityReservationGroupName, parameters) + ret0, _ := ret[0].(armcompute.CapacityReservationGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOrUpdate indicates an expected call of CreateOrUpdate. +func (mr *MockCapacityReservationGroupsClientMockRecorder) CreateOrUpdate(ctx, resourceGroupName, capacityReservationGroupName, parameters any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockCapacityReservationGroupsClient)(nil).CreateOrUpdate), ctx, resourceGroupName, capacityReservationGroupName, parameters) +} + +// Delete mocks base method. +func (m *MockCapacityReservationGroupsClient) Delete(ctx context.Context, resourceGroupName, capacityReservationGroupName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, capacityReservationGroupName) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockCapacityReservationGroupsClientMockRecorder) Delete(ctx, resourceGroupName, capacityReservationGroupName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCapacityReservationGroupsClient)(nil).Delete), ctx, resourceGroupName, capacityReservationGroupName) +} diff --git a/pkg/util/mocks/azureclient/azuresdk/armcompute/capacityreservations.go b/pkg/util/mocks/azureclient/azuresdk/armcompute/capacityreservations.go new file mode 100644 index 00000000000..1512e3746e5 --- /dev/null +++ b/pkg/util/mocks/azureclient/azuresdk/armcompute/capacityreservations.go @@ -0,0 +1,70 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./capacityreservations.go +// +// Generated by this command: +// +// mockgen -source ./capacityreservations.go -destination=../../../../util/mocks/azureclient/azuresdk/armcompute/capacityreservations.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armcompute CapacityReservationsClient +// + +// Package mock_armcompute is a generated GoMock package. +package mock_armcompute + +import ( + context "context" + reflect "reflect" + + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" + gomock "go.uber.org/mock/gomock" +) + +// MockCapacityReservationsClient is a mock of CapacityReservationsClient interface. +type MockCapacityReservationsClient struct { + ctrl *gomock.Controller + recorder *MockCapacityReservationsClientMockRecorder + isgomock struct{} +} + +// MockCapacityReservationsClientMockRecorder is the mock recorder for MockCapacityReservationsClient. +type MockCapacityReservationsClientMockRecorder struct { + mock *MockCapacityReservationsClient +} + +// NewMockCapacityReservationsClient creates a new mock instance. +func NewMockCapacityReservationsClient(ctrl *gomock.Controller) *MockCapacityReservationsClient { + mock := &MockCapacityReservationsClient{ctrl: ctrl} + mock.recorder = &MockCapacityReservationsClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCapacityReservationsClient) EXPECT() *MockCapacityReservationsClientMockRecorder { + return m.recorder +} + +// CreateOrUpdateAndWait mocks base method. +func (m *MockCapacityReservationsClient) CreateOrUpdateAndWait(ctx context.Context, resourceGroupName, capacityReservationGroupName, capacityReservationName string, parameters armcompute.CapacityReservation) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOrUpdateAndWait", ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName, parameters) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateOrUpdateAndWait indicates an expected call of CreateOrUpdateAndWait. +func (mr *MockCapacityReservationsClientMockRecorder) CreateOrUpdateAndWait(ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName, parameters any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateAndWait", reflect.TypeOf((*MockCapacityReservationsClient)(nil).CreateOrUpdateAndWait), ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName, parameters) +} + +// DeleteAndWait mocks base method. +func (m *MockCapacityReservationsClient) DeleteAndWait(ctx context.Context, resourceGroupName, capacityReservationGroupName, capacityReservationName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAndWait", ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAndWait indicates an expected call of DeleteAndWait. +func (mr *MockCapacityReservationsClientMockRecorder) DeleteAndWait(ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAndWait", reflect.TypeOf((*MockCapacityReservationsClient)(nil).DeleteAndWait), ctx, resourceGroupName, capacityReservationGroupName, capacityReservationName) +} diff --git a/pkg/util/mocks/azureclient/azuresdk/armcompute/virtualmachines.go b/pkg/util/mocks/azureclient/azuresdk/armcompute/virtualmachines.go new file mode 100644 index 00000000000..1dacff12f72 --- /dev/null +++ b/pkg/util/mocks/azureclient/azuresdk/armcompute/virtualmachines.go @@ -0,0 +1,114 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./virtualmachines.go +// +// Generated by this command: +// +// mockgen -source ./virtualmachines.go -destination=../../../../util/mocks/azureclient/azuresdk/armcompute/virtualmachines.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armcompute VirtualMachinesClient +// + +// Package mock_armcompute is a generated GoMock package. +package mock_armcompute + +import ( + context "context" + reflect "reflect" + + armcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7" + gomock "go.uber.org/mock/gomock" +) + +// MockVirtualMachinesClient is a mock of VirtualMachinesClient interface. +type MockVirtualMachinesClient struct { + ctrl *gomock.Controller + recorder *MockVirtualMachinesClientMockRecorder + isgomock struct{} +} + +// MockVirtualMachinesClientMockRecorder is the mock recorder for MockVirtualMachinesClient. +type MockVirtualMachinesClientMockRecorder struct { + mock *MockVirtualMachinesClient +} + +// NewMockVirtualMachinesClient creates a new mock instance. +func NewMockVirtualMachinesClient(ctrl *gomock.Controller) *MockVirtualMachinesClient { + mock := &MockVirtualMachinesClient{ctrl: ctrl} + mock.recorder = &MockVirtualMachinesClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVirtualMachinesClient) EXPECT() *MockVirtualMachinesClientMockRecorder { + return m.recorder +} + +// CreateOrUpdateAndWait mocks base method. +func (m *MockVirtualMachinesClient) CreateOrUpdateAndWait(ctx context.Context, resourceGroupName, vmName string, parameters armcompute.VirtualMachine) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOrUpdateAndWait", ctx, resourceGroupName, vmName, parameters) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateOrUpdateAndWait indicates an expected call of CreateOrUpdateAndWait. +func (mr *MockVirtualMachinesClientMockRecorder) CreateOrUpdateAndWait(ctx, resourceGroupName, vmName, parameters any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateAndWait", reflect.TypeOf((*MockVirtualMachinesClient)(nil).CreateOrUpdateAndWait), ctx, resourceGroupName, vmName, parameters) +} + +// DeallocateAndWait mocks base method. +func (m *MockVirtualMachinesClient) DeallocateAndWait(ctx context.Context, resourceGroupName, vmName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeallocateAndWait", ctx, resourceGroupName, vmName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeallocateAndWait indicates an expected call of DeallocateAndWait. +func (mr *MockVirtualMachinesClientMockRecorder) DeallocateAndWait(ctx, resourceGroupName, vmName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeallocateAndWait", reflect.TypeOf((*MockVirtualMachinesClient)(nil).DeallocateAndWait), ctx, resourceGroupName, vmName) +} + +// Get mocks base method. +func (m *MockVirtualMachinesClient) Get(ctx context.Context, resourceGroupName, vmName string) (armcompute.VirtualMachine, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, vmName) + ret0, _ := ret[0].(armcompute.VirtualMachine) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockVirtualMachinesClientMockRecorder) Get(ctx, resourceGroupName, vmName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockVirtualMachinesClient)(nil).Get), ctx, resourceGroupName, vmName) +} + +// List mocks base method. +func (m *MockVirtualMachinesClient) List(ctx context.Context, resourceGroupName string) ([]armcompute.VirtualMachine, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", ctx, resourceGroupName) + ret0, _ := ret[0].([]armcompute.VirtualMachine) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockVirtualMachinesClientMockRecorder) List(ctx, resourceGroupName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockVirtualMachinesClient)(nil).List), ctx, resourceGroupName) +} + +// StartAndWait mocks base method. +func (m *MockVirtualMachinesClient) StartAndWait(ctx context.Context, resourceGroupName, vmName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartAndWait", ctx, resourceGroupName, vmName) + ret0, _ := ret[0].(error) + return ret0 +} + +// StartAndWait indicates an expected call of StartAndWait. +func (mr *MockVirtualMachinesClientMockRecorder) StartAndWait(ctx, resourceGroupName, vmName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartAndWait", reflect.TypeOf((*MockVirtualMachinesClient)(nil).StartAndWait), ctx, resourceGroupName, vmName) +}