Skip to content

Commit

Permalink
Add ai cluster support (#83)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Denezhkin <idenezhkin@itkey.com>
  • Loading branch information
pikwick and pikwick authored Oct 3, 2023
1 parent 57e8df8 commit 8e62c87
Show file tree
Hide file tree
Showing 26 changed files with 4,823 additions and 11 deletions.
1,221 changes: 1,221 additions & 0 deletions client/ais/v1/ais/ias.go

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions client/ais/v1/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package client

import (
gcorecloud "github.com/G-Core/gcorelabscloud-go"
"github.com/G-Core/gcorelabscloud-go/client/common"

"github.com/urfave/cli/v2"
)

func NewAIClusterClientV1(c *cli.Context) (*gcorecloud.ServiceClient, error) {
return common.BuildClient(c, "ai/clusters", "v1")
}

func NewAIImageClientV1(c *cli.Context) (*gcorecloud.ServiceClient, error) {
return common.BuildClient(c, "ai/images", "v1")
}
func NewAIFlavorClientV1(c *cli.Context) (*gcorecloud.ServiceClient, error) {
return common.BuildClient(c, "ai/flavors", "v1")
}




12 changes: 12 additions & 0 deletions client/ais/v2/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package client

import (
gcorecloud "github.com/G-Core/gcorelabscloud-go"
"github.com/G-Core/gcorelabscloud-go/client/common"

"github.com/urfave/cli/v2"
)

func NewAIClusterClientV2(c *cli.Context) (*gcorecloud.ServiceClient, error) {
return common.BuildClient(c, "ai/clusters", "v2")
}
18 changes: 9 additions & 9 deletions client/instances/v1/instances/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func StringSliceToAppConfigSetOpts(slice []string) (map[string]interface{}, erro
return m, nil
}

func getUserData(c *cli.Context) (string, error) {
func GetUserData(c *cli.Context) (string, error) {
userData := ""
userDataFile := c.String("user-data-file")
userDataContent := c.String("user-data")
Expand All @@ -126,7 +126,7 @@ func getUserData(c *cli.Context) (string, error) {
return userData, nil
}

func getInstanceVolumes(c *cli.Context) ([]instances.CreateVolumeOpts, error) {
func GetInstanceVolumes(c *cli.Context) ([]instances.CreateVolumeOpts, error) {
volumeSources := utils.GetEnumStringSliceValue(c, "volume-source")
volumeTypes := utils.GetEnumStringSliceValue(c, "volume-type")
volumeBootIndexes := c.IntSlice("volume-boot-index")
Expand Down Expand Up @@ -198,7 +198,7 @@ func getInstanceVolumes(c *cli.Context) ([]instances.CreateVolumeOpts, error) {

}

func getInterfaces(c *cli.Context) ([]instances.InterfaceInstanceCreateOpts, error) {
func GetInterfaces(c *cli.Context) ([]instances.InterfaceInstanceCreateOpts, error) {
interfaceTypes := utils.GetEnumStringSliceValue(c, "interface-type")
interfaceNetworkIDs := c.StringSlice("interface-network-id")
interfaceSubnetIDs := c.StringSlice("interface-subnet-id")
Expand Down Expand Up @@ -284,7 +284,7 @@ func getBaremetalInterfaces(c *cli.Context) ([]bminstances.InterfaceOpts, error)

}

func getSecurityGroups(c *cli.Context) []gcorecloud.ItemID {
func GetSecurityGroups(c *cli.Context) []gcorecloud.ItemID {
securityGroups := c.StringSlice("security-group")
res := make([]gcorecloud.ItemID, len(securityGroups))
for i, s := range securityGroups {
Expand Down Expand Up @@ -681,26 +681,26 @@ var instanceCreateCommandV2 = cli.Command{
return cli.NewExitError(err, 1)
}

userData, err := getUserData(c)
userData, err := GetUserData(c)
if err != nil {
_ = cli.ShowCommandHelp(c, "create")
return cli.NewExitError(err, 1)
}

instanceVolumes, err := getInstanceVolumes(c)
instanceVolumes, err := GetInstanceVolumes(c)
if err != nil {
_ = cli.ShowCommandHelp(c, "create")
return cli.NewExitError(err, 1)
}

// todo add security group mapping
instanceInterfaces, err := getInterfaces(c)
instanceInterfaces, err := GetInterfaces(c)
if err != nil {
_ = cli.ShowCommandHelp(c, "create")
return cli.NewExitError(err, 1)
}

securityGroups := getSecurityGroups(c)
securityGroups := GetSecurityGroups(c)

metadata, err := StringSliceToMetadataSetOpts(c.StringSlice("metadata"))
if err != nil {
Expand Down Expand Up @@ -1290,7 +1290,7 @@ var instanceCreateBaremetalCommand = cli.Command{
return cli.NewExitError(err, 1)
}

userData, err := getUserData(c)
userData, err := GetUserData(c)
if err != nil {
_ = cli.ShowCommandHelp(c, "create_baremetal")
return cli.NewExitError(err, 1)
Expand Down
1 change: 0 additions & 1 deletion client/loadbalancers/v1/listeners/lsiteners.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ var listenerCreateSubCommand = cli.Command{
AllowedCIDRS: c.StringSlice("allowed-cidrs") ,
}


results, err := listeners.Create(client, opts).Extract()
if err != nil {
return cli.NewExitError(err, 1)
Expand Down
2 changes: 2 additions & 0 deletions cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/G-Core/gcorelabscloud-go/client/apitokens/v1/apitokens"
"github.com/G-Core/gcorelabscloud-go/client/apptemplates/v1/apptemplates"
"github.com/G-Core/gcorelabscloud-go/client/file_shares/v1/file_shares"
"github.com/G-Core/gcorelabscloud-go/client/ais/v1/ais"
"github.com/G-Core/gcorelabscloud-go/client/flags"
"github.com/G-Core/gcorelabscloud-go/client/flavors/v1/flavors"
"github.com/G-Core/gcorelabscloud-go/client/floatingips/v1/floatingips"
Expand Down Expand Up @@ -75,6 +76,7 @@ var commands = []*cli.Command{
&apptemplates.Commands,
&apitokens.Commands,
&file_shares.Commands,
&ais.Commands,
}

type clientCommands struct {
Expand Down
51 changes: 51 additions & 0 deletions gcore/ai/v1/aiflavors/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package aiflavors

import (
gcorecloud "github.com/G-Core/gcorelabscloud-go"
"github.com/G-Core/gcorelabscloud-go/pagination"
)


type ListOptsBuilder interface {
ToAIFlavorListQuery() (string, error)
}

type AIFlavorListOpts struct {
Disabled bool `q:"disabled"`
IncludeCapacity bool `q:"include_capacity"`
IncludePrices bool `q:"include_prices"`
}

// ToAIFlavorListQuery formats a AIFlavorListOpts into a query string.
func (opts AIFlavorListOpts) ToAIFlavorListQuery() (string, error) {
q, err := gcorecloud.BuildQueryString(opts)
if err != nil {
return "", err
}
return q.String(), err
}


// List retrieves list of AI flavors
func List(c *gcorecloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listAIFlavorsURL(c)
if opts != nil {
query, err := opts.ToAIFlavorListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return AIFlavorPage{pagination.LinkedPageBase{PageResult: r}}
})
}

// ListAll retrieves list of all AI flavors
func ListAll(c *gcorecloud.ServiceClient, opts ListOptsBuilder) ([]AIFlavor, error) {
results, err := List(c, opts).AllPages()
if err != nil {
return nil, err
}
return ExtractAIFlavors(results)
}
91 changes: 91 additions & 0 deletions gcore/ai/v1/aiflavors/results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package aiflavors

import (
gcorecloud "github.com/G-Core/gcorelabscloud-go"
"github.com/G-Core/gcorelabscloud-go/pagination"
"github.com/shopspring/decimal"
)

type commonResult struct {
gcorecloud.Result
}

// Extract is a function that accepts a result and extracts a instance resource.
func (r commonResult) Extract() (*AIFlavor, error) {
var s AIFlavor
err := r.ExtractInto(&s)
return &s, err
}

func (r commonResult) ExtractInto(v interface{}) error {
return r.Result.ExtractIntoStructPtr(v, "")
}

// AIFlavorPage is the page returned by a pager when traversing over a
// collection of instances.
type AIFlavorPage struct {
pagination.LinkedPageBase
}

// NextPageURL is invoked when a paginated collection of flavors has reached
// the end of a page and the pager seeks to traverse over a new one. In order
// to do this, it needs to construct the next page's URL.
func (r AIFlavorPage) NextPageURL() (string, error) {
var s struct {
Links []gcorecloud.Link `json:"links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gcorecloud.ExtractNextURL(s.Links)
}

// IsEmpty checks whether a FlavorPage struct is empty.
func (r AIFlavorPage) IsEmpty() (bool, error) {
is, err := ExtractAIFlavors(r)
return len(is) == 0, err
}

// ExtractFlavor accepts a Page struct, specifically a FlavorPage struct,
// and extracts the elements into a slice of Flavor structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractAIFlavors(r pagination.Page) ([]AIFlavor, error) {
var s []AIFlavor
err := ExtractAIFlavorsInto(r, &s)
return s, err
}

func ExtractAIFlavorsInto(r pagination.Page, v interface{}) error {
return r.(AIFlavorPage).Result.ExtractIntoSlicePtr(v, "results")
}


type HardwareDescription struct {
CPU string `json:"cpu,omitempty"`
Disk string `json:"disk,omitempty"`
Network string `json:"network,omitempty"`
RAM string `json:"ram,omitempty"`
Ephemeral string `json:"ephemeral,omitempty"`
GPU string `json:"gpu,omitempty"`
IPU string `json:"ipu,omitempty"`
PoplarCount string `json:"poplar_count,omitempty"`
SGXEPCSize string `json:"sgx_epc_size,omitempty"`
}

// Flavor represents a flavor structure.
type AIFlavor struct {
FlavorID string `json:"flavor_id"`
FlavorName string `json:"flavor_name"`
Disabled bool `json:"disabled"`
ResourceClass string `json:"resource_class"`
PriceStatus *string `json:"price_status,omitempty"`
CurrencyCode *gcorecloud.Currency `json:"currency_code,omitempty"`
PricePerHour *decimal.Decimal `json:"price_per_hour,omitempty"`
PricePerMonth *decimal.Decimal `json:"price_per_month,omitempty"`
HardwareDescription *HardwareDescription `json:"hardware_description,omitempty"`
RAM *int `json:"ram,omitempty"`
VCPUS *int `json:"vcpus,omitempty"`
Capacity *int `json:"capacity,omitempty"`
}

1 change: 1 addition & 0 deletions gcore/ai/v1/aiflavors/testing/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package testing
39 changes: 39 additions & 0 deletions gcore/ai/v1/aiflavors/testing/fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package testing

import (
"github.com/G-Core/gcorelabscloud-go/gcore/ai/v1/aiflavors"
)

const ListResponse = `
{
"count": 1,
"results": [
{
"resource_class": "bm1-ai-small",
"hardware_description": {
"network": "2x100G",
"ipu": "vPOD-16 (Classic)",
"poplar_count": "2"
},
"disabled": false,
"flavor_name": "bm1-ai-2xsmall-v1pod-16",
"flavor_id": "bm1-ai-2xsmall-v1pod-16"
}
]
}
`

var (
AIFlavor1 = aiflavors.AIFlavor{
FlavorID: "bm1-ai-2xsmall-v1pod-16",
FlavorName: "bm1-ai-2xsmall-v1pod-16",
Disabled: false,
ResourceClass: "bm1-ai-small",
HardwareDescription: &aiflavors.HardwareDescription{
Network: "2x100G",
IPU: "vPOD-16 (Classic)",
PoplarCount: "2",
},
}
ExpectedAIFlavorSlice = []aiflavors.AIFlavor{AIFlavor1}
)
59 changes: 59 additions & 0 deletions gcore/ai/v1/aiflavors/testing/requests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package testing

import (
"fmt"
"net/http"
"testing"

"github.com/G-Core/gcorelabscloud-go/gcore/ai/v1/aiflavors"
"github.com/G-Core/gcorelabscloud-go/pagination"
th "github.com/G-Core/gcorelabscloud-go/testhelper"
fake "github.com/G-Core/gcorelabscloud-go/testhelper/client"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)

func prepareListTestURLParams(version string, projectID int, regionID int) string {
return fmt.Sprintf("/%s/ai/flavors/%d/%d", version, projectID, regionID)
}

func prepareListTestURL() string {
return prepareListTestURLParams("v1", fake.ProjectID, fake.RegionID)
}

func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

th.Mux.HandleFunc(prepareListTestURL(), func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "Authorization", fmt.Sprintf("Bearer %s", fake.AccessToken))
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := fmt.Fprint(w, ListResponse)
if err != nil {
log.Error(err)
}
})

client := fake.ServiceTokenClient("ai/flavors", "v1")
count := 0

opts := aiflavors.AIFlavorListOpts{}

err := aiflavors.List(client, opts).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := aiflavors.ExtractAIFlavors(page)
require.NoError(t, err)
ct := actual[0]
require.Equal(t, AIFlavor1, ct)
require.Equal(t, ExpectedAIFlavorSlice, actual)
return true, nil
})

th.AssertNoErr(t, err)

if count != 1 {
t.Errorf("Expected 1 page, got %d", count)
}
}
9 changes: 9 additions & 0 deletions gcore/ai/v1/aiflavors/urls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package aiflavors

import (
gcorecloud "github.com/G-Core/gcorelabscloud-go"
)

func listAIFlavorsURL(c *gcorecloud.ServiceClient) string {
return c.ServiceURL()
}
Loading

0 comments on commit 8e62c87

Please sign in to comment.