From 38c85628e66260f4e2f770e5479c210d6522b201 Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Thu, 24 Oct 2024 18:07:01 -0400 Subject: [PATCH 1/3] sizes: support disk_info --- sizes.go | 34 +++++++++++----- sizes_test.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 14 deletions(-) diff --git a/sizes.go b/sizes.go index a3cb7452..b9086a72 100644 --- a/sizes.go +++ b/sizes.go @@ -22,16 +22,30 @@ var _ SizesService = &SizesServiceOp{} // Size represents a DigitalOcean Size type Size struct { - Slug string `json:"slug,omitempty"` - Memory int `json:"memory,omitempty"` - Vcpus int `json:"vcpus,omitempty"` - Disk int `json:"disk,omitempty"` - PriceMonthly float64 `json:"price_monthly,omitempty"` - PriceHourly float64 `json:"price_hourly,omitempty"` - Regions []string `json:"regions,omitempty"` - Available bool `json:"available,omitempty"` - Transfer float64 `json:"transfer,omitempty"` - Description string `json:"description,omitempty"` + Slug string `json:"slug,omitempty"` + Memory int `json:"memory,omitempty"` + Vcpus int `json:"vcpus,omitempty"` + Disk int `json:"disk,omitempty"` + PriceMonthly float64 `json:"price_monthly,omitempty"` + PriceHourly float64 `json:"price_hourly,omitempty"` + Regions []string `json:"regions,omitempty"` + Available bool `json:"available,omitempty"` + Transfer float64 `json:"transfer,omitempty"` + Description string `json:"description,omitempty"` + DiskInfo []DiskInfo `json:"disk_info,omitempty"` +} + +// DiskInfo containing information about the disks available to Droplets created +// with this size. +type DiskInfo struct { + Type string `json:"type,omitempty"` + Size DiskSize `json:"size,omitempty"` +} + +// DiskSize provides information about the size of a disk. +type DiskSize struct { + Amount int `json:"amount,omitempty"` + Unit string `json:"unit,omitempty"` } func (s Size) String() string { diff --git a/sizes_test.go b/sizes_test.go index 784a88f6..85febb77 100644 --- a/sizes_test.go +++ b/sizes_test.go @@ -23,6 +23,15 @@ func TestSizes_List(t *testing.T) { Available: true, Transfer: 1, Description: "Basic", + DiskInfo: []DiskInfo{ + { + Type: "local", + Size: DiskSize{ + Amount: 25, + Unit: "gib", + }, + }, + }, }, { Slug: "512mb", @@ -35,6 +44,43 @@ func TestSizes_List(t *testing.T) { Available: true, Transfer: 1, Description: "Legacy Basic", + DiskInfo: []DiskInfo{ + { + Type: "local", + Size: DiskSize{ + Amount: 20, + Unit: "gib", + }, + }, + }, + }, + { + Slug: "gpu-h100x8-640gb-200", + Memory: 1966080, + Vcpus: 160, + Disk: 200, + PriceMonthly: 35414.4, + PriceHourly: 52.7, + Regions: []string{"tor1"}, + Available: true, + Transfer: 60, + Description: "H100 GPU - 8X (small disk)", + DiskInfo: []DiskInfo{ + { + Type: "local", + Size: DiskSize{ + Amount: 200, + Unit: "gib", + }, + }, + { + Type: "scratch", + Size: DiskSize{ + Amount: 40960, + Unit: "gib", + }, + }, + }, }, } @@ -55,7 +101,16 @@ func TestSizes_List(t *testing.T) { "nyc2" ], "available": true, - "description": "Basic" + "description": "Basic", + "disk_info": [ + { + "type": "local", + "size": { + "amount": 25, + "unit": "gib" + } + } + ] }, { "slug": "512mb", @@ -70,11 +125,59 @@ func TestSizes_List(t *testing.T) { "nyc2" ], "available": true, - "description": "Legacy Basic" + "description": "Legacy Basic", + "disk_info": [ + { + "type": "local", + "size": { + "amount": 20, + "unit": "gib" + } + } + ] + }, + { + "slug": "gpu-h100x8-640gb-200", + "memory": 1966080, + "vcpus": 160, + "disk": 200, + "transfer": 60, + "price_monthly": 35414.4, + "price_hourly": 52.7, + "regions": [ + "tor1" + ], + "available": true, + "description": "H100 GPU - 8X (small disk)", + "networking_throughput": 10000, + "gpu_info": { + "count": 8, + "vram": { + "amount": 640, + "unit": "gib" + }, + "model": "nvidia_h100" + }, + "disk_info": [ + { + "type": "local", + "size": { + "amount": 200, + "unit": "gib" + } + }, + { + "type": "scratch", + "size": { + "amount": 40960, + "unit": "gib" + } + } + ] } ], "meta": { - "total": 2 + "total": 3 } }`) }) @@ -88,7 +191,7 @@ func TestSizes_List(t *testing.T) { t.Errorf("Sizes.List returned sizes %+v, expected %+v", sizes, expectedSizes) } - expectedMeta := &Meta{Total: 2} + expectedMeta := &Meta{Total: 3} if !reflect.DeepEqual(resp.Meta, expectedMeta) { t.Errorf("Sizes.List returned meta %+v, expected %+v", resp.Meta, expectedMeta) } From 82ca0f91c6bc8a1d17b6361cea6d9738bec2a0c2 Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Thu, 24 Oct 2024 18:19:11 -0400 Subject: [PATCH 2/3] sizes: support gpu_info --- sizes.go | 18 ++++++++++++++++-- sizes_test.go | 20 ++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/sizes.go b/sizes.go index b9086a72..72d5321c 100644 --- a/sizes.go +++ b/sizes.go @@ -32,14 +32,15 @@ type Size struct { Available bool `json:"available,omitempty"` Transfer float64 `json:"transfer,omitempty"` Description string `json:"description,omitempty"` + GPUInfo *GPUInfo `json:"gpu_info,omitempty"` DiskInfo []DiskInfo `json:"disk_info,omitempty"` } // DiskInfo containing information about the disks available to Droplets created // with this size. type DiskInfo struct { - Type string `json:"type,omitempty"` - Size DiskSize `json:"size,omitempty"` + Type string `json:"type,omitempty"` + Size *DiskSize `json:"size,omitempty"` } // DiskSize provides information about the size of a disk. @@ -48,6 +49,19 @@ type DiskSize struct { Unit string `json:"unit,omitempty"` } +// GPUInfo provides information about the GPU available to Droplets created with this size. +type GPUInfo struct { + Count int `json:"count,omitempty"` + VRAM *VRAM `json:"vram,omitempty"` + Model string `json:"model,omitempty"` +} + +// VRAM provides information about the amount of VRAM available to the GPU. +type VRAM struct { + Amount int `json:"amount,omitempty"` + Unit string `json:"unit,omitempty"` +} + func (s Size) String() string { return Stringify(s) } diff --git a/sizes_test.go b/sizes_test.go index 85febb77..145ea16c 100644 --- a/sizes_test.go +++ b/sizes_test.go @@ -26,7 +26,7 @@ func TestSizes_List(t *testing.T) { DiskInfo: []DiskInfo{ { Type: "local", - Size: DiskSize{ + Size: &DiskSize{ Amount: 25, Unit: "gib", }, @@ -47,7 +47,7 @@ func TestSizes_List(t *testing.T) { DiskInfo: []DiskInfo{ { Type: "local", - Size: DiskSize{ + Size: &DiskSize{ Amount: 20, Unit: "gib", }, @@ -65,17 +65,25 @@ func TestSizes_List(t *testing.T) { Available: true, Transfer: 60, Description: "H100 GPU - 8X (small disk)", + GPUInfo: &GPUInfo{ + Count: 8, + VRAM: &VRAM{ + Amount: 640, + Unit: "gib", + }, + Model: "nvidia_h100", + }, DiskInfo: []DiskInfo{ { Type: "local", - Size: DiskSize{ + Size: &DiskSize{ Amount: 200, Unit: "gib", }, }, { Type: "scratch", - Size: DiskSize{ + Size: &DiskSize{ Amount: 40960, Unit: "gib", }, @@ -153,8 +161,8 @@ func TestSizes_List(t *testing.T) { "gpu_info": { "count": 8, "vram": { - "amount": 640, - "unit": "gib" + "amount": 640, + "unit": "gib" }, "model": "nvidia_h100" }, From e99f22a846d05b3fa97bcec513310c6e985f297c Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Thu, 24 Oct 2024 18:41:31 -0400 Subject: [PATCH 3/3] droplets: support listing GPU Droplets. --- droplets.go | 12 +++++++ droplets_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/droplets.go b/droplets.go index 5f198636..1ed09ec8 100644 --- a/droplets.go +++ b/droplets.go @@ -17,6 +17,7 @@ var errNoNetworks = errors.New("no networks have been defined") // See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Droplets type DropletsService interface { List(context.Context, *ListOptions) ([]Droplet, *Response, error) + ListWithGPUs(context.Context, *ListOptions) ([]Droplet, *Response, error) ListByName(context.Context, string, *ListOptions) ([]Droplet, *Response, error) ListByTag(context.Context, string, *ListOptions) ([]Droplet, *Response, error) Get(context.Context, int) (*Droplet, *Response, error) @@ -321,6 +322,17 @@ func (s *DropletsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Dropl return s.list(ctx, path) } +// ListWithGPUs lists all Droplets with GPUs. +func (s *DropletsServiceOp) ListWithGPUs(ctx context.Context, opt *ListOptions) ([]Droplet, *Response, error) { + path := fmt.Sprintf("%s?type=gpus", dropletBasePath) + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + + return s.list(ctx, path) +} + // ListByName lists all Droplets filtered by name returning only exact matches. // It is case-insensitive func (s *DropletsServiceOp) ListByName(ctx context.Context, name string, opt *ListOptions) ([]Droplet, *Response, error) { diff --git a/droplets_test.go b/droplets_test.go index 17a62d75..ddd23f9e 100644 --- a/droplets_test.go +++ b/droplets_test.go @@ -44,6 +44,98 @@ func TestDroplets_ListDroplets(t *testing.T) { } } +func TestDroplets_ListDropletsWithGPUs(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/droplets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + if r.URL.Query().Get("type") != "gpus" { + t.Errorf("Droplets.ListWithGPUs did not request with a type parameter") + } + fmt.Fprint(w, `{ + "droplets": [ + { + "id": 1, + "size": { + "gpu_info": { + "count": 1, + "vram": { + "amount": 8, + "unit": "gib" + }, + "model": "nvidia_tesla_v100" + }, + "disk_info": [ + { + "type": "local", + "size": { + "amount": 200, + "unit": "gib" + } + }, + { + "type": "scratch", + "size": { + "amount": 40960, + "unit": "gib" + } + } + ] + } + } + ], + "meta": { + "total": 1 + } + }`) + }) + + droplets, resp, err := client.Droplets.ListWithGPUs(ctx, nil) + if err != nil { + t.Errorf("Droplets.List returned error: %v", err) + } + + expectedDroplets := []Droplet{ + { + ID: 1, + Size: &Size{ + GPUInfo: &GPUInfo{ + Count: 1, + VRAM: &VRAM{ + Amount: 8, + Unit: "gib", + }, + Model: "nvidia_tesla_v100", + }, + DiskInfo: []DiskInfo{ + { + Type: "local", + Size: &DiskSize{ + Amount: 200, + Unit: "gib", + }, + }, + { + Type: "scratch", + Size: &DiskSize{ + Amount: 40960, + Unit: "gib", + }, + }, + }, + }, + }, + } + if !reflect.DeepEqual(droplets, expectedDroplets) { + t.Errorf("Droplets.List\nDroplets: got=%#v\nwant=%#v", droplets, expectedDroplets) + } + expectedMeta := &Meta{Total: 1} + if !reflect.DeepEqual(resp.Meta, expectedMeta) { + t.Errorf("Droplets.List\nMeta: got=%#v\nwant=%#v", resp.Meta, expectedMeta) + } +} + func TestDroplets_ListDropletsByTag(t *testing.T) { setup() defer teardown()