Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions api/handler/space_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,28 @@ func (h *SpaceResourceHandler) Delete(ctx *gin.Context) {
slog.Info("Delete space resource successfully")
httpbase.OK(ctx, nil)
}

// ListHardwareTypes godoc
// @Security ApiKey
// @Summary List hardware types in a cluster
// @Description list hardware types in a cluster
// @Tags SpaceReource
// @Accept json
// @Produce json
// @Param cluster_id query string false "cluster_id"
// @Success 200 {object} types.Response{data=[]string} "OK"
// @Failure 500 {object} types.APIInternalServerError "Internal server error"
// @Router /space_resources/hardware_types [get]

func (h *SpaceResourceHandler) ListHardwareTypes(ctx *gin.Context) {
clusterId := ctx.Query("cluster_id")

types, err := h.spaceResource.ListHardwareTypes(ctx.Request.Context(), clusterId)
if err != nil {
slog.ErrorContext(ctx.Request.Context(), "Failed to list hardware types", slog.String("cluster_id", clusterId), slog.Any("error", err))
httpbase.ServerError(ctx, err)
return
}
slog.Info("List hardware types successfully")
httpbase.OK(ctx, types)
}
26 changes: 26 additions & 0 deletions api/handler/space_resource_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handler

import (
"errors"
"testing"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -107,3 +108,28 @@ func TestSpaceResourceHandler_Delete(t *testing.T) {

tester.ResponseEq(t, 200, tester.OKText, nil)
}

func TestSpaceResourceHandler_ListHardwareTypes(t *testing.T) {
t.Run("200", func(t *testing.T) {
tester := NewSpaceResourceTester(t).WithHandleFunc(func(h *SpaceResourceHandler) gin.HandlerFunc {
return h.ListHardwareTypes
})
tester.mocks.spaceResource.EXPECT().ListHardwareTypes(tester.Ctx(), "c1").Return(
[]string{"NVIDIA A100", "Intel Xeon"}, nil,
)
tester.WithQuery("cluster_id", "c1").WithUser().Execute()

tester.ResponseEq(t, 200, tester.OKText, []string{"NVIDIA A100", "Intel Xeon"})
})
t.Run("error", func(t *testing.T) {
tester := NewSpaceResourceTester(t).WithHandleFunc(func(h *SpaceResourceHandler) gin.HandlerFunc {
return h.ListHardwareTypes
})
tester.mocks.spaceResource.EXPECT().ListHardwareTypes(tester.Ctx(), "c1").Return(
nil, errors.New("database error"),
)
tester.WithQuery("cluster_id", "c1").WithUser().Execute()

tester.ResponseEq(t, 500, "database error", nil)
})
}
1 change: 1 addition & 0 deletions api/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ func NewRouter(config *config.Config, enableSwagger bool) (*gin.Engine, error) {
spaceResource.POST("", middlewareCollection.License.Check, middlewareCollection.Auth.NeedAdmin, spaceResourceHandler.Create)
spaceResource.PUT("/:id", middlewareCollection.License.Check, middlewareCollection.Auth.NeedAdmin, spaceResourceHandler.Update)
spaceResource.DELETE("/:id", middlewareCollection.License.Check, middlewareCollection.Auth.NeedAdmin, spaceResourceHandler.Delete)
spaceResource.GET("/hardware_types", spaceResourceHandler.ListHardwareTypes)
}

spaceSdkHandler, err := handler.NewSpaceSdkHandler(config)
Expand Down
60 changes: 60 additions & 0 deletions builder/store/database/space_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package database

import (
"context"
"encoding/json"
"fmt"

"opencsg.com/csghub-server/common/errorx"
"opencsg.com/csghub-server/common/types"
)

type spaceResourceStoreImpl struct {
Expand All @@ -19,6 +21,7 @@ type SpaceResourceStore interface {
FindByID(ctx context.Context, id int64) (*SpaceResource, error)
FindByName(ctx context.Context, name string) (*SpaceResource, error)
FindAll(ctx context.Context) ([]SpaceResource, error)
FindAllResourceTypes(ctx context.Context, clusterId string) ([]string, error)
}

func NewSpaceResourceStore() SpaceResourceStore {
Expand Down Expand Up @@ -97,3 +100,60 @@ func (s *spaceResourceStoreImpl) FindAll(ctx context.Context) ([]SpaceResource,
}
return result, nil
}

func (s *spaceResourceStoreImpl) FindAllResourceTypes(ctx context.Context, clusterId string) ([]string, error) {
typeSet := make(map[string]bool)
var hardWareTypes []string

// Use pagination to query resources
page := 1
per := 100 // Set a reasonable page size

for {
// Get resources for current page
resources, _, err := s.Index(ctx, clusterId, per, page)
if err != nil {
return nil, err
}

// If no resources returned, we've reached the end
if len(resources) == 0 {
break
}

// Process each resource
for _, resource := range resources {
var hw types.HardWare
if err := json.Unmarshal([]byte(resource.Resources), &hw); err != nil {
continue
}

// Extract type from each processor and CPU
processors := []types.Processor{
hw.Gpu,
hw.Npu,
hw.Gcu,
hw.Mlu,
hw.Dcu,
hw.GPGpu,
}

for _, p := range processors {
if p.Type != "" && !typeSet[p.Type] {
typeSet[p.Type] = true
hardWareTypes = append(hardWareTypes, p.Type)
}
}

if hw.Cpu.Type != "" && !typeSet[hw.Cpu.Type] {
typeSet[hw.Cpu.Type] = true
hardWareTypes = append(hardWareTypes, hw.Cpu.Type)
}
}

// Move to next page
page++
}

return hardWareTypes, nil
}
90 changes: 90 additions & 0 deletions builder/store/database/space_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,93 @@ func TestSpaceResourceStore_CRUD(t *testing.T) {
require.NotNil(t, err)

}

func TestSpaceResourceStore_FindAllResourceTypes(t *testing.T) {
db := tests.InitTestDB()
defer db.Close()
ctx := context.TODO()

store := database.NewSpaceResourceStoreWithDB(db)

// Create test resources with different hardware types
resources := []database.SpaceResource{
{
Name: "r1",
ClusterID: "c1",
Resources: `{"gpu":{"type":"NVIDIA A100"},"cpu":{"type":"Intel Xeon"}}`,
},
{
Name: "r2",
ClusterID: "c1",
Resources: `{"npu":{"type":"Ascend 910"},"cpu":{"type":"Intel Xeon"}}`, // Duplicate CPU type
},
{
Name: "r3",
ClusterID: "c1",
Resources: `{"gcu":{"type":"Enflame G100"}}`,
},
}

for _, r := range resources {
_, err := store.Create(ctx, r)
require.Nil(t, err)
}

// Test FindAllResourceTypes method
types, err := store.FindAllResourceTypes(ctx, "c1")
require.Nil(t, err)

// Expected unique types: NVIDIA A100, Intel Xeon, Ascend 910, Enflame G100
expectedTypes := map[string]bool{
"NVIDIA A100": true,
"Intel Xeon": true,
"Ascend 910": true,
"Enflame G100": true,
}

require.Equal(t, len(expectedTypes), len(types))

// Verify all expected types are present
typeMap := make(map[string]bool)
for _, t := range types {
typeMap[t] = true
}

for expected := range expectedTypes {
require.True(t, typeMap[expected], "Expected type %s not found", expected)
}
}

func TestSpaceResourceStore_FindAllResourceTypes_Empty(t *testing.T) {
db := tests.InitTestDB()
defer db.Close()
ctx := context.TODO()

store := database.NewSpaceResourceStoreWithDB(db)

// Test when there are no resources
types, err := store.FindAllResourceTypes(ctx, "c1")
require.Nil(t, err)
require.Empty(t, types)
}

func TestSpaceResourceStore_FindAllResourceTypes_InvalidJSON(t *testing.T) {
db := tests.InitTestDB()
defer db.Close()
ctx := context.TODO()

store := database.NewSpaceResourceStoreWithDB(db)

// Create a resource with invalid JSON
_, err := store.Create(ctx, database.SpaceResource{
Name: "invalid",
ClusterID: "c1",
Resources: `{invalid json}`,
})
require.Nil(t, err)

// The method should ignore invalid JSON and return empty list
types, err := store.FindAllResourceTypes(ctx, "c1")
require.Nil(t, err)
require.Empty(t, types)
}
Loading
Loading