pkg/fake/virtualmachinesapi.go (176 lines of code) (raw):
/*
Portions Copyright (c) Microsoft Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fake
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/Azure/azure-sdk-for-go-extensions/pkg/errors"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/go-autorest/autorest/to"
"github.com/Azure/karpenter-provider-azure/pkg/providers/instance"
"github.com/Azure/karpenter-provider-azure/pkg/utils"
"github.com/samber/lo"
)
type VirtualMachineCreateOrUpdateInput struct {
ResourceGroupName string
VMName string
VM armcompute.VirtualMachine
Options *armcompute.VirtualMachinesClientBeginCreateOrUpdateOptions
}
type VirtualMachineUpdateInput struct {
ResourceGroupName string
VMName string
Updates armcompute.VirtualMachineUpdate
Options *armcompute.VirtualMachinesClientBeginUpdateOptions
}
type VirtualMachineDeleteInput struct {
ResourceGroupName string
VMName string
Options *armcompute.VirtualMachinesClientBeginDeleteOptions
}
type VirtualMachineGetInput struct {
ResourceGroupName string
VMName string
Options *armcompute.VirtualMachinesClientGetOptions
}
type VirtualMachinesBehavior struct {
VirtualMachineCreateOrUpdateBehavior MockedLRO[VirtualMachineCreateOrUpdateInput, armcompute.VirtualMachinesClientCreateOrUpdateResponse]
VirtualMachineUpdateBehavior MockedLRO[VirtualMachineUpdateInput, armcompute.VirtualMachinesClientUpdateResponse]
VirtualMachineDeleteBehavior MockedLRO[VirtualMachineDeleteInput, armcompute.VirtualMachinesClientDeleteResponse]
VirtualMachineGetBehavior MockedFunction[VirtualMachineGetInput, armcompute.VirtualMachinesClientGetResponse]
Instances sync.Map
}
// assert that the fake implements the interface
var _ instance.VirtualMachinesAPI = &VirtualMachinesAPI{}
type VirtualMachinesAPI struct {
// TODO: document the implications of embedding vs. not embedding the interface here
// instance.VirtualMachinesAPI // - this is the interface we are mocking.
VirtualMachinesBehavior
}
// Reset must be called between tests otherwise tests will pollute each other.
func (c *VirtualMachinesAPI) Reset() {
c.VirtualMachineCreateOrUpdateBehavior.Reset()
c.VirtualMachineDeleteBehavior.Reset()
c.VirtualMachineGetBehavior.Reset()
c.VirtualMachineUpdateBehavior.Reset()
c.Instances.Range(func(k, v any) bool {
c.Instances.Delete(k)
return true
})
}
func (c *VirtualMachinesAPI) BeginCreateOrUpdate(_ context.Context, resourceGroupName string, vmName string, parameters armcompute.VirtualMachine, options *armcompute.VirtualMachinesClientBeginCreateOrUpdateOptions) (*runtime.Poller[armcompute.VirtualMachinesClientCreateOrUpdateResponse], error) {
// gather input parameters (may get rid of this with multiple mocked function signatures to reflect common patterns)
input := &VirtualMachineCreateOrUpdateInput{
ResourceGroupName: resourceGroupName,
VMName: vmName,
VM: parameters,
Options: options,
}
// BeginCreateOrUpdate should fail, if the vm exists in the cache, and we are attempting to change properties for zone
return c.VirtualMachineCreateOrUpdateBehavior.Invoke(input, func(input *VirtualMachineCreateOrUpdateInput) (*armcompute.VirtualMachinesClientCreateOrUpdateResponse, error) {
//if input.ResourceGroupName == "" {
// return nil, errors.New("ResourceGroupName is required")
//}
// TODO: may have to clone ...
// TODO: subscription ID?
vm := input.VM
id := utils.MkVMID(input.ResourceGroupName, input.VMName)
vm.ID = to.StringPtr(id)
// Check store for existing vm by name
existingVM, ok := c.Instances.Load(id)
if ok {
incomingZone := vm.Zones[0] // Note: this assumes at least 1 zone and only one zone is put on our vm
existingZone := existingVM.(armcompute.VirtualMachine).Zones[0]
if incomingZone != existingZone {
// Currently only returning for zones, but osProfile.customData will also return this error
errCode := "PropertyChangeNotAllowed"
msg := `Creating virtual machine "aks-default-4984v" failed: PUT https://management.azure.com/subscriptions/****/resourceGroups/****/providers/Microsoft.Compute/virtualMachines/aks-default-4984v
--------------------------------------------------------------------------------
RESPONSE 409: 409 Conflict
ERROR CODE: PropertyChangeNotAllowed
--------------------------------------------------------------------------------
{
"error": {
"code": "PropertyChangeNotAllowed",
"message": "Changing property 'zones' is not allowed.",
"target": "zones"
}
}
--------------------------------------------------------------------------------`
return nil, &azcore.ResponseError{
ErrorCode: errCode,
RawResponse: &http.Response{
Body: createSDKErrorBody(errCode, msg),
},
}
}
// Use existing vm rather than restoring
return &armcompute.VirtualMachinesClientCreateOrUpdateResponse{VirtualMachine: existingVM.(armcompute.VirtualMachine)}, nil
}
vm.Name = to.StringPtr(input.VMName)
if vm.Properties == nil {
vm.Properties = &armcompute.VirtualMachineProperties{}
}
if vm.Properties.TimeCreated == nil {
vm.Properties.TimeCreated = lo.ToPtr(time.Now()) // TODO: use simulated time?
}
c.Instances.Store(id, vm)
return &armcompute.VirtualMachinesClientCreateOrUpdateResponse{VirtualMachine: vm}, nil
})
}
func (c *VirtualMachinesAPI) BeginUpdate(_ context.Context, resourceGroupName string, vmName string, updates armcompute.VirtualMachineUpdate, options *armcompute.VirtualMachinesClientBeginUpdateOptions) (*runtime.Poller[armcompute.VirtualMachinesClientUpdateResponse], error) {
input := &VirtualMachineUpdateInput{
ResourceGroupName: resourceGroupName,
VMName: vmName,
Updates: updates,
Options: options,
}
return c.VirtualMachineUpdateBehavior.Invoke(input, func(input *VirtualMachineUpdateInput) (*armcompute.VirtualMachinesClientUpdateResponse, error) {
id := utils.MkVMID(input.ResourceGroupName, input.VMName)
instance, ok := c.Instances.Load(id)
if !ok {
return nil, &azcore.ResponseError{ErrorCode: errors.ResourceNotFound}
}
vm := instance.(armcompute.VirtualMachine)
// If other fields need to be updated in the future, you can similarly
// update the VM object by merging with updates.<New Field>.
vm.Tags = updates.Tags
if updates.Identity != nil {
if vm.Identity == nil {
vm.Identity = &armcompute.VirtualMachineIdentity{}
}
if updates.Identity.Type != nil {
vm.Identity.Type = updates.Identity.Type
}
if len(updates.Identity.UserAssignedIdentities) > 0 {
if vm.Identity.UserAssignedIdentities == nil {
vm.Identity.UserAssignedIdentities = make(map[string]*armcompute.UserAssignedIdentitiesValue)
}
for id, val := range updates.Identity.UserAssignedIdentities {
vm.Identity.UserAssignedIdentities[id] = val
}
}
}
// Update the stored shape
c.Instances.Store(id, vm)
return &armcompute.VirtualMachinesClientUpdateResponse{VirtualMachine: vm}, nil
})
}
func (c *VirtualMachinesAPI) Get(_ context.Context, resourceGroupName string, vmName string, options *armcompute.VirtualMachinesClientGetOptions) (armcompute.VirtualMachinesClientGetResponse, error) {
input := &VirtualMachineGetInput{
ResourceGroupName: resourceGroupName,
VMName: vmName,
Options: options,
}
return c.VirtualMachineGetBehavior.Invoke(input, func(input *VirtualMachineGetInput) (armcompute.VirtualMachinesClientGetResponse, error) {
instance, ok := c.Instances.Load(utils.MkVMID(input.ResourceGroupName, input.VMName))
if !ok {
return armcompute.VirtualMachinesClientGetResponse{}, &azcore.ResponseError{ErrorCode: errors.ResourceNotFound}
}
return armcompute.VirtualMachinesClientGetResponse{
VirtualMachine: instance.(armcompute.VirtualMachine),
}, nil
})
}
func (c *VirtualMachinesAPI) BeginDelete(_ context.Context, resourceGroupName string, vmName string, options *armcompute.VirtualMachinesClientBeginDeleteOptions) (*runtime.Poller[armcompute.VirtualMachinesClientDeleteResponse], error) {
input := &VirtualMachineDeleteInput{
ResourceGroupName: resourceGroupName,
VMName: vmName,
Options: options,
}
return c.VirtualMachineDeleteBehavior.Invoke(input, func(input *VirtualMachineDeleteInput) (*armcompute.VirtualMachinesClientDeleteResponse, error) {
c.Instances.Delete(utils.MkVMID(input.ResourceGroupName, input.VMName))
return &armcompute.VirtualMachinesClientDeleteResponse{}, nil
})
}
func createSDKErrorBody(code, message string) io.ReadCloser {
return io.NopCloser(bytes.NewReader([]byte(fmt.Sprintf(`{"error":{"code": "%s", "message": "%s"}}`, code, message))))
}