-
Notifications
You must be signed in to change notification settings - Fork 218
/
machines_launchinput.go
219 lines (197 loc) · 8.42 KB
/
machines_launchinput.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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package deploy
import (
"fmt"
"strconv"
"github.com/samber/lo"
fly "github.com/superfly/fly-go"
"github.com/superfly/flyctl/internal/buildinfo"
"github.com/superfly/flyctl/internal/machine"
"github.com/superfly/flyctl/terminal"
)
func (md *machineDeployment) launchInputForRestart(origMachineRaw *fly.Machine) *fly.LaunchMachineInput {
mConfig := machine.CloneConfig(origMachineRaw.Config)
md.setMachineReleaseData(mConfig)
return &fly.LaunchMachineInput{
ID: origMachineRaw.ID,
Config: mConfig,
Region: origMachineRaw.Region,
SkipLaunch: skipLaunch(origMachineRaw, mConfig),
}
}
func (md *machineDeployment) launchInputForLaunch(processGroup string, guest *fly.MachineGuest, standbyFor []string) (*fly.LaunchMachineInput, error) {
mConfig, err := md.appConfig.ToMachineConfig(processGroup, nil)
if err != nil {
return nil, err
}
// Obey the Guest if already set from [[compute]] section
if mConfig.Guest == nil {
mConfig.Guest = guest
}
mConfig.Image = md.img
md.setMachineReleaseData(mConfig)
// Get the final process group and prevent empty string
processGroup = mConfig.ProcessGroup()
region := md.appConfig.PrimaryRegion
if len(mConfig.Mounts) > 0 {
mount0 := &mConfig.Mounts[0]
vol := md.popVolumeFor(mount0.Name, region)
if vol == nil {
return nil, fmt.Errorf("New machine in group '%s' needs an unattached volume named '%s' in region '%s'", processGroup, mount0.Name, region)
}
mount0.Volume = vol.ID
}
if len(standbyFor) > 0 {
mConfig.Standbys = standbyFor
}
if hdid := md.appConfig.HostDedicationID; hdid != "" {
mConfig.Guest.HostDedicationID = hdid
}
return &fly.LaunchMachineInput{
Region: region,
Config: mConfig,
SkipLaunch: len(standbyFor) > 0,
}, nil
}
func (md *machineDeployment) launchInputForUpdate(origMachineRaw *fly.Machine) (*fly.LaunchMachineInput, error) {
mID := origMachineRaw.ID
machineShouldBeReplaced := dedicatedHostIdMismatch(origMachineRaw, md.appConfig)
processGroup := origMachineRaw.Config.ProcessGroup()
mConfig, err := md.appConfig.ToMachineConfig(processGroup, origMachineRaw.Config)
if err != nil {
return nil, err
}
mConfig.Image = md.img
md.setMachineReleaseData(mConfig)
// Get the final process group and prevent empty string
processGroup = mConfig.ProcessGroup()
// Mounts needs special treatment:
// * Volumes attached to existings machines can't be swapped by other volumes
// * The only allowed in-place operation is to update its destination mount path
// * The other option is to force a machine replacement to remove or attach a different volume
mMounts := mConfig.Mounts
oMounts := origMachineRaw.Config.Mounts
if len(oMounts) != 0 {
var latestExtendThresholdPercent, latestAddSizeGb, latestSizeGbLimit int
if len(mMounts) > 0 {
latestExtendThresholdPercent = mMounts[0].ExtendThresholdPercent
latestAddSizeGb = mMounts[0].AddSizeGb
latestSizeGbLimit = mMounts[0].SizeGbLimit
}
switch {
case len(mMounts) == 0:
// The mounts section was removed from fly.toml
machineShouldBeReplaced = true
terminal.Warnf("Machine %s has a volume attached but fly.toml doesn't have a [mounts] section\n", mID)
case oMounts[0].Name == "":
// It's rare but can happen, we don't know the mounted volume name
// so can't be sure it matches the mounts defined in fly.toml, in this
// case assume we want to retain existing mount
mMounts[0] = oMounts[0]
case mMounts[0].Name != oMounts[0].Name:
// The expected volume name for the machine and fly.toml are out sync
// As we can't change the volume for a running machine, the only
// way is to destroy the current machine and launch a new one with the new volume attached
mount0 := &mMounts[0]
terminal.Warnf("Machine %s has volume '%s' attached but fly.toml have a different name: '%s'\n", mID, oMounts[0].Name, mount0.Name)
vol := md.popVolumeFor(mount0.Name, origMachineRaw.Region)
if vol == nil {
return nil, fmt.Errorf("machine in group '%s' needs an unattached volume named '%s' in region '%s'", processGroup, mount0.Name, origMachineRaw.Region)
}
mount0.Volume = vol.ID
machineShouldBeReplaced = true
case mMounts[0].Path != oMounts[0].Path:
// The volume is the same but its mount path changed. Not a big deal.
terminal.Warnf(
"Updating the mount path for volume %s on machine %s from %s to %s due to fly.toml [mounts] destination value\n",
oMounts[0].Volume, mID, oMounts[0].Path, mMounts[0].Path,
)
// Copy the volume id over because path is already correct
mMounts[0].Volume = oMounts[0].Volume
default:
// In any other case retain the existing machine mounts
mMounts[0] = oMounts[0]
}
if len(mMounts) > 0 {
mMounts[0].ExtendThresholdPercent = latestExtendThresholdPercent
mMounts[0].AddSizeGb = latestAddSizeGb
mMounts[0].SizeGbLimit = latestSizeGbLimit
}
} else if len(mMounts) != 0 {
// Replace the machine because [mounts] section was added to fly.toml
// and it is not possible to attach a volume to an existing machine.
// The volume could be in a different zone than the machine.
mount0 := &mMounts[0]
vol := md.popVolumeFor(mount0.Name, origMachineRaw.Region)
if vol == nil {
return nil, fmt.Errorf("machine in group '%s' needs an unattached volume named '%s' in region '%s'", processGroup, mMounts[0].Name, origMachineRaw.Region)
}
mount0.Volume = vol.ID
machineShouldBeReplaced = true
}
// If this is a standby machine that now has a service, then clear
// the standbys list.
if len(mConfig.Services) > 0 && len(mConfig.Standbys) > 0 {
mConfig.Standbys = nil
}
if hdid := md.appConfig.HostDedicationID; hdid != "" && hdid != origMachineRaw.Config.Guest.HostDedicationID {
if len(oMounts) > 0 && len(mMounts) > 0 {
// Attempting to rellocate a machine with a volume attached to a different host
return nil, fmt.Errorf("can't rellocate machine '%s' to dedication id '%s' because it has an attached volume."+
" Retry after forking the volume with `fly volume fork --host-dedication-id %s %s`", mID, hdid, hdid, mMounts[0].Volume)
}
machineShouldBeReplaced = true
// Set HostDedicationID here for the apps that doesn't have a [[compute]] section in fly.toml
// but sets it as a top level directive.
// This also works when top level HDID is different than [compute.host_dedication_id]
// because a flatten config also overrides the top level directive
mConfig.Guest.HostDedicationID = hdid
}
return &fly.LaunchMachineInput{
ID: mID,
Region: origMachineRaw.Region,
Config: mConfig,
SkipLaunch: skipLaunch(origMachineRaw, mConfig),
RequiresReplacement: machineShouldBeReplaced,
}, nil
}
func (md *machineDeployment) setMachineReleaseData(mConfig *fly.MachineConfig) {
mConfig.Metadata = lo.Assign(mConfig.Metadata, map[string]string{
fly.MachineConfigMetadataKeyFlyReleaseId: md.releaseId,
fly.MachineConfigMetadataKeyFlyReleaseVersion: strconv.Itoa(md.releaseVersion),
fly.MachineConfigMetadataKeyFlyctlVersion: buildinfo.Version().String(),
})
// These defaults should come from appConfig.ToMachineConfig() and set on launch;
// leave them here for the moment becase very old machines may not have them
// and we want to set in case of simple app restarts
if _, ok := mConfig.Metadata[fly.MachineConfigMetadataKeyFlyPlatformVersion]; !ok {
mConfig.Metadata[fly.MachineConfigMetadataKeyFlyPlatformVersion] = fly.MachineFlyPlatformVersion2
}
if _, ok := mConfig.Metadata[fly.MachineConfigMetadataKeyFlyProcessGroup]; !ok {
mConfig.Metadata[fly.MachineConfigMetadataKeyFlyProcessGroup] = fly.MachineProcessGroupApp
}
// FIXME: Move this as extra metadata read from a machineDeployment argument
// It is not clear we have to cleanup the postgres metadata
if md.app.IsPostgresApp() {
mConfig.Metadata[fly.MachineConfigMetadataKeyFlyManagedPostgres] = "true"
} else {
delete(mConfig.Metadata, fly.MachineConfigMetadataKeyFlyManagedPostgres)
}
}
// Skip launching currently-stopped machines if:
// * any services use autoscaling (autostop or autostart).
// * it is a standby machine
func skipLaunch(origMachineRaw *fly.Machine, mConfig *fly.MachineConfig) bool {
switch {
case origMachineRaw.State == fly.MachineStateStarted:
return false
case len(mConfig.Standbys) > 0:
return true
case origMachineRaw.State == fly.MachineStateStopped:
for _, s := range mConfig.Services {
if (s.Autostop != nil && *s.Autostop) || (s.Autostart != nil && *s.Autostart) {
return true
}
}
}
return false
}