Change:
<!-- Please only use this template for submitting enhancement requests --What would you like to be : Instead of using the VM CreateOrUpdate () function of the Azure Go SDK, use the VM Update function () in the azureclient () Why is this needed: Currently, the Azure Cloud Provider is using the VM CreateOrUpdate call when e.g. detach/attaching disks to a VM. In some scenarios, this will re-create a VM that was deleted by an external application. This can lead to orphaned VMs or blocked resources. The Update function will only update an existing VM and will fail if the VM does not exist.
/sig azure
/assign
/sig cloud-provider
Could you describe more about the scenarios? Have you noticed such issues? I wonder adding etags to the API call could help to prevent this?
If you have an application that maintains your K8s clusters, e.g. takes care of OS update/patching. If the application wants to patch the OS of a worker node, it would drain the node first. If draining times out, it will go ahead and delete the VM anyway. But K8s might still be detaching disks. These detach operations, because they use CreateOrUpdate, will recreate the VM. Is there a reason why you would want to create a VM i.e. why CreateOrUpdate is required and Update would not work? AFAIK, Azure Compute RP does not support etags.
that sounds reasonable, thanks change to use function would be safer since it's only detach/attach disk operation. I will do that.
thanks for the information. Reasonable enough. /assign
/milestone v1.16
BTW, the reason why we only use func in the beginning is that Azure Compute go sdk did not provide a func at that time...
Great. Thanks!

Discussion:
// VirtualMachinesClient defines needed functions for azure compute.VirtualMachinesClient type VirtualMachinesClient interface { CreateOrUpdate(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachine, source string) (resp *http.Response, err error) <ins> Update(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachineUpdate, source string) (resp *http.Response, err error) </ins> Get(ctx context.Context, resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (result compute.VirtualMachine, err error) List(ctx context.Context, resourceGroupName string) (result []compute.VirtualMachine, err error) }

return future.Response(), err } <ins> func (az *azVirtualMachinesClient) Update(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachineUpdate, source string) (resp *http.Response, err error) { // /* Write rate limiting */ if !az.rateLimiterWriter.TryAccept() { err = createRateLimitErr(true, "VMUpdate") return } klog.V(10).Infof("azVirtualMachinesClient.Update(%q, %q): start", resourceGroupName, VMName) defer func() { klog.V(10).Infof("azVirtualMachinesClient.Update(%q, %q): end", resourceGroupName, VMName) }() mc := newMetricContext("vm", "update", resourceGroupName, az.client.SubscriptionID, source) future, err := az.client.Update(ctx, resourceGroupName, VMName, parameters) if err != nil { return future.Response(), err } err = future.WaitForCompletionRef(ctx, az.client.Client) mc.Observe(err) return future.Response(), err } </ins> func (az *azVirtualMachinesClient) Get(ctx context.Context, resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (result compute.VirtualMachine, err error) { if !az.rateLimiterReader.TryAccept() { err = createRateLimitErr(false, "VMGet")

}) } <del> newVM := compute.VirtualMachine{ Location: vm.Location, </del> <ins> newVM := compute.VirtualMachineUpdate{ </ins> VirtualMachineProperties: &compute.VirtualMachineProperties{ HardwareProfile: vm.HardwareProfile, StorageProfile: &compute.StorageProfile{

// Invalidate the cache right after updating defer as.cloud.vmCache.Delete(vmName) <del> _, err = as.VirtualMachinesClient.CreateOrUpdate(ctx, nodeResourceGroup, vmName, newVM, "attach_disk") </del> <ins> _, err = as.VirtualMachinesClient.Update(ctx, nodeResourceGroup, vmName, newVM, "attach_disk") </ins> if err != nil { klog.Errorf("azureDisk - attach disk(%s, %s) failed, err: %v", diskName, diskURI, err) detail := err.Error()

klog.Errorf("detach azure disk: disk %s not found, diskURI: %s", diskName, diskURI) } <del> newVM := compute.VirtualMachine{ Location: vm.Location, </del> <ins> newVM := compute.VirtualMachineUpdate{ </ins> VirtualMachineProperties: &compute.VirtualMachineProperties{ HardwareProfile: vm.HardwareProfile, StorageProfile: &compute.StorageProfile{

// Invalidate the cache right after updating defer as.cloud.vmCache.Delete(vmName) <del> return as.VirtualMachinesClient.CreateOrUpdate(ctx, nodeResourceGroup, vmName, newVM, "detach_disk") </del> <ins> return as.VirtualMachinesClient.Update(ctx, nodeResourceGroup, vmName, newVM, "detach_disk") </ins> } // GetDataDisks gets a list of data disks attached to the node.

return nil, nil } <ins> func (fVMC *fakeAzureVirtualMachinesClient) Update(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachineUpdate, source string) (resp *http.Response, err error) { fVMC.mutex.Lock() defer fVMC.mutex.Unlock() if _, ok := fVMC.FakeStore[resourceGroupName]; !ok { fVMC.FakeStore[resourceGroupName] = make(map[string]compute.VirtualMachine) } return nil, nil } </ins> func (fVMC *fakeAzureVirtualMachinesClient) Get(ctx context.Context, resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (result compute.VirtualMachine, err error) { fVMC.mutex.Lock() defer fVMC.mutex.Unlock()