Skip to content

Commit e39fd1e

Browse files
committed
feat(compute): add hypervisor endpoints
1 parent 1e39ae5 commit e39fd1e

6 files changed

Lines changed: 328 additions & 0 deletions

File tree

internal/api/compute/hypervisor.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package compute
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/JSYoo5B/SandStack/internal/api/respond"
8+
appcompute "github.com/JSYoo5B/SandStack/internal/app/compute"
9+
"github.com/go-chi/chi/v5"
10+
)
11+
12+
func (h Handler) listHypervisors(w http.ResponseWriter, _ *http.Request) {
13+
respond.JSON(w, http.StatusOK, hypervisorListResponse{
14+
Hypervisors: toHypervisorDocuments(h.service.ListHypervisors()),
15+
})
16+
}
17+
18+
func (h Handler) getHypervisorStatistics(w http.ResponseWriter, _ *http.Request) {
19+
respond.JSON(w, http.StatusOK, hypervisorStatisticsResponse{
20+
HypervisorStatistics: toHypervisorStatisticsDocument(
21+
h.service.ListHypervisors(),
22+
),
23+
})
24+
}
25+
26+
func (h Handler) getHypervisor(w http.ResponseWriter, r *http.Request) {
27+
hypervisor, err := h.service.GetHypervisor(chi.URLParam(r, "hypervisor_id"))
28+
if errors.Is(err, appcompute.ErrHypervisorNotFound) {
29+
respond.Error(w, http.StatusNotFound, "hypervisor not found")
30+
return
31+
}
32+
if err != nil {
33+
respond.Error(w, http.StatusInternalServerError, "hypervisor lookup failed")
34+
return
35+
}
36+
37+
respond.JSON(w, http.StatusOK, hypervisorResponse{
38+
Hypervisor: toHypervisorDocument(hypervisor),
39+
})
40+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package compute
2+
3+
import appcompute "github.com/JSYoo5B/SandStack/internal/app/compute"
4+
5+
type hypervisorListResponse struct {
6+
Hypervisors []hypervisorDocument `json:"hypervisors"`
7+
}
8+
9+
type hypervisorResponse struct {
10+
Hypervisor hypervisorDocument `json:"hypervisor"`
11+
}
12+
13+
type hypervisorStatisticsResponse struct {
14+
HypervisorStatistics hypervisorStatisticsDocument `json:"hypervisor_statistics"`
15+
}
16+
17+
type hypervisorDocument struct {
18+
ID string `json:"id"`
19+
CPUInfo hypervisorCPUInfoDocument `json:"cpu_info"`
20+
CurrentWorkload int `json:"current_workload"`
21+
Status string `json:"status"`
22+
State string `json:"state"`
23+
DiskAvailableLeast int `json:"disk_available_least"`
24+
HostIP string `json:"host_ip"`
25+
FreeDiskGB int `json:"free_disk_gb"`
26+
FreeRAMMB int `json:"free_ram_mb"`
27+
HypervisorHostname string `json:"hypervisor_hostname"`
28+
HypervisorType string `json:"hypervisor_type"`
29+
HypervisorVersion int `json:"hypervisor_version"`
30+
LocalGB int `json:"local_gb"`
31+
LocalGBUsed int `json:"local_gb_used"`
32+
MemoryMB int `json:"memory_mb"`
33+
MemoryMBUsed int `json:"memory_mb_used"`
34+
RunningVMs int `json:"running_vms"`
35+
Service hypervisorServiceDocument `json:"service"`
36+
VCPUs int `json:"vcpus"`
37+
VCPUsUsed int `json:"vcpus_used"`
38+
}
39+
40+
type hypervisorCPUInfoDocument struct {
41+
Vendor string `json:"vendor"`
42+
Arch string `json:"arch"`
43+
Model string `json:"model"`
44+
Features []string `json:"features"`
45+
Topology hypervisorCPUTopologyDocument `json:"topology"`
46+
}
47+
48+
type hypervisorCPUTopologyDocument struct {
49+
Cells int `json:"cells"`
50+
Sockets int `json:"sockets"`
51+
Cores int `json:"cores"`
52+
Threads int `json:"threads"`
53+
}
54+
55+
type hypervisorServiceDocument struct {
56+
ID string `json:"id"`
57+
Host string `json:"host"`
58+
DisabledReason string `json:"disabled_reason"`
59+
}
60+
61+
type hypervisorStatisticsDocument struct {
62+
Count int `json:"count"`
63+
CurrentWorkload int `json:"current_workload"`
64+
DiskAvailableLeast int `json:"disk_available_least"`
65+
FreeDiskGB int `json:"free_disk_gb"`
66+
FreeRAMMB int `json:"free_ram_mb"`
67+
LocalGB int `json:"local_gb"`
68+
LocalGBUsed int `json:"local_gb_used"`
69+
MemoryMB int `json:"memory_mb"`
70+
MemoryMBUsed int `json:"memory_mb_used"`
71+
RunningVMs int `json:"running_vms"`
72+
VCPUs int `json:"vcpus"`
73+
VCPUsUsed int `json:"vcpus_used"`
74+
}
75+
76+
func toHypervisorDocuments(
77+
hypervisors []appcompute.Hypervisor,
78+
) []hypervisorDocument {
79+
documents := make([]hypervisorDocument, 0, len(hypervisors))
80+
for _, hypervisor := range hypervisors {
81+
documents = append(documents, toHypervisorDocument(hypervisor))
82+
}
83+
84+
return documents
85+
}
86+
87+
func toHypervisorDocument(
88+
hypervisor appcompute.Hypervisor,
89+
) hypervisorDocument {
90+
return hypervisorDocument{
91+
ID: hypervisor.ID,
92+
CPUInfo: defaultCPUInfo(),
93+
CurrentWorkload: hypervisor.CurrentWorkload,
94+
Status: hypervisor.Status,
95+
State: hypervisor.State,
96+
DiskAvailableLeast: hypervisor.DiskAvailableLeast,
97+
HostIP: hypervisor.HostIP,
98+
FreeDiskGB: hypervisor.FreeDiskGB,
99+
FreeRAMMB: hypervisor.FreeRAMMB,
100+
HypervisorHostname: hypervisor.Hostname,
101+
HypervisorType: hypervisor.Type,
102+
HypervisorVersion: hypervisor.Version,
103+
LocalGB: hypervisor.LocalGB,
104+
LocalGBUsed: hypervisor.LocalGBUsed,
105+
MemoryMB: hypervisor.MemoryMB,
106+
MemoryMBUsed: hypervisor.MemoryMBUsed,
107+
RunningVMs: hypervisor.RunningVMs,
108+
Service: hypervisorServiceDocument{
109+
ID: hypervisor.ServiceID,
110+
Host: hypervisor.Hostname,
111+
},
112+
VCPUs: hypervisor.VCPUs,
113+
VCPUsUsed: hypervisor.VCPUsUsed,
114+
}
115+
}
116+
117+
func toHypervisorStatisticsDocument(
118+
hypervisors []appcompute.Hypervisor,
119+
) hypervisorStatisticsDocument {
120+
stats := hypervisorStatisticsDocument{Count: len(hypervisors)}
121+
for _, hypervisor := range hypervisors {
122+
stats.CurrentWorkload += hypervisor.CurrentWorkload
123+
stats.DiskAvailableLeast += hypervisor.DiskAvailableLeast
124+
stats.FreeDiskGB += hypervisor.FreeDiskGB
125+
stats.FreeRAMMB += hypervisor.FreeRAMMB
126+
stats.LocalGB += hypervisor.LocalGB
127+
stats.LocalGBUsed += hypervisor.LocalGBUsed
128+
stats.MemoryMB += hypervisor.MemoryMB
129+
stats.MemoryMBUsed += hypervisor.MemoryMBUsed
130+
stats.RunningVMs += hypervisor.RunningVMs
131+
stats.VCPUs += hypervisor.VCPUs
132+
stats.VCPUsUsed += hypervisor.VCPUsUsed
133+
}
134+
135+
return stats
136+
}
137+
138+
func defaultCPUInfo() hypervisorCPUInfoDocument {
139+
return hypervisorCPUInfoDocument{
140+
Vendor: "SandStack",
141+
Arch: "x86_64",
142+
Model: "compat",
143+
Features: []string{},
144+
Topology: hypervisorCPUTopologyDocument{
145+
Cells: 1,
146+
Sockets: 1,
147+
Cores: 1,
148+
Threads: 1,
149+
},
150+
}
151+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package compute_test
2+
3+
import (
4+
"net/http/httptest"
5+
"testing"
6+
7+
"github.com/JSYoo5B/SandStack/internal/api/compute"
8+
"github.com/JSYoo5B/SandStack/internal/testhelper"
9+
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/hypervisors"
10+
"github.com/stretchr/testify/suite"
11+
)
12+
13+
type HypervisorSuite struct {
14+
suite.Suite
15+
server *httptest.Server
16+
}
17+
18+
func TestHypervisorSuite(t *testing.T) {
19+
suite.Run(t, new(HypervisorSuite))
20+
}
21+
22+
func (s *HypervisorSuite) SetupTest() {
23+
s.server = httptest.NewServer(
24+
compute.NewRouter(testhelper.DefaultConfig()),
25+
)
26+
}
27+
28+
func (s *HypervisorSuite) TearDownTest() {
29+
s.server.Close()
30+
}
31+
32+
func (s *HypervisorSuite) TestListHypervisors() {
33+
client := testhelper.ServiceClient(s.server.URL + "/demo")
34+
pages, err := hypervisors.List(
35+
client,
36+
nil,
37+
).AllPages(s.T().Context())
38+
s.Require().NoError(err)
39+
40+
listed, err := hypervisors.ExtractHypervisors(pages)
41+
s.Require().NoError(err)
42+
43+
s.Require().Len(listed, 1)
44+
s.Assert().Equal("1", listed[0].ID)
45+
s.Assert().Equal("sandstack", listed[0].HypervisorHostname)
46+
s.Assert().Equal("enabled", listed[0].Status)
47+
}
48+
49+
func (s *HypervisorSuite) TestGetHypervisor() {
50+
found, err := hypervisors.Get(
51+
s.T().Context(),
52+
testhelper.ServiceClient(s.server.URL+"/demo"),
53+
"1",
54+
).Extract()
55+
s.Require().NoError(err)
56+
s.Require().NotNil(found)
57+
58+
s.Assert().Equal("1", found.ID)
59+
s.Assert().Equal("sandstack", found.HypervisorHostname)
60+
}
61+
62+
func (s *HypervisorSuite) TestGetHypervisorStatistics() {
63+
stats, err := hypervisors.GetStatistics(
64+
s.T().Context(),
65+
testhelper.ServiceClient(s.server.URL+"/demo"),
66+
).Extract()
67+
s.Require().NoError(err)
68+
s.Require().NotNil(stats)
69+
70+
s.Assert().Equal(1, stats.Count)
71+
s.Assert().Equal(200, stats.VCPUs)
72+
s.Assert().Equal(512000, stats.MemoryMB)
73+
}

internal/api/compute/router.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ func (h Handler) Router() http.Handler {
5858
router.Get("/{project_id}/os-availability-zone", h.listAvailabilityZones)
5959
router.Get("/{project_id}/os-availability-zone/detail", h.listAvailabilityZones)
6060
router.Get("/{project_id}/os-services", h.listComputeServices)
61+
router.Get("/{project_id}/os-hypervisors/detail", h.listHypervisors)
62+
router.Get("/{project_id}/os-hypervisors/statistics", h.getHypervisorStatistics)
63+
router.Get("/{project_id}/os-hypervisors/{hypervisor_id}", h.getHypervisor)
6164
router.Get("/{project_id}/flavors", h.listFlavors)
6265
router.Get("/{project_id}/flavors/detail", h.listFlavors)
6366
router.Get("/{project_id}/flavors/{flavor_id}", h.getFlavor)

internal/app/compute/hypervisor.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package compute
2+
3+
import "errors"
4+
5+
var ErrHypervisorNotFound = errors.New("hypervisor not found")
6+
7+
func (s *Service) ListHypervisors() []Hypervisor {
8+
return []Hypervisor{s.defaultHypervisor()}
9+
}
10+
11+
func (s *Service) GetHypervisor(id string) (Hypervisor, error) {
12+
hypervisor := s.defaultHypervisor()
13+
if hypervisor.ID != id {
14+
return Hypervisor{}, ErrHypervisorNotFound
15+
}
16+
17+
return hypervisor, nil
18+
}
19+
20+
func (s *Service) defaultHypervisor() Hypervisor {
21+
return Hypervisor{
22+
ID: "1",
23+
Status: "enabled",
24+
State: "up",
25+
DiskAvailableLeast: 80,
26+
HostIP: "127.0.0.1",
27+
FreeDiskGB: 80,
28+
FreeRAMMB: 512000,
29+
Hostname: "sandstack",
30+
Type: "sandstack",
31+
Version: 1,
32+
LocalGB: 100,
33+
LocalGBUsed: 20,
34+
MemoryMB: 512000,
35+
MemoryMBUsed: 0,
36+
ServiceID: "compute-service-1",
37+
VCPUs: 200,
38+
}
39+
}

internal/app/compute/types.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,28 @@ type ComputeService struct {
8585
Zone string
8686
}
8787

88+
type Hypervisor struct {
89+
ID string
90+
CurrentWorkload int
91+
Status string
92+
State string
93+
DiskAvailableLeast int
94+
HostIP string
95+
FreeDiskGB int
96+
FreeRAMMB int
97+
Hostname string
98+
Type string
99+
Version int
100+
LocalGB int
101+
LocalGBUsed int
102+
MemoryMB int
103+
MemoryMBUsed int
104+
RunningVMs int
105+
ServiceID string
106+
VCPUs int
107+
VCPUsUsed int
108+
}
109+
88110
type Limits struct {
89111
MaxTotalCores int
90112
MaxImageMeta int

0 commit comments

Comments
 (0)