Skip to content

Commit 922d077

Browse files
committed
Add external storage support for etcd
This allows DOMachines to be provisioned with attached block storage volumes. The current implemenation does not support resizing disks. This will be added as a follow-up.
1 parent 315917a commit 922d077

File tree

12 files changed

+269
-9
lines changed

12 files changed

+269
-9
lines changed

api/v1alpha3/domachine_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type DOMachineSpec struct {
3838
Size string `json:"size"`
3939
// Droplet image can be image id or slug. See https://developers.digitalocean.com/documentation/v2/#list-all-images
4040
Image intstr.IntOrString `json:"image"`
41+
// DataDisks specifies the parameters that are used to add one or more data disks to the machine
42+
DataDisks []DataDisk `json:"dataDisks,omitempty"`
4143
// SSHKeys is the ssh key id or fingerprint to attach in DigitalOcean droplet.
4244
// It must be available on DigitalOcean account. See https://developers.digitalocean.com/documentation/v2/#list-all-keys
4345
SSHKeys []intstr.IntOrString `json:"sshKeys"`

api/v1alpha3/types.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ limitations under the License.
1616

1717
package v1alpha3
1818

19-
import "strings"
19+
import (
20+
"fmt"
21+
"strings"
22+
)
2023

2124
// APIEndpoint represents a reachable Kubernetes API endpoint.
2225
type APIEndpoint struct {
@@ -72,6 +75,28 @@ type DOMachineTemplateResource struct {
7275
Spec DOMachineSpec `json:"spec"`
7376
}
7477

78+
// DataDiskName is the volume name used for a data disk of a droplet.
79+
// It's in the form of <dropletName>_<dataDiskNameSuffix>.
80+
func DataDiskName(m *DOMachine, suffix string) string {
81+
return DOSafeName(fmt.Sprintf("%s-%s", m.Name, suffix))
82+
}
83+
84+
// DataDisk specifies the parameters that are used to add a data disk to the machine.
85+
type DataDisk struct {
86+
// NameSuffix is the suffix to be appended to the machine name to generate the disk name.
87+
// Each disk name will be in format <dropletName>_<nameSuffix>.
88+
NameSuffix string `json:"nameSuffix"`
89+
// DiskSizeGB is the size in GB to assign to the data disk.
90+
DiskSizeGB int64 `json:"diskSizeGB"`
91+
// FilesystemType to be used on the volume. When provided the volume will
92+
// be automatically formatted.
93+
FilesystemType string `json:"filesystemType,omitempty"`
94+
// FilesystemLabel is the label that is applied to the created filesystem.
95+
// Character limits apply: 16 for ext4; 12 for xfs.
96+
// May only be used in conjunction with filesystemType.
97+
FilesystemLabel string `json:"filesystemLabel,omitempty"`
98+
}
99+
75100
// DONetwork encapsulates DigitalOcean networking configuration.
76101
type DONetwork struct {
77102
// Configures an API Server loadbalancers

api/v1alpha3/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/scope/clients.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
type DOClients struct {
2323
Actions godo.ActionsService
2424
Droplets godo.DropletsService
25+
Storage godo.StorageService
2526
Images godo.ImagesService
2627
Keys godo.KeysService
2728
LoadBalancers godo.LoadBalancersService

cloud/scope/cluster.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ func NewClusterScope(params ClusterScopeParams) (*ClusterScope, error) {
6666
params.DOClients.Droplets = session.Droplets
6767
}
6868

69+
if params.DOClients.Storage == nil {
70+
params.DOClients.Storage = session.Storage
71+
}
72+
6973
if params.DOClients.Images == nil {
7074
params.DOClients.Images = session.Images
7175
}

cloud/services/computes/droplets.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package computes
1818

1919
import (
20+
"fmt"
2021
"net/http"
2122
"strconv"
2223

@@ -82,6 +83,19 @@ func (s *Service) CreateDroplet(scope *scope.MachineScope) (*godo.Droplet, error
8283
})
8384
}
8485

86+
volumes := []godo.DropletCreateVolume{}
87+
for _, disk := range scope.DOMachine.Spec.DataDisks {
88+
volName := infrav1.DataDiskName(scope.DOMachine, disk.NameSuffix)
89+
vol, err := s.GetVolumeByName(volName)
90+
if err != nil {
91+
return nil, fmt.Errorf("could not get volume to attach to droplet: %w", err)
92+
}
93+
if vol == nil {
94+
return nil, fmt.Errorf("volume %q does not exist", volName)
95+
}
96+
volumes = append(volumes, godo.DropletCreateVolume{ID: vol.ID})
97+
}
98+
8599
request := &godo.DropletCreateRequest{
86100
Name: instanceName,
87101
Region: s.scope.Region(),
@@ -92,6 +106,7 @@ func (s *Service) CreateDroplet(scope *scope.MachineScope) (*godo.Droplet, error
92106
},
93107
UserData: bootstrapData,
94108
PrivateNetworking: true,
109+
Volumes: volumes,
95110
}
96111

97112
request.Tags = infrav1.BuildTags(infrav1.BuildTagParams{

cloud/services/computes/service.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import (
2222
"sigs.k8s.io/cluster-api-provider-digitalocean/cloud/scope"
2323
)
2424

25+
// TemporaryError is an error that is raised for temporary DO API errors that
26+
// could be retried in upper levels of the callstack.
27+
type TemporaryError struct {
28+
error
29+
}
30+
2531
// Service holds a collection of interfaces.
2632
type Service struct {
2733
scope *scope.ClusterScope

cloud/services/computes/volumes.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package computes
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/digitalocean/godo"
7+
"github.com/pkg/errors"
8+
9+
infrav1 "sigs.k8s.io/cluster-api-provider-digitalocean/api/v1alpha3"
10+
)
11+
12+
// GetVolumeByName takes a volume name and returns a Volume if found.
13+
func (s *Service) GetVolumeByName(name string) (*godo.Volume, error) {
14+
vols, _, err := s.scope.Storage.ListVolumes(s.ctx, &godo.ListVolumeParams{
15+
Name: name,
16+
Region: s.scope.Region(),
17+
})
18+
if err != nil {
19+
return nil, TemporaryError{fmt.Errorf("failed to list volumes: %w", err)}
20+
}
21+
if len(vols) == 0 {
22+
return nil, nil
23+
}
24+
if len(vols) > 1 {
25+
return nil, errors.New("volume names are not unique per region")
26+
}
27+
return &vols[0], nil
28+
}
29+
30+
// CreateVolume creates a block storage volume.
31+
func (s *Service) CreateVolume(disk infrav1.DataDisk, volName string) (*godo.Volume, error) {
32+
r := &godo.VolumeCreateRequest{
33+
Region: s.scope.Region(),
34+
Name: volName,
35+
SizeGigaBytes: disk.DiskSizeGB,
36+
FilesystemType: disk.FilesystemType,
37+
FilesystemLabel: disk.FilesystemLabel,
38+
}
39+
v, _, err := s.scope.Storage.CreateVolume(s.ctx, r)
40+
return v, errors.Wrap(err, "failed to create new volume")
41+
}
42+
43+
// DeleteVolume deletes a block storage volume.
44+
func (s *Service) DeleteVolume(id string) error {
45+
s.scope.V(2).Info("Attempting to delete block storage volume", "volume-id", id)
46+
47+
if resp, err := s.scope.Storage.DeleteVolume(s.ctx, id); err != nil {
48+
if resp.StatusCode >= 500 {
49+
err = TemporaryError{err}
50+
}
51+
return fmt.Errorf("failed to delete instance with id %q: %w", id, err)
52+
}
53+
54+
s.scope.V(2).Info("Deleted block storage volume", "volume-id", id)
55+
return nil
56+
}

config/crd/bases/infrastructure.cluster.x-k8s.io_domachines.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,37 @@ spec:
184184
items:
185185
type: string
186186
type: array
187+
dataDisks:
188+
description: DataDisks specifies the parameters that are used to add
189+
one or more data disks to the machine
190+
items:
191+
description: DataDisk specifies the parameters that are used to
192+
add a data disk to the machine.
193+
properties:
194+
diskSizeGB:
195+
description: DiskSizeGB is the size in GB to assign to the data
196+
disk.
197+
format: int64
198+
type: integer
199+
filesystemLabel:
200+
description: 'FilesystemLabel is the label that is applied to
201+
the created filesystem. Character limits apply: 16 for ext4;
202+
12 for xfs. May only be used in conjunction with filesystemType.'
203+
type: string
204+
filesystemType:
205+
description: FilesystemType to be used on the volume. When provided
206+
the volume will be automatically formatted.
207+
type: string
208+
nameSuffix:
209+
description: NameSuffix is the suffix to be appended to the
210+
machine name to generate the disk name. Each disk name will
211+
be in format <dropletName>_<nameSuffix>.
212+
type: string
213+
required:
214+
- diskSizeGB
215+
- nameSuffix
216+
type: object
217+
type: array
187218
image:
188219
anyOf:
189220
- type: integer

config/crd/bases/infrastructure.cluster.x-k8s.io_domachinetemplates.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,38 @@ spec:
126126
items:
127127
type: string
128128
type: array
129+
dataDisks:
130+
description: DataDisks specifies the parameters that are used
131+
to add one or more data disks to the machine
132+
items:
133+
description: DataDisk specifies the parameters that are
134+
used to add a data disk to the machine.
135+
properties:
136+
diskSizeGB:
137+
description: DiskSizeGB is the size in GB to assign
138+
to the data disk.
139+
format: int64
140+
type: integer
141+
filesystemLabel:
142+
description: 'FilesystemLabel is the label that is applied
143+
to the created filesystem. Character limits apply:
144+
16 for ext4; 12 for xfs. May only be used in conjunction
145+
with filesystemType.'
146+
type: string
147+
filesystemType:
148+
description: FilesystemType to be used on the volume.
149+
When provided the volume will be automatically formatted.
150+
type: string
151+
nameSuffix:
152+
description: NameSuffix is the suffix to be appended
153+
to the machine name to generate the disk name. Each
154+
disk name will be in format <dropletName>_<nameSuffix>.
155+
type: string
156+
required:
157+
- diskSizeGB
158+
- nameSuffix
159+
type: object
160+
type: array
129161
image:
130162
anyOf:
131163
- type: integer

0 commit comments

Comments
 (0)