diff --git a/droplets.go b/droplets.go index 5f19863..1ed09ec 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 17a62d7..ddd23f9 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() diff --git a/sizes.go b/sizes.go index a3cb745..72d5321 100644 --- a/sizes.go +++ b/sizes.go @@ -22,16 +22,44 @@ 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"` + 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"` +} + +// DiskSize provides information about the size of a disk. +type DiskSize struct { + Amount int `json:"amount,omitempty"` + 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 { diff --git a/sizes_test.go b/sizes_test.go index 784a88f..145ea16 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,51 @@ 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)", + GPUInfo: &GPUInfo{ + Count: 8, + VRAM: &VRAM{ + Amount: 640, + Unit: "gib", + }, + Model: "nvidia_h100", + }, + DiskInfo: []DiskInfo{ + { + Type: "local", + Size: &DiskSize{ + Amount: 200, + Unit: "gib", + }, + }, + { + Type: "scratch", + Size: &DiskSize{ + Amount: 40960, + Unit: "gib", + }, + }, + }, }, } @@ -55,7 +109,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 +133,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 +199,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) }