Skip to content

Commit 5ef1b79

Browse files
Merge pull request #521 from linode/proj/vm-placement
project: VM Placement
2 parents 1acdbeb + 30b5602 commit 5ef1b79

12 files changed

+1790
-58
lines changed

account_events.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ const (
158158
ActionPaymentMethodAdd EventAction = "payment_method_add"
159159
ActionPaymentSubmitted EventAction = "payment_submitted"
160160
ActionPasswordReset EventAction = "password_reset"
161+
ActionPlacementGroupCreate EventAction = "placement_group_create"
162+
ActionPlacementGroupUpdate EventAction = "placement_group_update"
163+
ActionPlacementGroupDelete EventAction = "placement_group_delete"
164+
ActionPlacementGroupAssign EventAction = "placement_group_assign"
165+
ActionPlacementGroupUnassign EventAction = "placement_group_unassign"
166+
ActionPlacementGroupBecameNonCompliant EventAction = "placement_group_became_non_compliant"
167+
ActionPlacementGroupBecameCompliant EventAction = "placement_group_became_compliant"
161168
ActionProfileUpdate EventAction = "profile_update"
162169
ActionStackScriptCreate EventAction = "stackscript_create"
163170
ActionStackScriptDelete EventAction = "stackscript_delete"
@@ -226,6 +233,7 @@ const (
226233
EntityManagedService EntityType = "managed_service"
227234
EntityNodebalancer EntityType = "nodebalancer"
228235
EntityOAuthClient EntityType = "oauth_client"
236+
EntityPlacementGroup EntityType = "placement_group"
229237
EntityProfile EntityType = "profile"
230238
EntityStackscript EntityType = "stackscript"
231239
EntityTag EntityType = "tag"

go.work.sum

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +0,0 @@
1-
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
2-
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
3-
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0=
4-
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
5-
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
6-
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
7-
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
8-
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
9-
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
10-
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
11-
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
12-
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
13-
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
14-
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
15-
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
16-
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
17-
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
18-
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
19-
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
20-
github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=
21-
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
22-
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
23-
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
24-
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
25-
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA=
26-
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
27-
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
28-
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
29-
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
30-
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
31-
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
32-
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
33-
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
34-
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
35-
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
36-
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
37-
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
38-
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
39-
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
40-
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
41-
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
42-
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
43-
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
44-
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
45-
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks=
46-
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=

instances.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ type Instance struct {
6262
Specs *InstanceSpec `json:"specs"`
6363
WatchdogEnabled bool `json:"watchdog_enabled"`
6464
Tags []string `json:"tags"`
65+
66+
// NOTE: Placement Groups may not currently be available to all users.
67+
PlacementGroup *InstancePlacementGroup `json:"placement_group"`
6568
}
6669

6770
// InstanceSpec represents a linode spec
@@ -104,6 +107,15 @@ type InstanceTransfer struct {
104107
Quota int `json:"quota"`
105108
}
106109

110+
// InstancePlacementGroup represents information about the placement group
111+
// this Linode is a part of.
112+
type InstancePlacementGroup struct {
113+
ID int `json:"id"`
114+
Label string `json:"label"`
115+
AffinityType PlacementGroupAffinityType `json:"affinity_type"`
116+
IsStrict bool `json:"is_strict"`
117+
}
118+
107119
// InstanceMetadataOptions specifies various Instance creation fields
108120
// that relate to the Linode Metadata service.
109121
type InstanceMetadataOptions struct {
@@ -130,6 +142,9 @@ type InstanceCreateOptions struct {
130142
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
131143
FirewallID int `json:"firewall_id,omitempty"`
132144

145+
// NOTE: Placement Groups may not currently be available to all users.
146+
PlacementGroup *InstanceCreatePlacementGroupOptions `json:"placement_group,omitempty"`
147+
133148
// Creation fields that need to be set explicitly false, "", or 0 use pointers
134149
SwapSize *int `json:"swap_size,omitempty"`
135150
Booted *bool `json:"booted,omitempty"`
@@ -138,6 +153,13 @@ type InstanceCreateOptions struct {
138153
Group string `json:"group,omitempty"`
139154
}
140155

156+
// InstanceCreatePlacementGroupOptions represents the placement group
157+
// to create this Linode under.
158+
type InstanceCreatePlacementGroupOptions struct {
159+
ID int `json:"id"`
160+
CompliantOnly *bool `json:"compliant_only,omitempty"`
161+
}
162+
141163
// InstanceUpdateOptions is an options struct used when Updating an Instance
142164
type InstanceUpdateOptions struct {
143165
Label string `json:"label,omitempty"`
@@ -190,13 +212,14 @@ type InstanceCloneOptions struct {
190212
Type string `json:"type,omitempty"`
191213

192214
// LinodeID is an optional existing instance to use as the target of the clone
193-
LinodeID int `json:"linode_id,omitempty"`
194-
Label string `json:"label,omitempty"`
195-
BackupsEnabled bool `json:"backups_enabled"`
196-
Disks []int `json:"disks,omitempty"`
197-
Configs []int `json:"configs,omitempty"`
198-
PrivateIP bool `json:"private_ip,omitempty"`
199-
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
215+
LinodeID int `json:"linode_id,omitempty"`
216+
Label string `json:"label,omitempty"`
217+
BackupsEnabled bool `json:"backups_enabled"`
218+
Disks []int `json:"disks,omitempty"`
219+
Configs []int `json:"configs,omitempty"`
220+
PrivateIP bool `json:"private_ip,omitempty"`
221+
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
222+
PlacementGroup *InstanceCreatePlacementGroupOptions `json:"placement_group,omitempty"`
200223

201224
// Deprecated: group is a deprecated property denoting a group label for the Linode.
202225
Group string `json:"group,omitempty"`
@@ -211,10 +234,12 @@ type InstanceResizeOptions struct {
211234
AllowAutoDiskResize *bool `json:"allow_auto_disk_resize,omitempty"`
212235
}
213236

214-
// InstanceResizeOptions is an options struct used when resizing an instance
237+
// InstanceMigrateOptions is an options struct used when migrating an instance
215238
type InstanceMigrateOptions struct {
216239
Type InstanceMigrationType `json:"type,omitempty"`
217240
Region string `json:"region,omitempty"`
241+
242+
PlacementGroup *InstanceCreatePlacementGroupOptions `json:"placement_group,omitempty"`
218243
}
219244

220245
// InstancesPagedResponse represents a linode API response for listing

placement_groups.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package linodego
2+
3+
import "context"
4+
5+
// PlacementGroupAffinityType is an enum that determines the affinity policy
6+
// for Linodes in a placement group.
7+
type PlacementGroupAffinityType string
8+
9+
const (
10+
AffinityTypeAntiAffinityLocal PlacementGroupAffinityType = "anti_affinity:local"
11+
)
12+
13+
// PlacementGroupMember represents a single Linode assigned to a
14+
// placement group.
15+
type PlacementGroupMember struct {
16+
LinodeID int `json:"linode_id"`
17+
IsCompliant bool `json:"is_compliant"`
18+
}
19+
20+
// PlacementGroup represents a Linode placement group.
21+
// NOTE: Placement Groups may not currently be available to all users.
22+
type PlacementGroup struct {
23+
ID int `json:"id"`
24+
Label string `json:"label"`
25+
Region string `json:"region"`
26+
AffinityType PlacementGroupAffinityType `json:"affinity_type"`
27+
IsCompliant bool `json:"is_compliant"`
28+
IsStrict bool `json:"is_strict"`
29+
Members []PlacementGroupMember `json:"members"`
30+
}
31+
32+
// PlacementGroupCreateOptions represents the options to use
33+
// when creating a placement group.
34+
type PlacementGroupCreateOptions struct {
35+
Label string `json:"label"`
36+
Region string `json:"region"`
37+
AffinityType PlacementGroupAffinityType `json:"affinity_type"`
38+
IsStrict bool `json:"is_strict"`
39+
}
40+
41+
// PlacementGroupUpdateOptions represents the options to use
42+
// when updating a placement group.
43+
type PlacementGroupUpdateOptions struct {
44+
Label string `json:"label,omitempty"`
45+
}
46+
47+
// PlacementGroupAssignOptions represents options used when
48+
// assigning Linodes to a placement group.
49+
type PlacementGroupAssignOptions struct {
50+
Linodes []int `json:"linodes"`
51+
CompliantOnly *bool `json:"compliant_only,omitempty"`
52+
}
53+
54+
// PlacementGroupUnAssignOptions represents options used when
55+
// unassigning Linodes from a placement group.
56+
type PlacementGroupUnAssignOptions struct {
57+
Linodes []int `json:"linodes"`
58+
}
59+
60+
// ListPlacementGroups lists placement groups under the current account
61+
// matching the given list options.
62+
// NOTE: Placement Groups may not currently be available to all users.
63+
func (c *Client) ListPlacementGroups(
64+
ctx context.Context,
65+
options *ListOptions,
66+
) ([]PlacementGroup, error) {
67+
return getPaginatedResults[PlacementGroup](
68+
ctx,
69+
c,
70+
"placement/groups",
71+
options,
72+
)
73+
}
74+
75+
// GetPlacementGroup gets a placement group with the specified ID.
76+
// NOTE: Placement Groups may not currently be available to all users.
77+
func (c *Client) GetPlacementGroup(
78+
ctx context.Context,
79+
id int,
80+
) (*PlacementGroup, error) {
81+
return doGETRequest[PlacementGroup](
82+
ctx,
83+
c,
84+
formatAPIPath("placement/groups/%d", id),
85+
)
86+
}
87+
88+
// CreatePlacementGroup creates a placement group with the specified options.
89+
// NOTE: Placement Groups may not currently be available to all users.
90+
func (c *Client) CreatePlacementGroup(
91+
ctx context.Context,
92+
options PlacementGroupCreateOptions,
93+
) (*PlacementGroup, error) {
94+
return doPOSTRequest[PlacementGroup](
95+
ctx,
96+
c,
97+
"placement/groups",
98+
options,
99+
)
100+
}
101+
102+
// UpdatePlacementGroup updates a placement group with the specified ID using the provided options.
103+
// NOTE: Placement Groups may not currently be available to all users.
104+
func (c *Client) UpdatePlacementGroup(
105+
ctx context.Context,
106+
id int,
107+
options PlacementGroupUpdateOptions,
108+
) (*PlacementGroup, error) {
109+
return doPUTRequest[PlacementGroup](
110+
ctx,
111+
c,
112+
formatAPIPath("placement/groups/%d", id),
113+
options,
114+
)
115+
}
116+
117+
// AssignPlacementGroupLinodes assigns the specified Linodes to the given
118+
// placement group.
119+
// NOTE: Placement Groups may not currently be available to all users.
120+
func (c *Client) AssignPlacementGroupLinodes(
121+
ctx context.Context,
122+
id int,
123+
options PlacementGroupAssignOptions,
124+
) (*PlacementGroup, error) {
125+
return doPOSTRequest[PlacementGroup](
126+
ctx,
127+
c,
128+
formatAPIPath("placement/groups/%d/assign", id),
129+
options,
130+
)
131+
}
132+
133+
// UnassignPlacementGroupLinodes un-assigns the specified Linodes from the given
134+
// placement group.
135+
// NOTE: Placement Groups may not currently be available to all users.
136+
func (c *Client) UnassignPlacementGroupLinodes(
137+
ctx context.Context,
138+
id int,
139+
options PlacementGroupUnAssignOptions,
140+
) (*PlacementGroup, error) {
141+
return doPOSTRequest[PlacementGroup](
142+
ctx,
143+
c,
144+
formatAPIPath("placement/groups/%d/unassign", id),
145+
options,
146+
)
147+
}
148+
149+
// DeletePlacementGroup deletes a placement group with the specified ID.
150+
// NOTE: Placement Groups may not currently be available to all users.
151+
func (c *Client) DeletePlacementGroup(
152+
ctx context.Context,
153+
id int,
154+
) error {
155+
return doDELETERequest(
156+
ctx,
157+
c,
158+
formatAPIPath("placement/groups/%d", id),
159+
)
160+
}

regions.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ type Region struct {
5353
// A List of enums from the above constants
5454
Capabilities []string `json:"capabilities"`
5555

56-
Status string `json:"status"`
57-
Resolvers RegionResolvers `json:"resolvers"`
58-
Label string `json:"label"`
59-
SiteType string `json:"site_type"`
56+
Status string `json:"status"`
57+
Label string `json:"label"`
58+
SiteType string `json:"site_type"`
59+
60+
Resolvers RegionResolvers `json:"resolvers"`
61+
PlacementGroupLimits *RegionPlacementGroupLimits `json:"placement_group_limits"`
6062
}
6163

6264
// RegionResolvers contains the DNS resolvers of a region
@@ -65,6 +67,13 @@ type RegionResolvers struct {
6567
IPv6 string `json:"ipv6"`
6668
}
6769

70+
// RegionPlacementGroupLimits contains information about the
71+
// placement group limits for the current user in the current region.
72+
type RegionPlacementGroupLimits struct {
73+
MaximumPGsPerCustomer int `json:"maximum_pgs_per_customer"`
74+
MaximumLinodesPerPG int `json:"maximum_linodes_per_pg"`
75+
}
76+
6877
// RegionsPagedResponse represents a linode API response for listing
6978
type RegionsPagedResponse struct {
7079
*PageOptions

0 commit comments

Comments
 (0)