Skip to content

Commit 86b86bc

Browse files
authored
Merge pull request #58 from easy-cloud-Knet/refactor/infra_separation
Refactor/infra separation
2 parents 7d398a0 + f91ee0b commit 86b86bc

16 files changed

Lines changed: 958 additions & 513 deletions

api/create_vm.go

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,75 @@ import (
55
"errors"
66
"net/http"
77

8-
"github.com/easy-cloud-Knet/KWS_Control/client/model"
98
"github.com/easy-cloud-Knet/KWS_Control/service"
9+
"github.com/easy-cloud-Knet/KWS_Control/structure"
1010
"github.com/easy-cloud-Knet/KWS_Control/util"
1111
)
1212

13+
// ApiCreateVmRequest for POST /vm HTTP Request Body contract.
14+
type ApiCreateVmRequest struct {
15+
DomType string `json:"domType"`
16+
DomName string `json:"domName"`
17+
UUID structure.UUID `json:"uuid"`
18+
OS string `json:"os"`
19+
HWInfo ApiHardwareInfo `json:"HWInfo"`
20+
Network ApiNetworkInfo `json:"network"`
21+
Users []ApiUserInfo `json:"users"`
22+
SubnetType string `json:"Subnettype"`
23+
}
24+
25+
type ApiHardwareInfo struct {
26+
CPU uint32 `json:"cpu"`
27+
Memory uint32 `json:"memory"` // MiB
28+
Disk uint32 `json:"disk"` // MiB
29+
}
30+
31+
type ApiNetworkInfo struct {
32+
IPs []string `json:"ips"`
33+
// NetType은 내부에서 0 고정 — API 클라이언트가 전송하더라도 무시됨
34+
}
35+
36+
type ApiUserInfo struct {
37+
Name string `json:"name"`
38+
Groups string `json:"groups"`
39+
Password string `json:"passWord"`
40+
SSHAuthorizedKeys []string `json:"ssh"`
41+
}
42+
43+
// ToServiceInput은 HTTP DTO를 서비스 계층 DTO로 변환
44+
func (r *ApiCreateVmRequest) ToServiceInput() service.CreateVMInput {
45+
users := make([]service.UserSpec, len(r.Users))
46+
for i, u := range r.Users {
47+
users[i] = service.UserSpec{
48+
Name: u.Name,
49+
Groups: u.Groups,
50+
Password: u.Password,
51+
SSHAuthorizedKeys: u.SSHAuthorizedKeys,
52+
}
53+
}
54+
return service.CreateVMInput{
55+
UUID: r.UUID,
56+
DomType: r.DomType,
57+
DomName: r.DomName,
58+
OS: r.OS,
59+
HardwareInfo: service.HardwareSpec{
60+
CPU: r.HWInfo.CPU,
61+
Memory: r.HWInfo.Memory,
62+
Disk: r.HWInfo.Disk,
63+
},
64+
Network: service.NetworkSpec{
65+
IPs: r.Network.IPs,
66+
},
67+
Users: users,
68+
SubnetType: r.SubnetType,
69+
}
70+
}
71+
1372
func (c *handlerContext) createVm(w http.ResponseWriter, r *http.Request) {
1473
log := util.GetLogger()
1574
defer r.Body.Close()
1675

17-
var req model.CreateVMRequest
76+
var req ApiCreateVmRequest
1877
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
1978
log.Error("createVm: failed to parse request body: %v", err, true)
2079
var syntaxErr *json.SyntaxError
@@ -26,13 +85,12 @@ func (c *handlerContext) createVm(w http.ResponseWriter, r *http.Request) {
2685
return
2786
}
2887

29-
if req.HardwareInfo.Memory == 0 || req.HardwareInfo.CPU == 0 || req.HardwareInfo.Disk == 0 {
88+
if req.HWInfo.Memory == 0 || req.HWInfo.CPU == 0 || req.HWInfo.Disk == 0 {
3089
util.RespondError(w, http.StatusBadRequest, "Memory, CPU, and Disk must be non-zero")
3190
return
3291
}
3392

34-
err := service.CreateVM(req, c.context, c.rdb)
35-
if err != nil {
93+
if err := service.CreateVM(req.ToServiceInput(), c.context, c.rdb); err != nil {
3694
log.Error("createVm: failed to create VM: %v", err, true)
3795
util.RespondError(w, http.StatusInternalServerError, err.Error())
3896
return

api/get_vm_status.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@ type ApiVmStatusRequest struct {
1414
Type string `json:"type"` // "cpu", "memory", or "disk"
1515
}
1616

17+
type ApiVmCpuStatusResponse struct {
18+
System float64 `json:"system_time"`
19+
Idle float64 `json:"idle_time"`
20+
Usage float64 `json:"usage_percent"`
21+
}
22+
23+
type ApiVmMemoryStatusResponse struct {
24+
Total uint64 `json:"total_gb"`
25+
Used uint64 `json:"used_gb"`
26+
Available uint64 `json:"available_gb"`
27+
UsedPercent float64 `json:"used_percent"`
28+
}
29+
30+
type ApiVmDiskStatusResponse struct {
31+
Total uint64 `json:"total_gb"`
32+
Used uint64 `json:"used_gb"`
33+
Free uint64 `json:"free_gb"`
34+
UsedPercent float64 `json:"used_percent"`
35+
}
36+
1737
func (c *handlerContext) vmStatus(w http.ResponseWriter, r *http.Request) {
1838
log := util.GetLogger()
1939
defer r.Body.Close()
@@ -36,11 +56,14 @@ func (c *handlerContext) vmStatus(w http.ResponseWriter, r *http.Request) {
3656

3757
switch statusType {
3858
case "cpu":
39-
data, err = service.GetVMCpuInfo(req.UUID, c.context)
59+
cpu, e := service.GetVMCpuInfo(req.UUID, c.context)
60+
data, err = ApiVmCpuStatusResponse{System: cpu.System, Idle: cpu.Idle, Usage: cpu.Usage}, e
4061
case "memory":
41-
data, err = service.GetVMMemoryInfo(req.UUID, c.context)
62+
mem, e := service.GetVMMemoryInfo(req.UUID, c.context)
63+
data, err = ApiVmMemoryStatusResponse{Total: mem.Total, Used: mem.Used, Available: mem.Available, UsedPercent: mem.UsedPercent}, e
4264
case "disk":
43-
data, err = service.GetVMDiskInfo(req.UUID, c.context)
65+
disk, e := service.GetVMDiskInfo(req.UUID, c.context)
66+
data, err = ApiVmDiskStatusResponse{Total: disk.Total, Used: disk.Used, Free: disk.Free, UsedPercent: disk.UsedPercent}, e
4467
}
4568

4669
if err != nil {

client/cms.go

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,54 +11,102 @@ import (
1111
"github.com/easy-cloud-Knet/KWS_Control/util"
1212
)
1313

14+
// CmsClient는 CMS서비스에 서브넷/인스턴스 생성을 요청하는 HTTP 클라이언트.
1415
type CmsClient struct {
1516
baseURL string
1617
client *http.Client
1718
}
1819

19-
type NewSubnetRequest struct {
20-
IP string `json:"ip"`
20+
type CmsNewInstanceResponse struct {
21+
IP string `json:"IP"`
2122
MacAddr string `json:"macAddr"`
2223
SdnUUID string `json:"sdnUUID"`
2324
}
2425

25-
type subnetRequest struct {
26+
type CmsDeleteInstanceResponse struct {
27+
Detail string `json:"detail,omitempty"`
28+
}
29+
30+
type cmsNewInstanceRequestBody struct {
2631
Subnet string `json:"Subnet"`
2732
}
2833

34+
type cmsDeleteInstanceRequestBody struct {
35+
IP string `json:"IP"`
36+
}
37+
2938
func NewCmsClient() *CmsClient {
30-
CMS_HOST := os.Getenv("CMS_HOST")
31-
if CMS_HOST == "" {
39+
host := os.Getenv("CMS_HOST")
40+
if host == "" {
3241
log := util.GetLogger()
3342
log.Error("CMS_HOST Re:Check your env variable", true)
34-
CMS_HOST = "localhost:8080"
35-
log.Warn("CMS_HOST set: %s", CMS_HOST, true)
43+
host = "localhost:8080"
44+
log.Warn("CMS_HOST set: %s", host, true)
3645
}
3746
return &CmsClient{
38-
baseURL: CMS_HOST,
47+
baseURL: host,
3948
client: &http.Client{
4049
Timeout: 10 * time.Second,
4150
},
4251
}
4352
}
4453

45-
func (c *CmsClient) RequestSubnet(subnet string) (*NewSubnetRequest, error) {
54+
func (c *CmsClient) RequestDeleteInstance(ip string) (*CmsDeleteInstanceResponse, error) {
4655
log := util.GetLogger()
4756

4857
reqURL := fmt.Sprintf("http://%s/New/Instance", c.baseURL)
49-
reqBody := subnetRequest{Subnet: subnet}
50-
jsonBody, err := json.Marshal(reqBody)
58+
jsonBody, err := json.Marshal(cmsDeleteInstanceRequestBody{IP: ip})
5159
if err != nil {
52-
log.Error("CMS : failed to marshal JSON: %v", err)
53-
return nil, fmt.Errorf("RequestSubnet: failed to marshal JSON: %w", err)
60+
log.Error("CmsClient.RequestDeleteInstance : failed to marshal JSON: %v", err)
61+
return nil, fmt.Errorf("CmsClient.RequestDeleteInstance: failed to marshal JSON: %w", err)
5462
}
63+
req, err := http.NewRequest("DELETE", reqURL, bytes.NewBuffer(jsonBody))
64+
if err != nil {
65+
log.Error("CmsClient.RequestDeleteInstance : failed to NewRequest: %v", err)
66+
return nil, fmt.Errorf("CmsClient.RequestDeleteInstance: failed to create HTTP request: %w", err)
67+
}
68+
req.Header.Set("Content-Type", "application/json")
69+
req.Header.Set("Accept", "application/json")
5570

56-
req, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
71+
log.DebugInfo("Making request to: %s", reqURL)
72+
log.DebugInfo("Request body: %s", string(jsonBody))
73+
74+
resp, err := c.client.Do(req)
75+
if err != nil {
76+
log.Error("CmsClient.RequestDeleteInstance : failed to send request: %v", err)
77+
return nil, fmt.Errorf("CmsClient.RequestDeleteInstance: failed to send request: %w", err)
78+
}
79+
defer resp.Body.Close()
80+
81+
if resp.StatusCode != http.StatusOK {
82+
log.Error("CmsClient.RequestDeleteInstance : CMS returned status: %s", resp.Status)
83+
return nil, fmt.Errorf("CmsClient.RequestDeleteInstance: CMS server returned non-OK status: %s", resp.Status)
84+
}
85+
86+
var addrResp CmsDeleteInstanceResponse
87+
if err := json.NewDecoder(resp.Body).Decode(&addrResp); err != nil {
88+
log.Error("CmsClient.RequestDeleteInstance : failed to decode CMS response: %v", err)
89+
return nil, fmt.Errorf("CmsClient.RequestDeleteInstance: failed to decode response: %w", err)
90+
}
91+
return &addrResp, nil
92+
}
93+
94+
// RequestNewInstance는 주어진 서브넷에 대해 CMS에 새 인스턴스 할당을 요청한다.
95+
func (c *CmsClient) RequestNewInstance(subnet string) (*CmsNewInstanceResponse, error) {
96+
log := util.GetLogger()
97+
98+
reqURL := fmt.Sprintf("http://%s/New/Instance", c.baseURL)
99+
jsonBody, err := json.Marshal(cmsNewInstanceRequestBody{Subnet: subnet})
57100
if err != nil {
58-
log.Error("CMS : failed to NewRequest: %v", err)
59-
return nil, fmt.Errorf("RequestSubnet: failed to create HTTP request: %w", err)
101+
log.Error("CmsClient.RequestNewInstance : failed to marshal JSON: %v", err)
102+
return nil, fmt.Errorf("CmsClient.RequestNewInstance: failed to marshal JSON: %w", err)
60103
}
61104

105+
req, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
106+
if err != nil {
107+
log.Error("CmsClient.RequestNewInstance : failed to NewRequest: %v", err)
108+
return nil, fmt.Errorf("CmsClient.RequestNewInstance: failed to create HTTP request: %w", err)
109+
}
62110
req.Header.Set("Content-Type", "application/json")
63111
req.Header.Set("Accept", "application/json")
64112

@@ -67,21 +115,20 @@ func (c *CmsClient) RequestSubnet(subnet string) (*NewSubnetRequest, error) {
67115

68116
resp, err := c.client.Do(req)
69117
if err != nil {
70-
log.Error("CMS : failed to send request: %v", err)
71-
return nil, fmt.Errorf("RequestSubnet: failed to send request: %w", err)
118+
log.Error("CmsClient.RequestNewInstance : failed to send request: %v", err)
119+
return nil, fmt.Errorf("CmsClient.RequestNewInstance: failed to send request: %w", err)
72120
}
73121
defer resp.Body.Close()
74122

75123
if resp.StatusCode != http.StatusOK {
76-
log.Error("CMS : CMS returned status: %s", resp.Status)
77-
return nil, fmt.Errorf("CMS server returned non-OK status: %s", resp.Status)
124+
log.Error("CmsClient.RequestNewInstance : CMS returned status: %s", resp.Status)
125+
return nil, fmt.Errorf("CmsClient.RequestNewInstance: CMS server returned non-OK status: %s", resp.Status)
78126
}
79127

80-
var addrResp NewSubnetRequest
128+
var addrResp CmsNewInstanceResponse
81129
if err := json.NewDecoder(resp.Body).Decode(&addrResp); err != nil {
82-
log.Error("CMS : failed to decode CMS response: %v", err)
83-
return nil, fmt.Errorf("RequestSubnet: failed to decode response: %w", err)
130+
log.Error("CmsClient.RequestNewInstance : failed to decode CMS response: %v", err)
131+
return nil, fmt.Errorf("CmsClient.RequestNewInstance: failed to decode response: %w", err)
84132
}
85-
86133
return &addrResp, nil
87134
}

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func main() {
3030
log.Error("Failed to initialize: %v", err, true)
3131
panic(err)
3232
}
33-
printCores(contextStruct.Cores)
33+
printCores(contextStruct.Resources.Cores)
3434

3535
go func() {
3636
err := api.Server(contextStruct.Config.Port, &contextStruct, rdb)

service/cleanup.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package service
2+
3+
// cleanupChain will execute registered cleanup functions in reverse order when run() is called.
4+
// cleanup chain will register cleanup functions for each step of the process,
5+
// so that if any step fails, all previous steps can be undone to maintain system consistency.
6+
type cleanupChain struct {
7+
steps []func()
8+
}
9+
10+
func (c *cleanupChain) push(fn func()) {
11+
c.steps = append(c.steps, fn)
12+
}
13+
14+
func (c *cleanupChain) run() {
15+
for i := len(c.steps) - 1; i >= 0; i-- {
16+
c.steps[i]()
17+
}
18+
//for Idempotency
19+
c.steps = nil
20+
}

service/dto.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package service
2+
3+
import (
4+
vms "github.com/easy-cloud-Knet/KWS_Control/structure"
5+
)
6+
7+
// Service Layer DTO.
8+
// DTOs for service layer to decouple API layer from internal implementation details.
9+
// API Handler will convert API layer DTOs to service layer DTOs.
10+
// Service layer will convert to internal client/model structures as needed.
11+
//
12+
13+
type CreateVMInput struct {
14+
UUID vms.UUID
15+
DomType string
16+
DomName string
17+
OS string
18+
HardwareInfo HardwareSpec
19+
Network NetworkSpec
20+
Users []UserSpec
21+
SubnetType string // "Add" 또는 그 외(=신규 서브넷)
22+
}
23+
24+
type HardwareSpec struct {
25+
CPU uint32
26+
Memory uint32 // MiB
27+
Disk uint32 // MiB
28+
}
29+
30+
type NetworkSpec struct {
31+
IPs []string
32+
}
33+
34+
type UserSpec struct {
35+
Name string
36+
Groups string
37+
Password string
38+
SSHAuthorizedKeys []string
39+
}
40+
41+
// Output DTOs
42+
type VMInfo struct {
43+
UUID vms.UUID
44+
CPU uint32
45+
Memory uint32 // MiB
46+
Disk uint32 // MiB
47+
IP string
48+
Status string
49+
}
50+
51+
type VMCpuStatus struct {
52+
System float64
53+
Idle float64
54+
Usage float64
55+
}
56+
57+
type VMMemoryStatus struct {
58+
Total uint64
59+
Used uint64
60+
Available uint64
61+
UsedPercent float64
62+
}
63+
64+
type VMDiskStatus struct {
65+
Total uint64
66+
Used uint64
67+
Free uint64
68+
UsedPercent float64
69+
}

service/guacamole.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ func GetGuacamoleToken(uuid structure.UUID, ctx *structure.ControlContext) (stri
1414
return "", structure.ErrCoreNotFound(uuid)
1515
}
1616

17-
ctx.RLock()
17+
ctx.Resources.RLock()
1818
vm, exists := core.VMInfoIdx[uuid]
19-
ctx.RUnlock()
19+
ctx.Resources.RUnlock()
2020

2121
if exists {
2222
guacClient := client.NewGuacamoleClient(&ctx.Config)

0 commit comments

Comments
 (0)