Skip to content

Commit 23b7c1b

Browse files
authored
Add support for placement groups (#13)
1 parent 072cba5 commit 23b7c1b

File tree

7 files changed

+179
-1
lines changed

7 files changed

+179
-1
lines changed

api/v1alpha1/scalewaymachine_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const MachineFinalizer = "scalewaycluster.infrastructure.cluster.x-k8s.io/sm-pro
1010
// ScalewayMachineSpec defines the desired state of ScalewayMachine.
1111
// +kubebuilder:validation:XValidation:rule="has(self.rootVolume) == has(oldSelf.rootVolume)",message="rootVolume cannot be added or removed"
1212
// +kubebuilder:validation:XValidation:rule="has(self.publicNetwork) == has(oldSelf.publicNetwork)",message="publicNetwork cannot be added or removed"
13+
// +kubebuilder:validation:XValidation:rule="has(self.placementGroup) == has(oldSelf.placementGroup)",message="placementGroup cannot be added or removed"
1314
type ScalewayMachineSpec struct {
1415
// ProviderID must match the provider ID as seen on the node object corresponding to this machine.
1516
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
@@ -34,6 +35,11 @@ type ScalewayMachineSpec struct {
3435
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
3536
// +optional
3637
PublicNetwork *PublicNetworkSpec `json:"publicNetwork,omitempty"`
38+
39+
// PlacementGroup allows attaching a Placement Group to the instance.
40+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
41+
// +optional
42+
PlacementGroup *PlacementGroupSpec `json:"placementGroup,omitempty"`
3743
}
3844

3945
// RootVolumeSpec defines the characteristics of the system (root) volume.
@@ -66,6 +72,14 @@ type PublicNetworkSpec struct {
6672
EnableIPv6 *bool `json:"enableIPv6,omitempty"`
6773
}
6874

75+
// +kubebuilder:validation:XValidation:rule="(has(self.id) ? 1 : 0) + (has(self.name) ? 1 : 0) == 1",message="exactly one of id or name must be set"
76+
type PlacementGroupSpec struct {
77+
// ID of the placement group.
78+
ID *string `json:"id,omitempty"`
79+
// Name of the placement group.
80+
Name *string `json:"name,omitempty"`
81+
}
82+
6983
// ImageSpec contains an ID, Name or Label to use to create the instance.
7084
// +kubebuilder:validation:XValidation:rule="(has(self.id) ? 1 : 0) + (has(self.name) ? 1 : 0) + (has(self.label) ? 1 : 0) == 1",message="exactly one of id, name or label must be set"
7185
type ImageSpec struct {

api/v1alpha1/zz_generated.deepcopy.go

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

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ spec:
8282
- message: exactly one of id, name or label must be set
8383
rule: '(has(self.id) ? 1 : 0) + (has(self.name) ? 1 : 0) + (has(self.label)
8484
? 1 : 0) == 1'
85+
placementGroup:
86+
description: PlacementGroup allows attaching a Placement Group to
87+
the instance.
88+
properties:
89+
id:
90+
description: ID of the placement group.
91+
type: string
92+
name:
93+
description: Name of the placement group.
94+
type: string
95+
type: object
96+
x-kubernetes-validations:
97+
- message: Value is immutable
98+
rule: self == oldSelf
99+
- message: exactly one of id or name must be set
100+
rule: '(has(self.id) ? 1 : 0) + (has(self.name) ? 1 : 0) == 1'
85101
providerID:
86102
description: ProviderID must match the provider ID as seen on the
87103
node object corresponding to this machine.
@@ -141,6 +157,8 @@ spec:
141157
rule: has(self.rootVolume) == has(oldSelf.rootVolume)
142158
- message: publicNetwork cannot be added or removed
143159
rule: has(self.publicNetwork) == has(oldSelf.publicNetwork)
160+
- message: placementGroup cannot be added or removed
161+
rule: has(self.placementGroup) == has(oldSelf.placementGroup)
144162
status:
145163
description: ScalewayMachineStatus defines the observed state of ScalewayMachine.
146164
properties:

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,23 @@ spec:
8383
- message: exactly one of id, name or label must be set
8484
rule: '(has(self.id) ? 1 : 0) + (has(self.name) ? 1 : 0)
8585
+ (has(self.label) ? 1 : 0) == 1'
86+
placementGroup:
87+
description: PlacementGroup allows attaching a Placement Group
88+
to the instance.
89+
properties:
90+
id:
91+
description: ID of the placement group.
92+
type: string
93+
name:
94+
description: Name of the placement group.
95+
type: string
96+
type: object
97+
x-kubernetes-validations:
98+
- message: Value is immutable
99+
rule: self == oldSelf
100+
- message: exactly one of id or name must be set
101+
rule: '(has(self.id) ? 1 : 0) + (has(self.name) ? 1 : 0)
102+
== 1'
86103
providerID:
87104
description: ProviderID must match the provider ID as seen
88105
on the node object corresponding to this machine.
@@ -144,6 +161,8 @@ spec:
144161
rule: has(self.rootVolume) == has(oldSelf.rootVolume)
145162
- message: publicNetwork cannot be added or removed
146163
rule: has(self.publicNetwork) == has(oldSelf.publicNetwork)
164+
- message: placementGroup cannot be added or removed
165+
rule: has(self.placementGroup) == has(oldSelf.placementGroup)
147166
required:
148167
- spec
149168
type: object

docs/scalewaymachine.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ The `image` field must contain one of the following:
7979
# some fields were omitted...
8080
```
8181

82-
Make sure this image exists in the zones when you plan to deploy your nodes.
82+
Make sure this image exists in the zones where you plan to deploy your nodes.
8383
You can list images by name with this command:
8484

8585
```bash
@@ -169,3 +169,52 @@ Private Network (`network.privateNetwork.enabled`) in the `ScalewayCluster`.
169169
connectivity, make sure a Public Gateway advertises its default route in the
170170
Private Network as nodes will not be able to access the control-plane Load Balancer
171171
without public connectivity.
172+
173+
## Placement Group
174+
175+
It is possible to attach an existing Placement group to the Instance server that will be created.
176+
177+
> [!WARNING]
178+
> A Placement group can be attached to at most 20 Instance servers.
179+
180+
Placement groups allow you to define if you want certain Instances to run on
181+
different physical hypervisors for maximum availability or as physically close
182+
together as possible for minimum latency. For more information about Placement
183+
groups, please refer to the [Scaleway documentation](https://www.scaleway.com/en/docs/instances/how-to/use-placement-groups/).
184+
185+
The `placementGroup` field must contain one of the following:
186+
187+
- A Placement group ID:
188+
189+
```yaml
190+
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
191+
kind: ScalewayMachine
192+
metadata:
193+
name: my-machine
194+
namespace: default
195+
spec:
196+
placementGroup:
197+
id: 11111111-1111-1111-1111-111111111111
198+
# some fields were omitted...
199+
```
200+
201+
- A Placement group name:
202+
203+
```yaml
204+
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
205+
kind: ScalewayMachine
206+
metadata:
207+
name: my-machine
208+
namespace: default
209+
spec:
210+
placementGroup:
211+
name: my-placement-group
212+
# some fields were omitted...
213+
```
214+
215+
Make sure this Placement group exists in the zones where you plan to deploy your nodes.
216+
You can list Placement groups by name with this command:
217+
218+
```bash
219+
scw instance placement-group list name=${IMAGE_NAME} zone=${SCW_ZONE}
220+
```

internal/service/scaleway/client/instance.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func (c *Client) CreateServer(
5050
ctx context.Context,
5151
zone scw.Zone,
5252
name, commercialType, imageID string,
53+
placementGroupID *string,
5354
rootVolumeSize scw.Size,
5455
rootVolumeType instance.VolumeVolumeType,
5556
publicIPs, tags []string,
@@ -72,6 +73,7 @@ func (c *Client) CreateServer(
7273
CommercialType: commercialType,
7374
DynamicIPRequired: scw.BoolPtr(false),
7475
Image: &imageID,
76+
PlacementGroup: placementGroupID,
7577
Volumes: map[string]*instance.VolumeServerTemplate{
7678
"0": {
7779
Size: &rootVolumeSize,
@@ -368,3 +370,32 @@ func (c *Client) DeleteServer(ctx context.Context, zone scw.Zone, serverID strin
368370

369371
return nil
370372
}
373+
374+
func (c *Client) FindPlacementGroup(ctx context.Context, zone scw.Zone, name string) (*instance.PlacementGroup, error) {
375+
if err := c.validateZone(c.instance, zone); err != nil {
376+
return nil, err
377+
}
378+
379+
resp, err := c.instance.ListPlacementGroups(&instance.ListPlacementGroupsRequest{
380+
Zone: zone,
381+
Name: &name,
382+
Project: &c.projectID,
383+
}, scw.WithContext(ctx), scw.WithAllPages())
384+
if err != nil {
385+
return nil, newCallError("ListPlacementGroups", err)
386+
}
387+
388+
// Filter out all placement groups that have the wrong name.
389+
placementGroups := slices.DeleteFunc(resp.PlacementGroups, func(pg *instance.PlacementGroup) bool {
390+
return pg.Name != name
391+
})
392+
393+
switch len(placementGroups) {
394+
case 0:
395+
return nil, ErrNoItemFound
396+
case 1:
397+
return placementGroups[0], nil
398+
default:
399+
return nil, fmt.Errorf("%w: found %d placement groups with name %s", ErrTooManyItemsFound, len(placementGroups), name)
400+
}
401+
}

internal/service/scaleway/instance/instance.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,30 @@ func (s *Service) ensureServer(ctx context.Context) (*instance.Server, error) {
256256
}
257257
}
258258

259+
// If user has specified a placement group, get its ID.
260+
var placementGroupID *string
261+
if s.ScalewayMachine.Spec.PlacementGroup != nil {
262+
switch pgref := s.ScalewayMachine.Spec.PlacementGroup; {
263+
case pgref.ID != nil:
264+
placementGroupID = pgref.ID
265+
case pgref.Name != nil:
266+
placementGroup, err := s.ScalewayClient.FindPlacementGroup(ctx, zone, *pgref.Name)
267+
if err != nil {
268+
return nil, fmt.Errorf("failed to find placement group: %w", err)
269+
}
270+
271+
placementGroupID = &placementGroup.ID
272+
}
273+
}
274+
259275
// Finally, create the server.
260276
server, err := s.ScalewayClient.CreateServer(
261277
ctx,
262278
zone,
263279
s.ResourceName(),
264280
s.ScalewayMachine.Spec.CommercialType,
265281
imageID,
282+
placementGroupID,
266283
s.RootVolumeSize(),
267284
volumeType,
268285
publicIPs,

0 commit comments

Comments
 (0)