1.5.8-k8s-extnet-branch v1.3.1
stSolo 2 years ago
parent 84b64b7d80
commit 7d6cda7119

1
.gitignore vendored

@ -1 +1,2 @@
cmd/ cmd/
.idea/

@ -1,39 +1,33 @@
## Version 1.3.0 ## Version 1.3.1
### Features ### Features
- Created CloudAPI/CloudBroker filtering, sorting and serialization functions for List requests. - Added FilterByGID for cloudapi/locations/list handler response, used to filter locations by specified GID.
- Every handler with present List request has available FilterBy functions. Filtering by ID, Name is common for each handler. - Added /cloudbroker/pcidevices endpoints support
- In case user needs to filter response by uncommon field FilterFunc with user-specified predicate is also available. - /cloudbroker/pcidevices/create
- CloudAPI/CloudBroker computes, disks and lb also have specific Filter methods predefined, to name a few: - /cloudbroker/pcidevices/delete
- computes: - /cloudbroker/pcidevices/disable
- FilterByK8SID, used to filter computes used by specified k8s cluster; - /cloudbroker/pcidevices/enable
- FilterByK8SMasters, FilterByK8SWorkers, used to filter master/workers nodes. Best used after FilterByK8SID call; - /cloudbroker/pcidevices/list
- FilterByLBID, used to filter computes used by specified load balancer; - Added /cloudbroker/vgpu endpoints support
- /cloudbroker/vgpu/allocate
- disks: - /cloudbroker/vgpu/create
- FilterByK8SID, used to filter disks attached to computes inside specified k8s cluster; - /cloudbroker/vgpu/deallocate
- FilterByLBID, used to filter disks attached to computes inside specified load balancer; - /cloudbroker/vgpu/destroy
- /cloudbroker/vgpu/list
- lb:
- FilterByK8SID, used to filter load balancers used by specified k8s cluster;
- Reinvented request validation using go-validator. Made easier to manipulate and add on to.
- Request/Config validation now uses tags instead of hard-coded validation functions;
- Added ability to parse client configuration from JSON or YAML formatted files.
### Bug Fixes ### Bug Fixes
- Fixed SSO_URL trailing slash possibly breaking authentication process. - Fixed cloudbroker/cloudapi/account/update request model types.
- Fixed cloudbroker/vins/nat_rule_add request model types. - Fixed cloudbroker/cloudapi/rg/update request model types.
- Fixed cloudbroker/grid DiskSize field type - Fixed cloudapi/account DeactivationTime field type.
- Fixed TasksResult, InfoResult in cloudbroker/cloudapi/tasks/models JSON unmarshalling. - Fixed cloudapi/k8s/workersGroupAdd return value type.
- Fixed cloudapi/disks/listUnattached return value type.
- Added ListDisksUnattached model as a cloudapi/disks/listUnattached handler response with filters.
- Fixed cloudapi/extnet Excluded field type.
- Fixed cloudapi/rg RecordResourceUsage model.
- Fixed cloudapi/compute ItemACL model.
### Tests ### Tests
- Covered CloudAPI/CloudBroker filters with unit tests. - Covered cloudapi/disks ListDisksUnattached filters with unit tests.
### Other
- Updated module to new repository

@ -1,6 +1,9 @@
package validators package validators
import "github.com/go-playground/validator/v10" import (
"github.com/go-playground/validator/v10"
"regexp"
)
// protoValidator is used to validate Proto fields. // protoValidator is used to validate Proto fields.
func protoValidator(fe validator.FieldLevel) bool { func protoValidator(fe validator.FieldLevel) bool {
@ -203,3 +206,12 @@ func sepFieldTypeValidator(fe validator.FieldLevel) bool {
return StringInSlice(fieldValue, sepFieldTypeValues) return StringInSlice(fieldValue, sepFieldTypeValues)
} }
// hwPathValidator is used to validate HWPath field.
func hwPathValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String()
ok, _ := regexp.MatchString(`^\b[0-9a-f]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.\d{1}$`, fieldValue)
return ok
}

@ -187,6 +187,10 @@ func errorMessage(fe validator.FieldError) string {
fe.Field(), fe.Field(),
joinValues(sepFieldTypeValues)) joinValues(sepFieldTypeValues))
case "hwPath":
return fmt.Sprintf("%s %s must be in format 0000:1f:2b.0",
prefix,
fe.Field())
} }
return fe.Error() return fe.Error()

@ -159,5 +159,10 @@ func registerAllValidators(validate *validator.Validate) error {
return err return err
} }
err = validate.RegisterValidation("hwPath", hwPathValidator)
if err != nil {
return err
}
return nil return nil
} }

