Skip to content

Commit 098418e

Browse files
QinYuuuuDev Agent
andauthored
add space resource list hardware types (#630)
Co-authored-by: Dev Agent <[email protected]>
1 parent e6b7bcb commit 098418e

File tree

9 files changed

+358
-0
lines changed

9 files changed

+358
-0
lines changed

_mocks/opencsg.com/csghub-server/builder/store/database/mock_SpaceResourceStore.go

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

_mocks/opencsg.com/csghub-server/component/mock_SpaceResourceComponent.go

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/handler/space_resource.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,28 @@ func (h *SpaceResourceHandler) Delete(ctx *gin.Context) {
184184
slog.Info("Delete space resource successfully")
185185
httpbase.OK(ctx, nil)
186186
}
187+
188+
// ListHardwareTypes godoc
189+
// @Security ApiKey
190+
// @Summary List hardware types in a cluster
191+
// @Description list hardware types in a cluster
192+
// @Tags SpaceReource
193+
// @Accept json
194+
// @Produce json
195+
// @Param cluster_id query string false "cluster_id"
196+
// @Success 200 {object} types.Response{data=[]string} "OK"
197+
// @Failure 500 {object} types.APIInternalServerError "Internal server error"
198+
// @Router /space_resources/hardware_types [get]
199+
200+
func (h *SpaceResourceHandler) ListHardwareTypes(ctx *gin.Context) {
201+
clusterId := ctx.Query("cluster_id")
202+
203+
types, err := h.spaceResource.ListHardwareTypes(ctx.Request.Context(), clusterId)
204+
if err != nil {
205+
slog.ErrorContext(ctx.Request.Context(), "Failed to list hardware types", slog.String("cluster_id", clusterId), slog.Any("error", err))
206+
httpbase.ServerError(ctx, err)
207+
return
208+
}
209+
slog.Info("List hardware types successfully")
210+
httpbase.OK(ctx, types)
211+
}

api/handler/space_resource_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handler
22

33
import (
4+
"errors"
45
"testing"
56

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

108109
tester.ResponseEq(t, 200, tester.OKText, nil)
109110
}
111+
112+
func TestSpaceResourceHandler_ListHardwareTypes(t *testing.T) {
113+
t.Run("200", func(t *testing.T) {
114+
tester := NewSpaceResourceTester(t).WithHandleFunc(func(h *SpaceResourceHandler) gin.HandlerFunc {
115+
return h.ListHardwareTypes
116+
})
117+
tester.mocks.spaceResource.EXPECT().ListHardwareTypes(tester.Ctx(), "c1").Return(
118+
[]string{"NVIDIA A100", "Intel Xeon"}, nil,
119+
)
120+
tester.WithQuery("cluster_id", "c1").WithUser().Execute()
121+
122+
tester.ResponseEq(t, 200, tester.OKText, []string{"NVIDIA A100", "Intel Xeon"})
123+
})
124+
t.Run("error", func(t *testing.T) {
125+
tester := NewSpaceResourceTester(t).WithHandleFunc(func(h *SpaceResourceHandler) gin.HandlerFunc {
126+
return h.ListHardwareTypes
127+
})
128+
tester.mocks.spaceResource.EXPECT().ListHardwareTypes(tester.Ctx(), "c1").Return(
129+
nil, errors.New("database error"),
130+
)
131+
tester.WithQuery("cluster_id", "c1").WithUser().Execute()
132+
133+
tester.ResponseEq(t, 500, "database error", nil)
134+
})
135+
}

api/router/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ func NewRouter(config *config.Config, enableSwagger bool) (*gin.Engine, error) {
258258
spaceResource.POST("", middlewareCollection.License.Check, middlewareCollection.Auth.NeedAdmin, spaceResourceHandler.Create)
259259
spaceResource.PUT("/:id", middlewareCollection.License.Check, middlewareCollection.Auth.NeedAdmin, spaceResourceHandler.Update)
260260
spaceResource.DELETE("/:id", middlewareCollection.License.Check, middlewareCollection.Auth.NeedAdmin, spaceResourceHandler.Delete)
261+
spaceResource.GET("/hardware_types", spaceResourceHandler.ListHardwareTypes)
261262
}
262263

263264
spaceSdkHandler, err := handler.NewSpaceSdkHandler(config)

builder/store/database/space_resource.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package database
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67

78
"opencsg.com/csghub-server/common/errorx"
9+
"opencsg.com/csghub-server/common/types"
810
)
911

