Skip to content

Commit d2e9182

Browse files
authored
fix(instance_server): attach external block volume on update (scaleway#2574)
* fix(instance_server): attach external block volume on update * update cassettes * remove unused code
1 parent 1b42618 commit d2e9182

File tree

5 files changed

+8009
-4745
lines changed

5 files changed

+8009
-4745
lines changed

internal/services/instance/helpers_instance_block.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package instance
22

33
import (
4+
"errors"
5+
46
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
57
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
68
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
@@ -14,6 +16,99 @@ type BlockAndInstanceAPI struct {
1416
blockAPI *block.API
1517
}
1618

19+
type GetUnknownVolumeRequest struct {
20+
VolumeID string
21+
Zone scw.Zone
22+
}
23+
24+
type UnknownVolume struct {
25+
Zone scw.Zone
26+
ID string
27+
Name string
28+
Size scw.Size
29+
ServerID *string
30+
31+
// IsBlockVolume is true if volume is managed by block API
32+
IsBlockVolume bool
33+
34+
InstanceVolumeType instance.VolumeVolumeType
35+
}
36+
37+
// VolumeTemplateUpdate return a VolumeServerTemplate for an UpdateServer request
38+
func (volume *UnknownVolume) VolumeTemplateUpdate() *instance.VolumeServerTemplate {
39+
template := &instance.VolumeServerTemplate{
40+
ID: scw.StringPtr(volume.ID),
41+
Name: &volume.Name, // name is ignored by the API, any name will work here
42+
}
43+
if volume.IsBlockVolume {
44+
template.Name = nil
45+
template.VolumeType = volume.InstanceVolumeType
46+
}
47+
48+
return template
49+
}
50+
51+
// IsLocal returns true if the volume is a local volume
52+
func (volume *UnknownVolume) IsLocal() bool {
53+
return !volume.IsBlockVolume && volume.InstanceVolumeType == instance.VolumeVolumeTypeLSSD
54+
}
55+
56+
// IsAttached returns true if the volume is attached to a server
57+
func (volume *UnknownVolume) IsAttached() bool {
58+
return volume.ServerID != nil && *volume.ServerID != ""
59+
}
60+
61+
func (api *BlockAndInstanceAPI) GetUnknownVolume(req *GetUnknownVolumeRequest, opts ...scw.RequestOption) (*UnknownVolume, error) {
62+
getVolumeResponse, err := api.API.GetVolume(&instance.GetVolumeRequest{
63+
Zone: req.Zone,
64+
VolumeID: req.VolumeID,
65+
}, opts...)
66+
notFoundErr := &scw.ResourceNotFoundError{}
67+
if err != nil && !errors.As(err, &notFoundErr) {
68+
return nil, err
69+
}
70+
71+
if getVolumeResponse != nil {
72+
vol := &UnknownVolume{
73+
Zone: getVolumeResponse.Volume.Zone,
74+
ID: getVolumeResponse.Volume.ID,
75+
Name: getVolumeResponse.Volume.Name,
76+
Size: getVolumeResponse.Volume.Size,
77+
IsBlockVolume: false,
78+
InstanceVolumeType: getVolumeResponse.Volume.VolumeType,
79+
}
80+
if getVolumeResponse.Volume.Server != nil {
81+
vol.ServerID = &getVolumeResponse.Volume.Server.ID
82+
}
83+
84+
return vol, nil
85+
}
86+
87+
blockVolume, err := api.blockAPI.GetVolume(&block.GetVolumeRequest{
88+
Zone: req.Zone,
89+
VolumeID: req.VolumeID,
90+
}, opts...)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
vol := &UnknownVolume{
96+
Zone: blockVolume.Zone,
97+
ID: blockVolume.ID,
98+
Name: blockVolume.Name,
99+
Size: blockVolume.Size,
100+
IsBlockVolume: true,
101+
InstanceVolumeType: instance.VolumeVolumeTypeSbsVolume,
102+
}
103+
for _, ref := range blockVolume.References {
104+
if ref.ProductResourceType == "instance_server" {
105+
vol.ServerID = &ref.ProductResourceID
106+
}
107+
}
108+
109+
return vol, nil
110+
}
111+
17112
// newAPIWithZone returns a new instance API and the zone for a Create request
18113
func instanceAndBlockAPIWithZone(d *schema.ResourceData, m interface{}) (*BlockAndInstanceAPI, scw.Zone, error) {
19114
instanceAPI := instance.NewAPI(meta.ExtractScwClient(m))

internal/services/instance/server.go

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -830,27 +830,19 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m
830830

831831
for i, volumeID := range raw.([]interface{}) {
832832
volumeHasChange := d.HasChange("additional_volume_ids." + strconv.Itoa(i))
833-
// local volumes can only be added when the instanceSDK is stopped
834-
if volumeHasChange && !isStopped {
835-
volumeResp, err := api.API.GetVolume(&instanceSDK.GetVolumeRequest{
836-
Zone: zone,
837-
VolumeID: zonal.ExpandID(volumeID).ID,
838-
})
839-
if err != nil {
840-
return diag.FromErr(fmt.Errorf("failed to get updated volume: %w", err))
841-
}
842-
843-
// We must be able to tell whether a volume is already present in the server or not
844-
if volumeResp.Volume.Server != nil {
845-
if volumeResp.Volume.VolumeType == instanceSDK.VolumeVolumeTypeLSSD && volumeResp.Volume.Server.ID != "" {
846-
return diag.FromErr(errors.New("instanceSDK must be stopped to change local volumes"))
847-
}
848-
}
833+
volume, err := api.GetUnknownVolume(&GetUnknownVolumeRequest{
834+
VolumeID: zonal.ExpandID(volumeID).ID,
835+
Zone: zone,
836+
}, scw.WithContext(ctx))
837+
if err != nil {
838+
return diag.FromErr(fmt.Errorf("failed to get updated volume: %w", err))
849839
}
850-
volumes[strconv.Itoa(i+1)] = &instanceSDK.VolumeServerTemplate{
851-
ID: scw.StringPtr(zonal.ExpandID(volumeID).ID),
852-
Name: scw.StringPtr(types.NewRandomName("vol")), // name is ignored by the API, any name will work here
840+
841+
// local volumes can only be added when the server is stopped
842+
if volumeHasChange && !isStopped && volume.IsLocal() && volume.IsAttached() {
843+
return diag.FromErr(errors.New("instanceSDK must be stopped to change local volumes"))
853844
}
845+
volumes[strconv.Itoa(i+1)] = volume.VolumeTemplateUpdate()
854846
}
855847

856848
serverShouldUpdate = true

internal/services/instance/server_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,6 +1955,24 @@ func TestAccServer_BlockExternal(t *testing.T) {
19551955
resource.TestCheckResourceAttr("scaleway_instance_server.main", "additional_volume_ids.#", "0"),
19561956
),
19571957
},
1958+
{
1959+
Config: `
1960+
resource "scaleway_block_volume" "volume" {
1961+
iops = 5000
1962+
size_in_gb = 10
1963+
}
1964+
1965+
resource "scaleway_instance_server" "main" {
1966+
image = "ubuntu_jammy"
1967+
type = "PLAY2-PICO"
1968+
additional_volume_ids = [scaleway_block_volume.volume.id]
1969+
}`,
1970+
Check: resource.ComposeTestCheckFunc(
1971+
resource.TestCheckResourceAttr("scaleway_instance_server.main", "type", "PLAY2-PICO"),
1972+
resource.TestCheckResourceAttr("scaleway_instance_server.main", "additional_volume_ids.#", "1"),
1973+
resource.TestCheckResourceAttrPair("scaleway_instance_server.main", "additional_volume_ids.0", "scaleway_block_volume.volume", "id"),
1974+
),
1975+
},
19581976
},
19591977
})
19601978
}

internal/services/instance/testdata/server-additional-volumes.cassette.yaml

Lines changed: 3815 additions & 2595 deletions
Large diffs are not rendered by default.

internal/services/instance/testdata/server-block-external.cassette.yaml

Lines changed: 4070 additions & 2131 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)