Skip to content
Open
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
16 changes: 6 additions & 10 deletions DomCon/domainList_status/cpu_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,19 @@ import (
// 인터페이스 구현체

func (vs *VCPUStatus) EmitStatus(dls *DomainListStatus) {
vs.Total = int(dls.VCPUTotal)
vs.Allocated = int(dls.VcpuAllocated)
vs.Sleeping = int(dls.VcpuSleeping)

vs.Idle = vs.Total - vs.Allocated
if vs.Idle < 0 {
vs.Idle = 0
}
vs.Total = int(atomic.LoadInt64(&dls.VCPUTotal))
vs.Allocated = int(atomic.LoadInt64(&dls.VcpuAllocated))
vs.Sleeping = int(atomic.LoadInt64(&dls.VcpuSleeping))

vs.Idle = max(vs.Total-vs.Allocated, 0)
}

func (dls *DomainListStatus) Update() {
dls.UpdateCPUTotal()
}

func (dls *DomainListStatus) UpdateCPUTotal() {
totalCPU := runtime.NumCPU()
dls.VCPUTotal = int64(totalCPU)
atomic.StoreInt64(&dls.VCPUTotal, int64(runtime.NumCPU()))
}

func (dls *DomainListStatus) AddAllocatedCPU(vcpu int) {
Expand Down
34 changes: 18 additions & 16 deletions api/Create/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,28 @@ import (
)

type CreateVMRequest struct {
DomName string `json:"domName"`
UUID string `json:"uuid"`
OS string `json:"os"`
HardwareInfo vmtypes.HardwareInfo `json:"HWInfo"`
NetConf network.NetDefine `json:"network"`
Users []vmtypes.User_info_VM `json:"users"`
SDNUUID string `json:"sdnUUID"`
MacAddr string `json:"macAddr"`
DomName string `json:"domName"`
UUID string `json:"uuid"`
OS string `json:"os"`
PresignedImageUrl string `json:"presignedImageUrl,omitempty"`
HardwareInfo vmtypes.HardwareInfo `json:"HWInfo"`
NetConf network.NetDefine `json:"network"`
Users []vmtypes.User_info_VM `json:"users"`
SDNUUID string `json:"sdnUUID"`
MacAddr string `json:"macAddr"`
}

func (r *CreateVMRequest) toVMInitInfo() *vmtypes.VM_Init_Info {
return &vmtypes.VM_Init_Info{
DomName: r.DomName,
UUID: r.UUID,
OS: r.OS,
HardwareInfo: r.HardwareInfo,
NetConf: r.NetConf,
Users: r.Users,
SDNUUID: r.SDNUUID,
MacAddr: r.MacAddr,
DomName: r.DomName,
UUID: r.UUID,
OS: r.OS,
PresignedImageUrl: r.PresignedImageUrl,
HardwareInfo: r.HardwareInfo,
NetConf: r.NetConf,
Users: r.Users,
SDNUUID: r.SDNUUID,
MacAddr: r.MacAddr,
}
}

Expand Down
49 changes: 49 additions & 0 deletions api/Snapshot/Snapshot.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package snapshot

import (
"context"
"fmt"
"net/http"
"time"

"github.com/easy-cloud-Knet/KWS_Core/internal/config"
virerr "github.com/easy-cloud-Knet/KWS_Core/internal/error"
httputil "github.com/easy-cloud-Knet/KWS_Core/pkg/httputil"
externalsnapshot "github.com/easy-cloud-Knet/KWS_Core/services/snapshot/external_snap"
Expand Down Expand Up @@ -292,3 +295,49 @@ func (h *Handler) DeleteSnapshot(w http.ResponseWriter, r *http.Request) {
h.Logger.Info("snapshot delete success", zap.String("uuid", param.UUID), zap.String("snapshot_name", param.Name))
resp.ResponseWriteOK(w, nil)
}

func (h *Handler) TakeExternalSnapshot(w http.ResponseWriter, r *http.Request) {
param := &TakeExternalSnapshotRequest{}
resp := httputil.ResponseGen[TakeExternalSnapshotResponse]("Take External Snapshot")

if err := httputil.HttpDecoder(r, param); err != nil {
resp.ResponseWriteErr(w, err, http.StatusBadRequest)
h.Logger.Error("take external snapshot decode failed", zap.Error(err))
return
}

if param.UUID == "" || param.SnapKey == "" || param.PresignedURL == "" {
resp.ResponseWriteErr(w, virerr.ErrorGen(virerr.InvalidParameter, fmt.Errorf("uuid, snapKey and presignedUrl are required")), http.StatusBadRequest)
return
}

h.Logger.Info("take external snapshot start", zap.String("uuid", param.UUID), zap.String("snap_key", param.SnapKey))

dom, err := h.DomainControl.GetDomain(param.UUID)
if err != nil {
resp.ResponseWriteErr(w, err, http.StatusInternalServerError)
h.Logger.Error("take external snapshot failed - domain not found", zap.String("uuid", param.UUID), zap.Error(err))
return
}

snapName, err := externalsnapshot.CreateExternalSnapshot(dom, param.SnapKey, &externalsnapshot.ExternalSnapshotOptions{})
if err != nil {
resp.ResponseWriteErr(w, err, http.StatusInternalServerError)
h.Logger.Error("take external snapshot failed - create snapshot", zap.String("uuid", param.UUID), zap.Error(err))
return
}
Comment on lines +323 to +328

filePath := externalsnapshot.SnapshotFilePath(config.StorageBase, param.UUID, snapName, "vda")

ctx, cancel := context.WithTimeout(r.Context(), 10*time.Minute)
defer cancel()

if err := externalsnapshot.UploadToPresignedURL(ctx, filePath, param.PresignedURL); err != nil {
resp.ResponseWriteErr(w, virerr.ErrorGen(virerr.SnapshotError, err), http.StatusInternalServerError)
h.Logger.Error("take external snapshot failed - upload", zap.String("uuid", param.UUID), zap.String("file", filePath), zap.Error(err))
return
}

h.Logger.Info("take external snapshot success", zap.String("uuid", param.UUID), zap.String("snap_key", snapName))
resp.ResponseWriteOK(w, &TakeExternalSnapshotResponse{UUID: param.UUID, SnapKey: snapName})
}
11 changes: 11 additions & 0 deletions api/Snapshot/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ type ExternalSnapshotMergeResponse struct {
UUID string `json:"UUID"`
MergedDisks []string `json:"MergedDisks"`
}

type TakeExternalSnapshotRequest struct {
UUID string `json:"uuid"`
SnapKey string `json:"snapKey"`
PresignedURL string `json:"presignedUrl"`
}

type TakeExternalSnapshotResponse struct {
UUID string `json:"uuid"`
SnapKey string `json:"snapKey"`
}
Comment on lines +39 to +48
49 changes: 49 additions & 0 deletions api/Snapshot/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,55 @@ func TestMergeExternalSnapshot_GetDomainError(t *testing.T) {
}
}

// TakeExternalSnapshot

func TestTakeExternalSnapshot_BadRequest(t *testing.T) {
h := newTestHandler(&mockDomainController{})
r := httptest.NewRequest(http.MethodPost, "/TakeExternalSnapshot", bytes.NewBufferString("invalid json"))
w := httptest.NewRecorder()

h.TakeExternalSnapshot(w, r)

if w.Code != http.StatusBadRequest {
t.Errorf("expected %d, got %d", http.StatusBadRequest, w.Code)
}
}

func TestTakeExternalSnapshot_MissingFields(t *testing.T) {
h := newTestHandler(&mockDomainController{})

cases := []TakeExternalSnapshotRequest{
{SnapKey: "snap1", PresignedURL: "http://example.com"},
{UUID: "test-uuid", PresignedURL: "http://example.com"},
{UUID: "test-uuid", SnapKey: "snap1"},
}

for _, req := range cases {
r := testutil.MakeRequest(t, req)
w := httptest.NewRecorder()
h.TakeExternalSnapshot(w, r)
if w.Code != http.StatusBadRequest {
t.Errorf("expected %d, got %d for req %+v", http.StatusBadRequest, w.Code, req)
}
}
}

func TestTakeExternalSnapshot_GetDomainError(t *testing.T) {
h := newTestHandler(domainErrMock())
r := testutil.MakeRequest(t, TakeExternalSnapshotRequest{
UUID: "test-uuid",
SnapKey: "snap1",
PresignedURL: "http://example.com/presigned",
})
w := httptest.NewRecorder()

h.TakeExternalSnapshot(w, r)

if w.Code != http.StatusInternalServerError {
t.Errorf("expected %d, got %d", http.StatusInternalServerError, w.Code)
}
}

// DeleteSnapshot

func TestDeleteSnapshot_BadRequest(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func InitServer(portNum int, h Handlers, logger *zap.Logger) {
mux.HandleFunc("POST /RevertExternalSnapshot", h.Snapshot.RevertExternalSnapshot)
mux.HandleFunc("POST /MergeExternalSnapshot", h.Snapshot.MergeExternalSnapshot)
mux.HandleFunc("POST /DeleteSnapshot", h.Snapshot.DeleteSnapshot)
mux.HandleFunc("POST /TakeExternalSnapshot", h.Snapshot.TakeExternalSnapshot)

mux.HandleFunc("GET /metrics", h.Metric.DefaultMetric().ServeHTTP)

Expand Down
17 changes: 9 additions & 8 deletions pkg/types/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package vmtypes
import network "github.com/easy-cloud-Knet/KWS_Core/internal/net"

type VM_Init_Info struct {
DomName string `json:"domName"`
UUID string `json:"uuid"`
OS string `json:"os"`
HardwareInfo HardwareInfo `json:"HWInfo"`
NetConf network.NetDefine `json:"network"`
Users []User_info_VM `json:"users"`
SDNUUID string `json:"sdnUUID"`
MacAddr string `json:"macAddr"`
DomName string `json:"domName"`
UUID string `json:"uuid"`
OS string `json:"os"`
PresignedImageUrl string `json:"presignedImageUrl,omitempty"`
HardwareInfo HardwareInfo `json:"HWInfo"`
NetConf network.NetDefine `json:"network"`
Users []User_info_VM `json:"users"`
SDNUUID string `json:"sdnUUID"`
MacAddr string `json:"macAddr"`
}

type HardwareInfo struct {
Expand Down
62 changes: 61 additions & 1 deletion services/creation/generate_files.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package creation

import (
"context"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"time"

"github.com/easy-cloud-Knet/KWS_Core/internal/config"
virerr "github.com/easy-cloud-Knet/KWS_Core/internal/error"
Expand All @@ -16,7 +21,7 @@ func (DB localConfigurer) CreateDiskImage(dirPath string, diskSize int) error {
"-b", baseImage,
"-f", "qcow2",
"-F", "qcow2",
targetImage, fmt.Sprintf("%dG", diskSize), // 10G
targetImage, fmt.Sprintf("%dG", diskSize),
)
if err := qemuImgCmd.Run(); err != nil {
errorDescription := fmt.Errorf("generating Disk image error, check duplicated uuid or lack of HD capacity, or validity for base img %s, %v", dirPath, err)
Expand All @@ -26,6 +31,61 @@ func (DB localConfigurer) CreateDiskImage(dirPath string, diskSize int) error {
return nil
}

// ensureBaseImage checks if the base image exists locally.
// If not, it downloads it from presignedURL to the target path atomically.
func ensureBaseImage(path, presignedURL string) error {
if _, err := os.Stat(path); err == nil {
return nil
}
Comment on lines +37 to +39

if presignedURL == "" {
return fmt.Errorf("base image not found at %s and no presignedImageUrl provided", path)
}

if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return fmt.Errorf("failed to create baseimg directory: %w", err)
}

tmpPath := path + ".tmp"

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, presignedURL, nil)
if err != nil {
return fmt.Errorf("failed to build download request: %w", err)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to download base image: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("download base image returned status %d", resp.StatusCode)
}

tmp, err := os.Create(tmpPath)
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
Comment on lines +49 to +72

if _, err := io.Copy(tmp, resp.Body); err != nil {
tmp.Close()
os.Remove(tmpPath)
return fmt.Errorf("failed to write base image: %w", err)
}
tmp.Close()

if err := os.Rename(tmpPath, path); err != nil {
os.Remove(tmpPath)
return fmt.Errorf("failed to place base image: %w", err)
}

return nil
}

func (DB localConfigurer) CreateISOFile(dirPath string) error {

isoOutput := filepath.Join(dirPath, "cidata.iso")
Expand Down
Loading
Loading