Skip to content

Commit

Permalink
new: Add support for VM Placement (#490)
Browse files Browse the repository at this point in the history
* Add placement group object

* Add placement group limits to region response

* Implement instance changes

* prefixture

* Add pg basic test

* finish assignment tests

* Add regions test

* oops

* make tidy
  • Loading branch information
lgarber-akamai authored Apr 16, 2024
1 parent 4eb5658 commit 7ebeb19
Show file tree
Hide file tree
Showing 11 changed files with 1,805 additions and 34 deletions.
75 changes: 48 additions & 27 deletions instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,26 @@ const (

// Instance represents a linode object
type Instance struct {
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Region string `json:"region"`
Alerts *InstanceAlert `json:"alerts"`
Backups *InstanceBackup `json:"backups"`
Image string `json:"image"`
Group string `json:"group"`
IPv4 []*net.IP `json:"ipv4"`
IPv6 string `json:"ipv6"`
Label string `json:"label"`
Type string `json:"type"`
Status InstanceStatus `json:"status"`
HasUserData bool `json:"has_user_data"`
Hypervisor string `json:"hypervisor"`
HostUUID string `json:"host_uuid"`
Specs *InstanceSpec `json:"specs"`
WatchdogEnabled bool `json:"watchdog_enabled"`
Tags []string `json:"tags"`
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Region string `json:"region"`
Alerts *InstanceAlert `json:"alerts"`
Backups *InstanceBackup `json:"backups"`
Image string `json:"image"`
Group string `json:"group"`
IPv4 []*net.IP `json:"ipv4"`
IPv6 string `json:"ipv6"`
Label string `json:"label"`
Type string `json:"type"`
Status InstanceStatus `json:"status"`
HasUserData bool `json:"has_user_data"`
Hypervisor string `json:"hypervisor"`
HostUUID string `json:"host_uuid"`
Specs *InstanceSpec `json:"specs"`
WatchdogEnabled bool `json:"watchdog_enabled"`
Tags []string `json:"tags"`
PlacementGroup *InstancePlacementGroup `json:"placement_group"`
}

// InstanceSpec represents a linode spec
Expand Down Expand Up @@ -104,6 +105,15 @@ type InstanceTransfer struct {
Quota int `json:"quota"`
}

// InstancePlacementGroup represents information about the placement group
// this Linode is a part of.
type InstancePlacementGroup struct {
ID int `json:"id"`
Label string `json:"label"`
AffinityType PlacementGroupAffinityType `json:"affinity_type"`
IsStrict bool `json:"is_strict"`
}

// InstanceMetadataOptions specifies various Instance creation fields
// that relate to the Linode Metadata service.
type InstanceMetadataOptions struct {
Expand All @@ -129,6 +139,7 @@ type InstanceCreateOptions struct {
Tags []string `json:"tags,omitempty"`
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
FirewallID int `json:"firewall_id,omitempty"`
PlacementGroup *InstanceCreatePlacementGroupOptions `json:"placement_group,omitempty"`

// Creation fields that need to be set explicitly false, "", or 0 use pointers
SwapSize *int `json:"swap_size,omitempty"`
Expand All @@ -138,6 +149,13 @@ type InstanceCreateOptions struct {
Group string `json:"group,omitempty"`
}

// InstanceCreatePlacementGroupOptions represents the placement group
// to create this Linode under.
type InstanceCreatePlacementGroupOptions struct {
ID int `json:"id"`
CompliantOnly *bool `json:"compliant_only,omitempty"`
}

// InstanceUpdateOptions is an options struct used when Updating an Instance
type InstanceUpdateOptions struct {
Label string `json:"label,omitempty"`
Expand Down Expand Up @@ -190,13 +208,14 @@ type InstanceCloneOptions struct {
Type string `json:"type,omitempty"`

// LinodeID is an optional existing instance to use as the target of the clone
LinodeID int `json:"linode_id,omitempty"`
Label string `json:"label,omitempty"`
BackupsEnabled bool `json:"backups_enabled"`
Disks []int `json:"disks,omitempty"`
Configs []int `json:"configs,omitempty"`
PrivateIP bool `json:"private_ip,omitempty"`
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
LinodeID int `json:"linode_id,omitempty"`
Label string `json:"label,omitempty"`
BackupsEnabled bool `json:"backups_enabled"`
Disks []int `json:"disks,omitempty"`
Configs []int `json:"configs,omitempty"`
PrivateIP bool `json:"private_ip,omitempty"`
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
PlacementGroup *InstanceCreatePlacementGroupOptions `json:"placement_group,omitempty"`

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

// InstanceResizeOptions is an options struct used when resizing an instance
// InstanceMigrateOptions is an options struct used when migrating an instance
type InstanceMigrateOptions struct {
Type InstanceMigrationType `json:"type,omitempty"`
Region string `json:"region,omitempty"`

PlacementGroup *InstanceCreatePlacementGroupOptions `json:"placement_group,omitempty"`
}

// InstancesPagedResponse represents a linode API response for listing
Expand Down
152 changes: 152 additions & 0 deletions placement_groups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package linodego

import "context"

// PlacementGroupAffinityType is an enum that determines the affinity policy
// for Linodes in a placement group.
type PlacementGroupAffinityType string

const (
AffinityTypeAntiAffinityLocal PlacementGroupAffinityType = "anti_affinity:local"
)

// PlacementGroupMember represents a single Linode assigned to a
// placement group.
type PlacementGroupMember struct {
LinodeID int `json:"linode_id"`
IsCompliant bool `json:"is_compliant"`
}

// PlacementGroup represents a Linode placement group.
type PlacementGroup struct {
ID int `json:"id"`
Label string `json:"label"`
Region string `json:"region"`
AffinityType PlacementGroupAffinityType `json:"affinity_type"`
IsCompliant bool `json:"is_compliant"`
IsStrict bool `json:"is_strict"`
Members []PlacementGroupMember `json:"members"`
}

// PlacementGroupCreateOptions represents the options to use
// when creating a placement group.
type PlacementGroupCreateOptions struct {
Label string `json:"label"`
Region string `json:"region"`
AffinityType PlacementGroupAffinityType `json:"affinity_type"`
IsStrict bool `json:"is_strict"`
}

// PlacementGroupUpdateOptions represents the options to use
// when updating a placement group.
type PlacementGroupUpdateOptions struct {
Label string `json:"label,omitempty"`
}

// PlacementGroupAssignOptions represents options used when
// assigning Linodes to a placement group.
type PlacementGroupAssignOptions struct {
Linodes []int `json:"linodes"`
CompliantOnly *bool `json:"compliant_only,omitempty"`
}

// PlacementGroupUnAssignOptions represents options used when
// unassigning Linodes from a placement group.
type PlacementGroupUnAssignOptions struct {
Linodes []int `json:"linodes"`
}

// ListPlacementGroups lists placement groups under the current account
// matching the given list options.
func (c *Client) ListPlacementGroups(
ctx context.Context,
options *ListOptions,
) ([]PlacementGroup, error) {
return getPaginatedResults[PlacementGroup](
ctx,
c,
"placement/groups",
options,
)
}

// GetPlacementGroup gets a placement group with the specified ID.
func (c *Client) GetPlacementGroup(
ctx context.Context,
id int,
) (*PlacementGroup, error) {
return doGETRequest[PlacementGroup](
ctx,
c,
formatAPIPath("placement/groups/%d", id),
)
}

// CreatePlacementGroup creates a placement group with the specified options.
func (c *Client) CreatePlacementGroup(
ctx context.Context,
options PlacementGroupCreateOptions,
) (*PlacementGroup, error) {
return doPOSTRequest[PlacementGroup](
ctx,
c,
"placement/groups",
options,
)
}

// UpdatePlacementGroup updates a placement group with the specified ID using the provided options.
func (c *Client) UpdatePlacementGroup(
ctx context.Context,
id int,
options PlacementGroupUpdateOptions,
) (*PlacementGroup, error) {
return doPUTRequest[PlacementGroup](
ctx,
c,
formatAPIPath("placement/groups/%d", id),
options,
)
}

// AssignPlacementGroupLinodes assigns the specified Linodes to the given
// placement group.
func (c *Client) AssignPlacementGroupLinodes(
ctx context.Context,
id int,
options PlacementGroupAssignOptions,
) (*PlacementGroup, error) {
return doPOSTRequest[PlacementGroup](
ctx,
c,
formatAPIPath("placement/groups/%d/assign", id),
options,
)
}

// UnAssignPlacementGroupLinodes un-assigns the specified Linodes from the given
// placement group.
func (c *Client) UnAssignPlacementGroupLinodes(
ctx context.Context,
id int,
options PlacementGroupUnAssignOptions,
) (*PlacementGroup, error) {
return doPOSTRequest[PlacementGroup](
ctx,
c,
formatAPIPath("placement/groups/%d/unassign", id),
options,
)
}

// DeletePlacementGroup deletes a placement group with the specified ID.
func (c *Client) DeletePlacementGroup(
ctx context.Context,
id int,
) error {
return doDELETERequest(
ctx,
c,
formatAPIPath("placement/groups/%d", id),
)
}
23 changes: 16 additions & 7 deletions regions.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ var cacheExpiryTime = time.Minute

// Region represents a linode region object
type Region struct {
ID string `json:"id"`
Country string `json:"country"`
Capabilities []string `json:"capabilities"`
Status string `json:"status"`
Resolvers RegionResolvers `json:"resolvers"`
Label string `json:"label"`
SiteType string `json:"site_type"`
ID string `json:"id"`
Country string `json:"country"`
Capabilities []string `json:"capabilities"`
Status string `json:"status"`
Label string `json:"label"`
SiteType string `json:"site_type"`

Resolvers RegionResolvers `json:"resolvers"`
PlacementGroupLimits *RegionPlacementGroupLimits `json:"placement_group_limits"`
}

// RegionResolvers contains the DNS resolvers of a region
Expand All @@ -30,6 +32,13 @@ type RegionResolvers struct {
IPv6 string `json:"ipv6"`
}

// RegionPlacementGroupLimits contains information about the
// placement group limits for the current user in the current region.
type RegionPlacementGroupLimits struct {
MaximumPGsPerCustomer int `json:"maximum_pgs_per_customer"`
MaximumLinodesPerPG int `json:"maximum_linodes_per_pg"`
}

// RegionsPagedResponse represents a linode API response for listing
type RegionsPagedResponse struct {
*PageOptions
Expand Down
2 changes: 2 additions & 0 deletions test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/jarcoal/httpmock v1.3.1
github.com/linode/linodego v1.30.0
github.com/linode/linodego/k8s v0.0.0-00010101000000-000000000000
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.19.0
k8s.io/client-go v0.28.8
Expand All @@ -31,6 +32,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
Expand Down
Loading

0 comments on commit 7ebeb19

Please sign in to comment.