1012
type spaceResourceStoreImpl struct {
@@ -19,6 +21,7 @@ type SpaceResourceStore interface {
1921
FindByID(ctx context.Context, id int64) (*SpaceResource, error)
2022
FindByName(ctx context.Context, name string) (*SpaceResource, error)
2123
FindAll(ctx context.Context) ([]SpaceResource, error)
24+
FindAllResourceTypes(ctx context.Context, clusterId string) ([]string, error)
2225
}
2326

2427
func NewSpaceResourceStore() SpaceResourceStore {
@@ -99,3 +102,60 @@ func (s *spaceResourceStoreImpl) FindAll(ctx context.Context) ([]SpaceResource,
99102
}
100103
return result, nil
101104
}
105+
106+
func (s *spaceResourceStoreImpl) FindAllResourceTypes(ctx context.Context, clusterId string) ([]string, error) {
107+
typeSet := make(map[string]bool)
108+
var hardWareTypes []string
109+
110+
// Use pagination to query resources
111+
page := 1
112+
per := 100 // Set a reasonable page size
113+
114+
for {
115+
// Get resources for current page
116+
resources, _, err := s.Index(ctx, clusterId, per, page)
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
// If no resources returned, we've reached the end
122+
if len(resources) == 0 {
123+
break
124+
}
125+
126+
// Process each resource
127+
for _, resource := range resources {
128+
var hw types.HardWare
129+
if err := json.Unmarshal([]byte(resource.Resources), &hw); err != nil {
130+
continue
131+
}
132+
133+
// Extract type from each processor and CPU
134+
processors := []types.Processor{
135+
hw.Gpu,
136+
hw.Npu,
137+
hw.Gcu,
138+
hw.Mlu,
139+
hw.Dcu,
140+
hw.GPGpu,
141+
}
142+
143+
for _, p := range processors {
144+
if p.Type != "" && !typeSet[p.Type] {
145+
typeSet[p.Type] = true
146+
hardWareTypes = append(hardWareTypes, p.Type)
147+
}
148+
}
149+
150+
if hw.Cpu.Type != "" && !typeSet[hw.Cpu.Type] {
151+
typeSet[hw.Cpu.Type] = true
152+
hardWareTypes = append(hardWareTypes, hw.Cpu.Type)
153+
}
154+
}
155+
156+
// Move to next page
157+
page++
158+
}
159+
160+
return hardWareTypes, nil
161+
}

builder/store/database/space_resource_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,93 @@ func TestSpaceResourceStore_CRUD(t *testing.T) {
5757
require.NotNil(t, err)
5858

5959
}
60+
61+
func TestSpaceResourceStore_FindAllResourceTypes(t *testing.T) {
62+
db := tests.InitTestDB()
63+
defer db.Close()
64+
ctx := context.TODO()
65+
66+
store := database.NewSpaceResourceStoreWithDB(db)
67+
68+
// Create test resources with different hardware types
69+
resources := []database.SpaceResource{
70+
{
71+
Name: "r1",
72+
ClusterID: "c1",
73+
Resources: `{"gpu":{"type":"NVIDIA A100"},"cpu":{"type":"Intel Xeon"}}`,
74+
},
75+
{
76+
Name: "r2",
77+
ClusterID: "c1",
78+
Resources: `{"npu":{"type":"Ascend 910"},"cpu":{"type":"Intel Xeon"}}`, // Duplicate CPU type
79+
},
80+
{
81+
Name: "r3",
82+
ClusterID: "c1",
83+
Resources: `{"gcu":{"type":"Enflame G100"}}`,
84+
},
85+
}
86+
87+
for _, r := range resources {
88+
_, err := store.Create(ctx, r)
89+
require.Nil(t, err)
90+
}
91+
92+
// Test FindAllResourceTypes method
93+
types, err := store.FindAllResourceTypes(ctx, "c1")
94+
require.Nil(t, err)
95+
96+
// Expected unique types: NVIDIA A100, Intel Xeon, Ascend 910, Enflame G100
97+
expectedTypes := map[string]bool{
98+
"NVIDIA A100": true,
99+
"Intel Xeon": true,
100+
"Ascend 910": true,
101+
"Enflame G100": true,
102+
}
103+
104+
require.Equal(t, len(expectedTypes), len(types))
105+
106+
// Verify all expected types are present
107+
typeMap := make(map[string]bool)
108+
for _, t := range types {
109+
typeMap[t] = true
110+
}
111+
112+
for expected := range expectedTypes {
113+
require.True(t, typeMap[expected], "Expected type %s not found", expected)
114+
}
115+
}
116+
117+
func TestSpaceResourceStore_FindAllResourceTypes_Empty(t *testing.T) {
118+
db := tests.InitTestDB()
119+
defer db.Close()
120+
ctx := context.TODO()
121+
122+
store := database.NewSpaceResourceStoreWithDB(db)
123+
124+
// Test when there are no resources
125+
types, err := store.FindAllResourceTypes(ctx, "c1")
126+
require.Nil(t, err)
127+
require.Empty(t, types)
128+
}
129+
130+
func TestSpaceResourceStore_FindAllResourceTypes_InvalidJSON(t *testing.T) {
131+
db := tests.InitTestDB()
132+
defer db.Close()
133+
ctx := context.TODO()
134+
135+
store := database.NewSpaceResourceStoreWithDB(db)
136+
137+
// Create a resource with invalid JSON
138+
_, err := store.Create(ctx, database.SpaceResource{
139+
Name: "invalid",
140+
ClusterID: "c1",
141+
Resources: `{invalid json}`,
142+
})
143+
require.Nil(t, err)
144+
145+
// The method should ignore invalid JSON and return empty list
146+
types, err := store.FindAllResourceTypes(ctx, "c1")
147+
require.Nil(t, err)
148+
require.Empty(t, types)
149+
}

0 commit comments

Comments
 (0)