Skip to content

fix(instance): handle reboot on update, not only stop/start #660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions scaleway/helpers_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,55 @@ func reachState(ctx context.Context, instanceAPI *instance.API, zone scw.Zone, s
return nil
}

// prepareVolumeForAttach will make sure a volume is either not attached to a server or already attached to the given server. If volume is attached to another server, it will be stopped
// to allow volume detachment.
// Note: the Volume.ServerSummary may not be accurate
func prepareVolumeForAttach(ctx context.Context, instanceAPI *instance.API, zone scw.Zone, volumeID string, serverID string) (*instance.Volume, error) {
res, err := instanceAPI.GetVolume(&instance.GetVolumeRequest{
Zone: zone,
VolumeID: volumeID,
}, scw.WithContext(ctx))
if err != nil {
return nil, err
}

if res.Volume.Server == nil || res.Volume.Server.ID == serverID {
return res.Volume, nil
}

defer lockLocalizedID(newZonedIDString(zone, res.Volume.Server.ID))()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try to avoid using lockLocalizedID as we try to deprecate it cf #662 @jerome-quere


if res.Volume.VolumeType == instance.VolumeVolumeTypeLSSD {
serverResp, err := instanceAPI.GetServer(&instance.GetServerRequest{
ServerID: res.Volume.Server.ID,
Zone: zone,
})

// If 404 this mean server is deleted and volume is already detached
if is404Error(err) {
return res.Volume, nil
}
if err != nil {
return nil, err
}
if serverResp.Server.State != instance.ServerStateStopped {
// Here we have a local volume on a server that might not be managed by Terraform, better throw an error
return nil, fmt.Errorf("volume %s in zone %s is already mounted on the server %s, please stop it", volumeID, zone.String(), res.Volume.Server.ID)
}
}
_, err = instanceAPI.DetachVolume(&instance.DetachVolumeRequest{
Zone: zone,
VolumeID: res.Volume.ID,
}, scw.WithContext(ctx))

// TODO find a better way to test this error
if err != nil && err.Error() != "scaleway-sdk-go: volume should be attached to a server" {
return nil, err
}

return res.Volume, nil
}

// detachVolume will make sure a volume is not attached to any server. If volume is attached to a server, it will be stopped
// to allow volume detachment.
func detachVolume(ctx context.Context, instanceAPI *instance.API, zone scw.Zone, volumeID string) error {
Expand Down
27 changes: 21 additions & 6 deletions scaleway/resource_instance_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ func resourceScalewayInstanceServerUpdate(ctx context.Context, d *schema.Resourc

// This variable will be set to true if any state change requires a server reboot.
var forceReboot bool
var forceStopStart bool

////
// Construct UpdateServerRequest
Expand Down Expand Up @@ -532,27 +533,30 @@ func resourceScalewayInstanceServerUpdate(ctx context.Context, d *schema.Resourc
volumes["0"] = &instance.VolumeTemplate{ID: expandZonedID(d.Get("root_volume.0.volume_id")).ID, Name: newRandomName("vol")} // name is ignored by the API, any name will work here

for i, volumeID := range raw.([]interface{}) {
// We make sure volume is detached so we can attach it to the server.
err = detachVolume(nil, instanceAPI, zone, expandZonedID(volumeID).ID)
// We make sure volume is either detached or already attached to the server so we can attach it to the server.
volume, err := prepareVolumeForAttach(nil, instanceAPI, zone, expandZonedID(volumeID).ID, ID)
if err != nil {
return diag.FromErr(err)
}
if volume.VolumeType == instance.VolumeVolumeTypeLSSD {
// we need to stop the server if we attach a local volume
forceStopStart = true
}
volumes[strconv.Itoa(i+1)] = &instance.VolumeTemplate{
ID: expandZonedID(volumeID).ID,
Name: newRandomName("vol"), // name is ignored by the API, any name will work here
}
}

updateRequest.Volumes = &volumes
forceReboot = true
}

if d.HasChange("placement_group_id") {
placementGroupID := expandZonedID(d.Get("placement_group_id")).ID
if placementGroupID == "" {
updateRequest.PlacementGroup = &instance.NullableStringValue{Null: true}
} else {
forceReboot = true
forceStopStart = true
updateRequest.PlacementGroup = &instance.NullableStringValue{Value: placementGroupID}
}
}
Expand Down Expand Up @@ -642,17 +646,28 @@ func resourceScalewayInstanceServerUpdate(ctx context.Context, d *schema.Resourc

defer lockLocalizedID(d.Id())()

if forceReboot {
if forceStopStart {
err = reachState(ctx, instanceAPI, zone, ID, InstanceServerStateStopped)
if err != nil {
return diag.FromErr(err)
}
}
_, err = instanceAPI.UpdateServer(updateRequest)
updateServerResp, err := instanceAPI.UpdateServer(updateRequest)
if err != nil {
return diag.FromErr(err)
}

if forceReboot && updateServerResp.Server.State == instance.ServerStateRunning {
_, err = instanceAPI.ServerAction(&instance.ServerActionRequest{
Zone: zone,
ServerID: ID,
Action: instance.ServerActionReboot,
})
if err != nil {
return diag.FromErr(err)
}
}

targetState, err := serverStateExpand(d.Get("state").(string))
if err != nil {
return diag.FromErr(err)
Expand Down