@ -165,7 +165,7 @@ type RecordAccount struct {
CreatedTime uint64 `json:"createdTime"` CreatedTime uint64 `json:"createdTime"`
// Deactivation time // Deactivation time
DeactivationTime uint64 `json:"deactivationTime"` DeactivationTime float64 `json:"deactivationTime"`
// Deleted by // Deleted by
DeletedBy string `json:"deletedBy"` DeletedBy string `json:"deletedBy"`

@ -20,23 +20,23 @@ type UpdateRequest struct {
// Max size of memory in MB // Max size of memory in MB
// Required: false // Required: false
MaxMemoryCapacity uint64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"` MaxMemoryCapacity int64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"`
// Max size of aggregated vdisks in GB // Max size of aggregated vdisks in GB
// Required: false // Required: false
MaxVDiskCapacity uint64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"` MaxVDiskCapacity int64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"`
// Max number of CPU cores // Max number of CPU cores
// Required: false // Required: false
MaxCPUCapacity uint64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"` MaxCPUCapacity int64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"`
// Max sent/received network transfer peering // Max sent/received network transfer peering
// Required: false // Required: false
MaxNetworkPeerTransfer uint64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"` MaxNetworkPeerTransfer int64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"`
// Max number of assigned public IPs // Max number of assigned public IPs
// Required: false // Required: false
MaxNumPublicIP uint64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"` MaxNumPublicIP int64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"`
// If true send emails when a user is granted access to resources // If true send emails when a user is granted access to resources
// Required: false // Required: false
@ -44,7 +44,7 @@ type UpdateRequest struct {
// Limit (positive) or disable (0) GPU resources // Limit (positive) or disable (0) GPU resources
// Required: false // Required: false
GPUUnits uint64 `url:"gpu_units,omitempty" json:"gpu_units,omitempty"` GPUUnits int64 `url:"gpu_units,omitempty" json:"gpu_units,omitempty"`
} }
// Update updates an account name and resource types and limits // Update updates an account name and resource types and limits

@ -4,7 +4,7 @@ import "testing"
var computes = ListComputes{ var computes = ListComputes{
ItemCompute{ ItemCompute{
ACL: []interface{}{}, ACL: ListACL{},
AccountID: 132847, AccountID: 132847,
AccountName: "std_2", AccountName: "std_2",
AffinityLabel: "", AffinityLabel: "",
@ -85,7 +85,7 @@ var computes = ListComputes{
VirtualImageID: 0, VirtualImageID: 0,
}, },
ItemCompute{ ItemCompute{
ACL: []interface{}{}, ACL: ListACL{},
AccountID: 132848, AccountID: 132848,
AccountName: "std_broker", AccountName: "std_broker",
AffinityLabel: "", AffinityLabel: "",
@ -229,7 +229,7 @@ func TestSortingByCreatedTime(t *testing.T) {
func TestSortingByCPU(t *testing.T) { func TestSortingByCPU(t *testing.T) {
actual := computes.SortByCPU(false) actual := computes.SortByCPU(false)
if actual[0].CPU != 4{ if actual[0].CPU != 4 {
t.Fatal("expected 4 CPU cores, found: ", actual[0].CPU) t.Fatal("expected 4 CPU cores, found: ", actual[0].CPU)
} }

@ -1,5 +1,7 @@
package compute package compute
import "strconv"
// Access Control List // Access Control List
type RecordACL struct { type RecordACL struct {
// Account ACL list // Account ACL list
@ -12,10 +14,27 @@ type RecordACL struct {
RGACL ListACL `json:"rgAcl"` RGACL ListACL `json:"rgAcl"`
} }
type Explicit bool
func (e *Explicit) UnmarshalJSON(b []byte) error {
if b[0] == '"' {
b = b[1 : len(b)-1]
}
res, err := strconv.ParseBool(string(b))
if err != nil {
return err
}
*e = Explicit(res)
return nil
}
// ACL information // ACL information
type ItemACL struct { type ItemACL struct {
// Explicit // Explicit
Explicit bool `json:"explicit"` Explicit Explicit `json:"explicit"`
// GUID // GUID
GUID string `json:"guid"` GUID string `json:"guid"`
@ -709,8 +728,7 @@ type IOTune struct {
// Main information about compute // Main information about compute
type ItemCompute struct { type ItemCompute struct {
// Access Control List // Access Control List
ACL []interface{} `json:"acl"` ACL ListACL `json:"acl"`
// Account ID // Account ID
AccountID uint64 `json:"accountId"` AccountID uint64 `json:"accountId"`

@ -130,3 +130,62 @@ func (ld ListDisks) FindOne() ItemDisk {
return ld[0] return ld[0]
} }
// FilterByID returns ListDisksUnattached with specified ID.
func (lu ListDisksUnattached) FilterByID(id uint64) ListDisksUnattached {
predicate := func(idisk ItemDiskUnattached) bool {
return idisk.ID == id
}
return lu.FilterFunc(predicate)
}
// FilterByName returns ListDisksUnattached with specified Name.
func (lu ListDisksUnattached) FilterByName(name string) ListDisksUnattached {
predicate := func(idisk ItemDiskUnattached) bool {
return idisk.Name == name
}
return lu.FilterFunc(predicate)
}
// FilterByStatus returns ListDisksUnattached with specified Status.
func (lu ListDisksUnattached) FilterByStatus(status string) ListDisksUnattached {
predicate := func(idisk ItemDiskUnattached) bool {
return idisk.Status == status
}
return lu.FilterFunc(predicate)
}
// FilterByTechStatus returns ListDisksUnattached with specified TechStatus.
func (lu ListDisksUnattached) FilterByTechStatus(techStatus string) ListDisksUnattached {
predicate := func(idisk ItemDiskUnattached) bool {
return idisk.TechStatus == techStatus
}
return lu.FilterFunc(predicate)
}
// FilterFunc allows filtering ListDisksUnattached based on a user-specified predicate.
func (lu ListDisksUnattached) FilterFunc(predicate func(ItemDiskUnattached) bool) ListDisksUnattached {
var result ListDisksUnattached
for _, item := range lu {
if predicate(item) {
result = append(result, item)
}
}
return result
}
// FindOne returns first found ItemDiskUnattached
// If none was found, returns an empty struct.
func (lu ListDisksUnattached) FindOne() ItemDiskUnattached {
if len(lu) == 0 {
return ItemDiskUnattached{}
}
return lu[0]
}

@ -1,6 +1,8 @@
package disks package disks
import "testing" import (
"testing"
)
var disks = ListDisks{ var disks = ListDisks{
ItemDisk{ ItemDisk{
@ -175,3 +177,198 @@ func TestSortByCreatedTime(t *testing.T) {
t.Fatal("expected ID 65193, found: ", actual[0].ID) t.Fatal("expected ID 65193, found: ", actual[0].ID)
} }
} }
var unattachedDisks = ListDisksUnattached{
{
CKey: "",
Meta: []interface{}{
"cloudbroker",
"disk",
1,
},
AccountID: 149,
AccountName: "test_account1",
ACL: map[string]interface{}{},
BootPartition: 0,
CreatedTime: 1681477547,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
DiskPath: "",
GID: 2002,
GUID: 22636,
ID: 22636,
ImageID: 0,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
},
IQN: "",
Login: "",
Milestones: 43834,
Name: "test_disk",
Order: 0,
Params: "",
ParentID: 0,
Password: "",
PCISlot: -1,
Pool: "data05",
PresentTo: []uint64{},
PurgeAttempts: 0,
PurgeTime: 0,
RealityDeviceNumber: 0,
ReferenceID: "",
ResID: "79bd3bd8-3424-48d3-963f-1870d506f169",
ResName: "volumes/volume_22636",
Role: "",
SEPID: 1,
Shareable: false,
SizeMax: 0,
SizeUsed: 0,
Snapshots: nil,
Status: "CREATED",
TechStatus: "ALLOCATED",
Type: "D",
VMID: 0,
},
{
CKey: "",
Meta: []interface{}{
"cloudbroker",
"disk",
1,
},
AccountID: 150,
AccountName: "test_account",
ACL: map[string]interface{}{},
BootPartition: 0,
CreatedTime: 1681477558,
DeletedTime: 0,
Description: "",
DestructionTime: 0,
DiskPath: "",
GID: 2002,
GUID: 22637,
ID: 22637,
ImageID: 0,
Images: []uint64{},
IOTune: IOTune{
TotalIOPSSec: 2000,
},
IQN: "",
Login: "",
Milestones: 43834,
Name: "test_disk",
Order: 0,
Params: "",
ParentID: 0,
Password: "",
PCISlot: -1,
Pool: "data05",
PresentTo: []uint64{
27,
27,
},
PurgeAttempts: 0,
PurgeTime: 0,
RealityDeviceNumber: 0,
ReferenceID: "",
ResID: "79bd3bd8-3424-48d3-963f-1870d506f169",
ResName: "volumes/volume_22637",
Role: "",
SEPID: 1,
Shareable: false,
SizeMax: 0,
SizeUsed: 0,
Snapshots: nil,
Status: "CREATED",
TechStatus: "ALLOCATED",
Type: "B",
VMID: 0,
},
}
func TestListDisksUnattached_FilterByID(t *testing.T) {
actual := unattachedDisks.FilterByID(22636)
if len(actual) == 0 {
t.Fatal("No elements were found")
}
actualItem := actual.FindOne()
if actualItem.ID != 22636 {
t.Fatal("expected ID 22636, found: ", actualItem.ID)
}
}
func TestListDisksUnattached_FilterByName(t *testing.T) {
actual := unattachedDisks.FilterByName("test_disk")
if len(actual) != 2 {
t.Fatal("expected 2 elements, found: ", len(actual))
}
for _, item := range actual {
if item.Name != "test_disk" {
t.Fatal("expected 'test_disk' name, found: ", item.Name)
}
}
}
func TestListDisksUnattached_FilterByStatus(t *testing.T) {
actual := unattachedDisks.FilterByStatus("CREATED")
if len(actual) == 0 {
t.Fatal("No elements were found")
}
for _, item := range actual {
if item.Status != "CREATED" {
t.Fatal("expected 'CREATED' status, found: ", item.Status)
}
}
}
func TestListDisksUnattached_FilterByTechStatus(t *testing.T) {
actual := unattachedDisks.FilterByTechStatus("ALLOCATED")
if len(actual) == 0 {
t.Fatal("No elements were found")
}
for _, item := range actual {
if item.TechStatus != "ALLOCATED" {
t.Fatal("expected 'ALLOCATED' techStatus, found: ", item.TechStatus)
}
}
}
func TestListDisksUnattached_FilterFunc(t *testing.T) {
actual := unattachedDisks.FilterFunc(func(id ItemDiskUnattached) bool {
return len(id.PresentTo) == 2
})
if len(actual) == 0 {
t.Fatal("No elements were found")
}
if len(actual[0].PresentTo) != 2 {
t.Fatal("expected 2 elements in PresentTo, found: ", len(actual[0].PresentTo))
}
}
func TestListDisksUnattached_SortByCreatedTime(t *testing.T) {
actual := unattachedDisks.SortByCreatedTime(false)
if actual[0].ID != 22636 {
t.Fatal("expected ID 22636, found: ", actual[0].ID)
}
actual = unattachedDisks.SortByCreatedTime(true)
if actual[0].ID != 22637 {
t.Fatal("expected ID 22637, found: ", actual[0].ID)
}
}

@ -14,7 +14,7 @@ type ListUnattachedRequest struct {
} }
// ListUnattached gets list of unattached disks // ListUnattached gets list of unattached disks
func (d Disks) ListUnattached(ctx context.Context, req ListUnattachedRequest) (ListDisks, error) { func (d Disks) ListUnattached(ctx context.Context, req ListUnattachedRequest) (ListDisksUnattached, error) {
url := "/cloudapi/disks/listUnattached" url := "/cloudapi/disks/listUnattached"
res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req) res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
@ -22,7 +22,7 @@ func (d Disks) ListUnattached(ctx context.Context, req ListUnattachedRequest) (L
return nil, err return nil, err
} }
list := ListDisks{} list := ListDisksUnattached{}
err = json.Unmarshal(res, &list) err = json.Unmarshal(res, &list)
if err != nil { if err != nil {

@ -114,9 +114,146 @@ type ItemDisk struct {
VMID uint64 `json:"vmid"` VMID uint64 `json:"vmid"`
} }
type ItemDiskUnattached struct {
// CKey
CKey string `json:"_ckey"`
// Meta
Meta []interface{} `json:"_meta"`
// Account ID
AccountID uint64 `json:"accountId"`
// Account name
AccountName string `json:"accountName"`
// Access Control List
ACL map[string]interface{} `json:"acl"`
// Boot Partition
BootPartition uint64 `json:"bootPartition"`
// Created time
CreatedTime uint64 `json:"createdTime"`
// Deleted time
DeletedTime uint64 `json:"deletedTime"`
// Description
Description string `json:"desc"`
// Destruction time
DestructionTime uint64 `json:"destructionTime"`
// Disk path
DiskPath string `json:"diskPath"`
// Grid ID
GID uint64 `json:"gid"`
// GUID
GUID uint64 `json:"guid"`
// ID
ID uint64 `json:"id"`
// Image ID
ImageID uint64 `json:"imageId"`
// Images
Images []uint64 `json:"images"`
// IOTune
IOTune IOTune `json:"iotune"`
// IQN
IQN string `json:"iqn"`
// Login
Login string `json:"login"`
// Milestones
Milestones uint64 `json:"milestones"`
// Name
Name string `json:"name"`
// Order
Order uint64 `json:"order"`
// Params
Params string `json:"params"`
// Parent ID
ParentID uint64 `json:"parentId"`
// Password
Password string `json:"passwd"`
//PCISlot
PCISlot int64 `json:"pciSlot"`
// Pool
Pool string `json:"pool"`
// Present to
PresentTo []uint64 `json:"presentTo"`
// Purge attempts
PurgeAttempts uint64 `json:"purgeAttempts"`
// Purge time
PurgeTime uint64 `json:"purgeTime"`
// Reality device number
RealityDeviceNumber uint64 `json:"realityDeviceNumber"`
// Reference ID
ReferenceID string `json:"referenceId"`
// Resource ID
ResID string `json:"resId"`
// Resource name
ResName string `json:"resName"`
// Role
Role string `json:"role"`
// ID SEP
SEPID uint64 `json:"sepId"`
// Shareable
Shareable bool `json:"shareable"`
// Size max
SizeMax uint64 `json:"sizeMax"`
// Size used
SizeUsed float64 `json:"sizeUsed"`
// List of snapshots
Snapshots ListSnapshots `json:"snapshots"`
// Status
Status string `json:"status"`
// Tech status
TechStatus string `json:"techStatus"`
// Type
Type string `json:"type"`
// Virtual machine ID
VMID uint64 `json:"vmid"`
}
// List of disks // List of disks
type ListDisks []ItemDisk type ListDisks []ItemDisk
// List of unattached disks
type ListDisksUnattached []ItemDiskUnattached
// Main information about snapshot // Main information about snapshot
type ItemSnapshot struct { type ItemSnapshot struct {
// GUID // GUID

@ -41,3 +41,39 @@ func (idisk ItemDisk) Serialize(params ...string) (serialization.Serialized, err
return json.Marshal(idisk) return json.Marshal(idisk)
} }
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (lu ListDisksUnattached) Serialize(params ...string) (serialization.Serialized, error) {
if len(lu) == 0 {
return []byte{}, nil
}
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(lu, prefix, indent)
}
return json.Marshal(lu)
}
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (idisk ItemDiskUnattached) Serialize(params ...string) (serialization.Serialized, error) {
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(idisk, prefix, indent)
}
return json.Marshal(idisk)
}

@ -58,3 +58,60 @@ func (ld ListDisks) SortByDeletedTime(inverse bool) ListDisks {
return ld return ld
} }
// SortByCreatedTime sorts ListDisksUnattached by the CreatedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByCreatedTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 {
return lu
}
sort.Slice(lu, func(i, j int) bool {
if inverse {
return lu[i].CreatedTime > lu[j].CreatedTime
}
return lu[i].CreatedTime < lu[j].CreatedTime
})
return lu
}
// SortByDestructionTime sorts ListDisksUnattached by the DestructionTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByDestructionTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 {
return lu
}
sort.Slice(lu, func(i, j int) bool {
if inverse {
return lu[i].DestructionTime > lu[j].DestructionTime
}
return lu[i].DestructionTime < lu[j].DestructionTime
})
return lu
}
// SortByDeletedTime sorts ListDisksUnattached by the DeletedTime field in ascending order.
//
// If inverse param is set to true, the order is reversed.
func (lu ListDisksUnattached) SortByDeletedTime(inverse bool) ListDisksUnattached {
if len(lu) < 2 {
return lu
}
sort.Slice(lu, func(i, j int) bool {
if inverse {
return lu[i].DeletedTime > lu[j].DeletedTime
}
return lu[i].DeletedTime < lu[j].DeletedTime
})
return lu
}

@ -107,6 +107,23 @@ type VNFs struct {
DHCP uint64 `json:"dhcp"` DHCP uint64 `json:"dhcp"`
} }
type Excluded struct {
// ClientType
ClientType string `json:"clientType"`
// IP
IP string `json:"ip"`
// MAC
MAC string `json:"mac"`
// Type
Type string `json:"type"`
// VMID
VMID uint64 `json:"vmId"`
}
// Detailed information about external network // Detailed information about external network
type RecordExtNet struct { type RecordExtNet struct {
// CKey // CKey
@ -134,7 +151,7 @@ type RecordExtNet struct {
DNS []string `json:"dns"` DNS []string `json:"dns"`
// Excluded // Excluded
Excluded []string `json:"excluded"` Excluded []Excluded `json:"excluded"`
// Free IPs // Free IPs
FreeIPs uint64 `json:"free_ips"` FreeIPs uint64 `json:"free_ips"`

@ -59,11 +59,11 @@ type WorkersGroupAddRequest struct {
} }
// WorkersGroupAdd adds workers group to Kubernetes cluster // WorkersGroupAdd adds workers group to Kubernetes cluster
func (k8s K8S) WorkersGroupAdd(ctx context.Context, req WorkersGroupAddRequest) (bool, error) { func (k8s K8S) WorkersGroupAdd(ctx context.Context, req WorkersGroupAddRequest) (uint64, error) {
err := validators.ValidateRequest(req) err := validators.ValidateRequest(req)
if err != nil { if err != nil {
for _, validationError := range validators.GetErrors(err) { for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError) return 0, validators.ValidationError(validationError)
} }
} }
@ -71,12 +71,12 @@ func (k8s K8S) WorkersGroupAdd(ctx context.Context, req WorkersGroupAddRequest)
res, err := k8s.client.DecortApiCall(ctx, http.MethodPost, url, req) res, err := k8s.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil { if err != nil {
return false, err return 0, err
} }
result, err := strconv.ParseBool(string(res)) result, err := strconv.ParseUint(string(res), 10, 64)
if err != nil { if err != nil {
return false, err return 0, err
} }
return result, nil return result, nil

@ -18,6 +18,15 @@ func (ll ListLocations) FilterByName(name string) ListLocations {
return ll.FilterFunc(predicate) return ll.FilterFunc(predicate)
} }
// FilterByGID returns ListLocations with specified GID.
func (ll ListLocations) FilterByGID(gid uint64) ListLocations {
predicate := func(il ItemLocation) bool {
return il.GID == gid
}
return ll.FilterFunc(predicate)
}
// FilterFunc allows filtering ListLocations based on a user-specified predicate. // FilterFunc allows filtering ListLocations based on a user-specified predicate.
func (ll ListLocations) FilterFunc(predicate func(ItemLocation) bool) ListLocations { func (ll ListLocations) FilterFunc(predicate func(ItemLocation) bool) ListLocations {
var result ListLocations var result ListLocations

@ -735,7 +735,10 @@ type RecordResourceUsage struct {
CPU uint64 `json:"cpu"` CPU uint64 `json:"cpu"`
// Disk size // Disk size
DiskSize uint64 `json:"disksize"` DiskSize float64 `json:"disksize"`
// Max disk size
DiskSizeMax uint64 `json:"disksizemax"`
// Number of external IPs // Number of external IPs
ExtIPs uint64 `json:"extips"` ExtIPs uint64 `json:"extips"`
@ -748,4 +751,7 @@ type RecordResourceUsage struct {
// Number of RAM // Number of RAM
RAM uint64 `json:"ram"` RAM uint64 `json:"ram"`
// SEPs
SEPs map[string]map[string]DiskUsage `json:"seps"`
} }

@ -24,23 +24,23 @@ type UpdateRequest struct {
// Max size of memory in MB // Max size of memory in MB
// Required: false // Required: false
MaxMemoryCapacity uint64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"` MaxMemoryCapacity int64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"`
// Max size of aggregated virtual disks in GB // Max size of aggregated virtual disks in GB
// Required: false // Required: false
MaxVDiskCapacity uint64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"` MaxVDiskCapacity int64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"`
// Max number of CPU cores // Max number of CPU cores
// Required: false // Required: false
MaxCPUCapacity uint64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"` MaxCPUCapacity int64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"`
// Max sent/received network transfer peering // Max sent/received network transfer peering
// Required: false // Required: false
MaxNetworkPeerTransfer uint64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"` MaxNetworkPeerTransfer int64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"`
// Max number of assigned public IPs // Max number of assigned public IPs
// Required: false // Required: false
MaxNumPublicIP uint64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"` MaxNumPublicIP int64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"`
// Register computes in registration system // Register computes in registration system
// Required: false // Required: false

@ -15,8 +15,8 @@ type UpdateRequest struct {
AccountID uint64 `url:"accountId" json:"accountId" validate:"required"` AccountID uint64 `url:"accountId" json:"accountId" validate:"required"`
// Display name // Display name
// Required: true // Required: false
Name string `url:"name" json:"name" validate:"required"` Name string `url:"name" json:"name"`
// Name of the account // Name of the account
// Required: true // Required: true
@ -28,23 +28,23 @@ type UpdateRequest struct {
// Max size of memory in MB // Max size of memory in MB
// Required: false // Required: false
MaxMemoryCapacity uint64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"` MaxMemoryCapacity int64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"`
// Max size of aggregated vdisks in GB // Max size of aggregated vdisks in GB
// Required: false // Required: false
MaxVDiskCapacity uint64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"` MaxVDiskCapacity int64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"`
// Max number of CPU cores // Max number of CPU cores
// Required: false // Required: false
MaxCPUCapacity uint64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"` MaxCPUCapacity int64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"`
// Max sent/received network transfer peering // Max sent/received network transfer peering
// Required: false // Required: false
MaxNetworkPeerTransfer uint64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"` MaxNetworkPeerTransfer int64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"`
// Max number of assigned public IPs // Max number of assigned public IPs
// Required: false // Required: false
MaxNumPublicIP uint64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"` MaxNumPublicIP int64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"`
// If true send emails when a user is granted access to resources // If true send emails when a user is granted access to resources
// Required: false // Required: false
@ -52,7 +52,7 @@ type UpdateRequest struct {
// Limit (positive) or disable (0) GPU resources // Limit (positive) or disable (0) GPU resources
// Required: false // Required: false
GPUUnits uint64 `url:"gpu_units,omitempty" json:"gpu_units,omitempty"` GPUUnits int64 `url:"gpu_units,omitempty" json:"gpu_units,omitempty"`
// List of strings with pools // List of strings with pools
// i.e.: ["sep1_poolName1", "sep2_poolName2", etc] // i.e.: ["sep1_poolName1", "sep2_poolName2", etc]

@ -0,0 +1,8 @@
package cloudbroker
import "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/pcidevice"
// Accessing the PCI Device method group
func (cb *CloudBroker) PCIDevice() *pcidevice.PCIDevice {
return pcidevice.New(cb.client)
}

@ -0,0 +1,56 @@
package pcidevice
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request struct for creating PCI device
type CreateRequest struct {
// StackID
// Required: true
StackID uint64 `url:"stackId" json:"stackId" validate:"required"`
// Resource group ID
// Required: true
RGID uint64 `url:"rgId" json:"rgId" validate:"required"`
// Name of device
// Required: true
Name string `url:"name" json:"name" validate:"required"`
// PCI address of the device
// Must be in format 0000:1f:2b.0
// Required: true
HWPath string `url:"hwPath" json:"hwPath" validate:"required,hwPath"`
// Description, just for information
// Required: false
Description string `url:"description,omitempty" json:"description,omitempty"`
}
// Create creates PCI Device
func (p PCIDevice) Create(ctx context.Context, req CreateRequest) (uint64, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return 0, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/pcidevice/create"
res, err := p.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return 0, err
}
result, err := strconv.ParseUint(string(res), 10, 64)
if err != nil {
return 0, err
}
return result, nil
}

@ -0,0 +1,43 @@
package pcidevice
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request struct for deleting PCI device
type DeleteRequest struct {
// PCI device ID
// Required: true
DeviceID uint64 `url:"deviceId" json:"deviceId" validate:"required"`
// Force delete
// Required: false
Force bool `url:"force,omitempty" json:"force,omitempty"`
}
// Delete PCI device
func (p PCIDevice) Delete(ctx context.Context, req DeleteRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/pcidevice/delete"
res, err := p.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

@ -0,0 +1,43 @@
package pcidevice
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request struct for disabling PCI device
type DisableRequest struct {
// PCI device ID
// Required: true
DeviceID uint64 `url:"deviceId" json:"deviceId" validate:"required"`
// Force delete
// Required: false
Force bool `url:"force,omitempty" json:"force,omitempty"`
}
// Disable PCI device
func (p PCIDevice) Disable(ctx context.Context, req DisableRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/pcidevice/disable"
res, err := p.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

@ -0,0 +1,39 @@
package pcidevice
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request struct for enabling PCI device
type EnableRequest struct {
// PCI device ID
// Required: true
DeviceID uint64 `url:"deviceId" json:"deviceId" validate:"required"`
}
// Enable PCI device
func (p PCIDevice) Enable(ctx context.Context, req EnableRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/pcidevice/enable"
res, err := p.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

@ -0,0 +1,26 @@
package pcidevice
import (
"context"
"encoding/json"
"net/http"
)
// List gets list all pci devices
func (p PCIDevice) List(ctx context.Context) (ListPCIDevices, error) {
url := "/cloudbroker/pcidevice/list"
res, err := p.client.DecortApiCall(ctx, http.MethodPost, url, nil)
if err != nil {
return nil, err
}
list := ListPCIDevices{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
}

@ -0,0 +1,43 @@
package pcidevice
// Main information about PCI device
type ItemPCIDevice struct {
// CKey
CKey string `json:"_ckey"`
// Meta
Meta []interface{} `json:"_meta"`
// Compute ID
ComputeID uint64 `json:"computeId"`
// Description
Description string `json:"description"`
// GUID
GUID uint64 `json:"guid"`
// HwPath
HwPath string `json:"hwPath"`
// ID
ID uint64 `json:"id"`
// Name
Name string `json:"name"`
// Resource group ID
RGID uint64 `json:"rgId"`
// Stack ID
StackID uint64 `json:"stackId"`
// Status
Status string `json:"status"`
// System name
SystemName string `json:"systemName"`
}
// List PCI devices
type ListPCIDevices []ItemPCIDevice

@ -0,0 +1,15 @@
package pcidevice
import "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
// Structure for creating request to PCI device
type PCIDevice struct {
client interfaces.Caller
}
// Builder for PCI device endpoints
func New(client interfaces.Caller) *PCIDevice {
return &PCIDevice{
client: client,
}
}

@ -0,0 +1,42 @@
package pcidevice
import (
"encoding/json"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/serialization"
)
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (l ListPCIDevices) Serialize(params ...string) (serialization.Serialized, error) {
if len(l) == 0 {
return []byte{}, nil
}
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(l, prefix, indent)
}
return json.Marshal(l)
}
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (i ItemPCIDevice) Serialize(params ...string) (serialization.Serialized, error) {
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(i, prefix, indent)
}
return json.Marshal(i)
}

@ -24,23 +24,23 @@ type UpdateRequest struct {
// Max size of memory in MB // Max size of memory in MB
// Required: false // Required: false
MaxMemoryCapacity uint64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"` MaxMemoryCapacity int64 `url:"maxMemoryCapacity,omitempty" json:"maxMemoryCapacity,omitempty"`
// Max size of aggregated virtual disks in GB // Max size of aggregated virtual disks in GB
// Required: false // Required: false
MaxVDiskCapacity uint64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"` MaxVDiskCapacity int64 `url:"maxVDiskCapacity,omitempty" json:"maxVDiskCapacity,omitempty"`
// Max number of CPU cores // Max number of CPU cores
// Required: false // Required: false
MaxCPUCapacity uint64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"` MaxCPUCapacity int64 `url:"maxCPUCapacity,omitempty" json:"maxCPUCapacity,omitempty"`
// Max sent/received network transfer peering // Max sent/received network transfer peering
// Required: false // Required: false
MaxNetworkPeerTransfer uint64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"` MaxNetworkPeerTransfer int64 `url:"maxNetworkPeerTransfer,omitempty" json:"maxNetworkPeerTransfer,omitempty"`
// Max number of assigned public IPs // Max number of assigned public IPs
// Required: false // Required: false
MaxNumPublicIP uint64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"` MaxNumPublicIP int64 `url:"maxNumPublicIP,omitempty" json:"maxNumPublicIP,omitempty"`
// Register computes in registration system // Register computes in registration system
// Required: false // Required: false

@ -0,0 +1,8 @@
package cloudbroker
import "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vgpu"
// Accessing the VGPU method group
func (cb *CloudBroker) VGPU() *vgpu.VGPU {
return vgpu.New(cb.client)
}

@ -0,0 +1,39 @@
package vgpu
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request for allocating VGPU
type AllocateRequest struct {
// Virtual GPU ID
// Required: true
VGPUID uint64 `url:"vgpuId" json:"vgpuId" validate:"required"`
}
// Allocate allocates GPU
func (v VGPU) Allocate(ctx context.Context, req AllocateRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/vgpu/allocate"
res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

@ -0,0 +1,51 @@
package vgpu
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request struct for creating VGPU
type CreateRequest struct {
// ID of pGPU
// Required: true
PGPUID uint64 `url:"pgpuId" json:"pgpuId" validate:"required"`
// ID of the target resource group.
// Required: true
RGID uint64 `url:"rgId" json:"rgId" validate:"required"`
// Virtual profile id
// Required: false
ProfileID uint64 `url:"profileId,omitempty" json:"profileId,omitempty"`
// Allocate vgpu after creation
// Required: false
Allocate bool `url:"allocate,omitempty" json:"allocate,omitempty"`
}
// Create creates VGPU
func (v VGPU) Create(ctx context.Context, req CreateRequest) (uint64, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return 0, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/vgpu/create"
res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return 0, err
}
result, err := strconv.ParseUint(string(res), 10, 64)
if err != nil {
return 0, err
}
return result, nil
}

@ -0,0 +1,43 @@
package vgpu
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request for deallocating VGPU
type DeallocateRequest struct {
// Virtual GPU ID
// Required: true
VGPUID uint64 `url:"vgpuId" json:"vgpuId" validate:"required"`
// Force delete (detach from compute)
// Required: false
Force bool `url:"force,omitempty" json:"force,omitempty"`
}
// Deallocate releases GPU resources
func (v VGPU) Deallocate(ctx context.Context, req DeallocateRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/vgpu/deallocate"
res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

@ -0,0 +1,43 @@
package vgpu
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
"strconv"
)
// Request for destroying VGPU
type DestroyRequest struct {
// Virtual GPU ID
// Required: true
VGPUID uint64 `url:"vgpuId" json:"vgpuId" validate:"required"`
// Force delete (deallocate and detach from compute)
// Required: false
Force bool `url:"force,omitempty" json:"force,omitempty"`
}
// Destroy destroys VGPU
func (v VGPU) Destroy(ctx context.Context, req DestroyRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
for _, validationError := range validators.GetErrors(err) {
return false, validators.ValidationError(validationError)
}
}
url := "/cloudbroker/vgpu/destroy"
res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

@ -0,0 +1,37 @@
package vgpu
import (
"context"
"encoding/json"
"net/http"
)
// Request struct for getting list of VGPU
type ListRequest struct {
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
}
// List gets list all VGPU
func (v VGPU) List(ctx context.Context, req ListRequest) (ListVGPU, error) {
url := "/cloudbroker/vgpu/list"
res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return nil, err
}
list := ListVGPU{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
}

@ -0,0 +1,66 @@
package vgpu
type ItemVGPU struct {
// CKey
CKey string `json:"_ckey"`
// Meta
Meta []interface{} `json:"_meta"`
// Account ID
AccountID uint64 `json:"accountId"`
// Created time
CreatedTime uint64 `json:"createdTime"`
// Deleted time
DeletedTime uint64 `json:"deletedTime"`
//Grid ID
GID uint64 `json:"gid"`
// GUID
GUID uint64 `json:"guid"`
// VGPU ID
ID uint64 `json:"id"`
// Last claimed by
LastClaimedBy uint64 `json:"lastClaimedBy"`
// Last update time
LastUpdateTime uint64 `json:"lastUpdateTime"`
// Mode
Mode string `json:"mode"`
// PCI Slot
PCISlot interface{} `json:"pciSlot"`
// PGPUID
PGPUID uint64 `json:"pgpuid"`
// Profile ID
ProfileID interface{} `json:"profileId"`
// RAM
RAM uint64 `json:"ram"`
// Reference ID
ReferenceID interface{} `json:"referenceId"`
// RGID
RGID uint64 `json:"rgId"`
// Status
Status string `json:"status"`
// Type
Type string `json:"type"`
// VMID
VMID uint64 `json:"vmid"`
}
// List of VGPU
type ListVGPU []ItemVGPU

@ -0,0 +1,42 @@
package vgpu
import (
"encoding/json"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/serialization"
)
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (l ListVGPU) Serialize(params ...string) (serialization.Serialized, error) {
if len(l) == 0 {
return []byte{}, nil
}
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(l, prefix, indent)
}
return json.Marshal(l)
}
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (i ItemVGPU) Serialize(params ...string) (serialization.Serialized, error) {
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(i, prefix, indent)
}
return json.Marshal(i)
}

@ -0,0 +1,15 @@
package vgpu
import "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
// Structure for creating request to VGPU
type VGPU struct {
client interfaces.Caller
}
// Builder for VGPU endpoints
func New(client interfaces.Caller) *VGPU {
return &VGPU{
client: client,
}
}
Loading…
Cancel
Save