/
virtual_machine_scale_set_update.go
184 lines (150 loc) · 8.27 KB
/
virtual_machine_scale_set_update.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package compute
import (
"context"
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/compute/client"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)
type virtualMachineScaleSetUpdateMetaData struct {
// is "automaticOSUpgrade" enable in the upgradeProfile block
AutomaticOSUpgradeIsEnabled bool
// can we roll instances if we need too? this is a feature toggle
CanRollInstancesWhenRequired bool
// do we need to roll the instances in this scale set?
UpdateInstances bool
Client *client.Client
Existing compute.VirtualMachineScaleSet
ID *VirtualMachineScaleSetResourceID
OSType compute.OperatingSystemTypes
}
func (metadata virtualMachineScaleSetUpdateMetaData) performUpdate(ctx context.Context, update compute.VirtualMachineScaleSetUpdate) error {
if metadata.AutomaticOSUpgradeIsEnabled {
// Virtual Machine Scale Sets with Automatic OS Upgrade enabled must have all VM instances upgraded to same
// Platform Image. Upgrade all VM instances to latest Virtual Machine Scale Set model while property
// 'upgradePolicy.automaticOSUpgradePolicy.enableAutomaticOSUpgrade' is false and then update property
// 'upgradePolicy.automaticOSUpgradePolicy.enableAutomaticOSUpgrade' to true
update.VirtualMachineScaleSetUpdateProperties.UpgradePolicy.AutomaticOSUpgradePolicy.EnableAutomaticOSUpgrade = utils.Bool(false)
}
if err := metadata.updateVmss(ctx, update); err != nil {
return err
}
// if we update the SKU, we also need to subsequently roll the instances using the `UpdateInstances` API
if metadata.UpdateInstances {
userWantsToRollInstances := metadata.CanRollInstancesWhenRequired
upgradeMode := metadata.Existing.VirtualMachineScaleSetProperties.UpgradePolicy.Mode
if userWantsToRollInstances {
if upgradeMode == compute.Automatic {
if err := metadata.upgradeInstancesForAutomaticUpgradePolicy(ctx); err != nil {
return err
}
}
}
if upgradeMode == compute.Manual {
if err := metadata.upgradeInstancesForManualUpgradePolicy(ctx); err != nil {
return err
}
}
}
if metadata.AutomaticOSUpgradeIsEnabled {
// Virtual Machine Scale Sets with Automatic OS Upgrade enabled must have all VM instances upgraded to same
// Platform Image. Upgrade all VM instances to latest Virtual Machine Scale Set model while property
// 'upgradePolicy.automaticOSUpgradePolicy.enableAutomaticOSUpgrade' is false and then update property
// 'upgradePolicy.automaticOSUpgradePolicy.enableAutomaticOSUpgrade' to true
// finally set this to true
update.VirtualMachineScaleSetUpdateProperties.UpgradePolicy.AutomaticOSUpgradePolicy.EnableAutomaticOSUpgrade = utils.Bool(true)
// then update the VM
if err := metadata.updateVmss(ctx, update); err != nil {
return err
}
}
return nil
}
func (metadata virtualMachineScaleSetUpdateMetaData) updateVmss(ctx context.Context, update compute.VirtualMachineScaleSetUpdate) error {
client := metadata.Client.VMScaleSetClient
id := metadata.ID
log.Printf("[DEBUG] Updating %s Virtual Machine Scale Set %q (Resource Group %q)..", metadata.OSType, id.Name, id.ResourceGroup)
future, err := client.Update(ctx, id.ResourceGroup, id.Name, update)
if err != nil {
return fmt.Errorf("Error updating L%sinux Virtual Machine Scale Set %q (Resource Group %q): %+v", metadata.OSType, id.Name, id.ResourceGroup, err)
}
log.Printf("[DEBUG] Waiting for update of %s Virtual Machine Scale Set %q (Resource Group %q)..", metadata.OSType, id.Name, id.ResourceGroup)
if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error waiting for update of %s Virtual Machine Scale Set %q (Resource Group %q): %+v", metadata.OSType, id.Name, id.ResourceGroup, err)
}
log.Printf("[DEBUG] Updated %s Virtual Machine Scale Set %q (Resource Group %q).", metadata.OSType, id.Name, id.ResourceGroup)
return nil
}
func (metadata virtualMachineScaleSetUpdateMetaData) upgradeInstancesForAutomaticUpgradePolicy(ctx context.Context) error {
client := metadata.Client.VMScaleSetClient
rollingUpgradesClient := metadata.Client.VMScaleSetRollingUpgradesClient
id := metadata.ID
log.Printf("[DEBUG] Updating instances for %s Virtual Machine Scale Set %q (Resource Group %q)..", metadata.OSType, id.Name, id.ResourceGroup)
future, err := rollingUpgradesClient.StartOSUpgrade(ctx, id.ResourceGroup, id.Name)
if err != nil {
return fmt.Errorf("Error updating instances for %s Virtual Machine Scale Set %q (Resource Group %q): %+v", metadata.OSType, id.Name, id.ResourceGroup, err)
}
log.Printf("[DEBUG] Waiting for update of instances for %s Virtual Machine Scale Set %q (Resource Group %q)..", metadata.OSType, id.Name, id.ResourceGroup)
if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error waiting for update of instances for %s Virtual Machine Scale Set %q (Resource Group %q): %+v", metadata.OSType, id.Name, id.ResourceGroup, err)
}
log.Printf("[DEBUG] Updated instances for %s Virtual Machine Scale Set %q (Resource Group %q).", metadata.OSType, id.Name, id.ResourceGroup)
return nil
}
func (metadata virtualMachineScaleSetUpdateMetaData) upgradeInstancesForManualUpgradePolicy(ctx context.Context) error {
client := metadata.Client.VMScaleSetClient
id := metadata.ID
log.Printf("[DEBUG] Rolling the VM Instances for %s Virtual Machine Scale Set %q (Resource Group %q)..", metadata.OSType, id.Name, id.ResourceGroup)
instancesClient := metadata.Client.VMScaleSetVMsClient
instances, err := instancesClient.ListComplete(ctx, id.ResourceGroup, id.Name, "", "", "")
if err != nil {
return fmt.Errorf("Error listing VM Instances for %s Virtual Machine Scale Set %q (Resource Group %q): %+v", metadata.OSType, id.Name, id.ResourceGroup, err)
}
log.Printf("[DEBUG] Determining instances to roll..")
instanceIdsToRoll := make([]string, 0)
for instances.NotDone() {
instance := instances.Value()
props := instance.VirtualMachineScaleSetVMProperties
if props != nil && instance.InstanceID != nil {
latestModel := props.LatestModelApplied
if latestModel != nil || !*latestModel {
instanceIdsToRoll = append(instanceIdsToRoll, *instance.InstanceID)
}
}
if err := instances.NextWithContext(ctx); err != nil {
return fmt.Errorf("Error enumerating instances: %s", err)
}
}
// TODO: there's a performance enhancement to do batches here, but this is fine for a first pass
for _, instanceId := range instanceIdsToRoll {
instanceIds := []string{instanceId}
log.Printf("[DEBUG] Updating Instance %q to the Latest Configuration..", instanceId)
ids := compute.VirtualMachineScaleSetVMInstanceRequiredIDs{
InstanceIds: &instanceIds,
}
future, err := client.UpdateInstances(ctx, id.ResourceGroup, id.Name, ids)
if err != nil {
return fmt.Errorf("Error updating Instance %q (%s VM Scale Set %q / Resource Group %q) to the Latest Configuration: %+v", instanceId, metadata.OSType, id.Name, id.ResourceGroup, err)
}
if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error waiting for update of Instance %q (%s VM Scale Set %q / Resource Group %q) to the Latest Configuration: %+v", instanceId, metadata.OSType, id.Name, id.ResourceGroup, err)
}
log.Printf("[DEBUG] Updated Instance %q to the Latest Configuration.", instanceId)
// TODO: does this want to be a separate, user-configurable toggle?
log.Printf("[DEBUG] Reimaging Instance %q..", instanceId)
reimageInput := &compute.VirtualMachineScaleSetReimageParameters{
InstanceIds: &instanceIds,
}
reimageFuture, err := client.Reimage(ctx, id.ResourceGroup, id.Name, reimageInput)
if err != nil {
return fmt.Errorf("Error reimaging Instance %q (%s VM Scale Set %q / Resource Group %q): %+v", instanceId, metadata.OSType, id.Name, id.ResourceGroup, err)
}
if err = reimageFuture.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error waiting for reimage of Instance %q (%s VM Scale Set %q / Resource Group %q): %+v", instanceId, metadata.OSType, id.Name, id.ResourceGroup, err)
}
log.Printf("[DEBUG] Reimaged Instance %q..", instanceId)
}
log.Printf("[DEBUG] Rolled the VM Instances for %s Virtual Machine Scale Set %q (Resource Group %q).", metadata.OSType, id.Name, id.ResourceGroup)
return nil
}