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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
CLAUDE.md

# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,goland+all
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,goland+all

Expand All @@ -18,7 +20,6 @@ out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# 빌드 및 실행 단계
FROM golang:1.23
FROM golang:1.24

# 필수 패키지 설치 (libvirt 개발 패키지 포함)
RUN apt-get update && apt-get install -y \
Expand Down
4 changes: 4 additions & 0 deletions api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func Server(portNum int, contextStruct *vms.ControlContext, rdb *redis.Client) e
http.HandleFunc("GET /vm/info", withSecurityHeaders(h.vmInfo))
http.HandleFunc("POST /vm/start", withSecurityHeaders(h.startVm))

http.HandleFunc("POST /vm/snapshot", withSecurityHeaders(h.takeSnapshot))
http.HandleFunc("GET /vm/snapshot", withSecurityHeaders(h.listSnapshots))
http.HandleFunc("DELETE /vm/snapshot", withSecurityHeaders(h.deleteSnapshot))

fmt.Printf("Running server on port %d\n", portNum)
err := http.ListenAndServe(":"+strconv.Itoa(portNum), nil)
if err != nil {
Expand Down
85 changes: 85 additions & 0 deletions api/snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package api

import (
"encoding/json"
"net/http"

"github.com/easy-cloud-Knet/KWS_Control/service"
"github.com/easy-cloud-Knet/KWS_Control/structure"
"github.com/easy-cloud-Knet/KWS_Control/util"
)

type ApiTakeSnapshotRequest struct {
UUID structure.UUID `json:"uuid"`
SnapName string `json:"snapName"`
}

type ApiDeleteSnapshotRequest struct {
UUID structure.UUID `json:"uuid"`
SnapKey string `json:"snapKey"`
}

func (c *handlerContext) takeSnapshot(w http.ResponseWriter, r *http.Request) {
log := util.GetLogger()
defer r.Body.Close()

var req ApiTakeSnapshotRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
util.RespondError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.UUID == "" || req.SnapName == "" {
util.RespondError(w, http.StatusBadRequest, "uuid and snapName are required")
return
}

if err := service.TakeSnapshot(req.UUID, req.SnapName, c.context); err != nil {
log.Error("takeSnapshot: %v", err, true)
util.RespondError(w, http.StatusInternalServerError, err.Error())
return
}
Comment thread
kwonkwonn marked this conversation as resolved.

util.RespondJSON(w, http.StatusOK, map[string]string{"message": "snapshot taken"})
}

func (c *handlerContext) listSnapshots(w http.ResponseWriter, r *http.Request) {
log := util.GetLogger()

uuid := structure.UUID(r.URL.Query().Get("uuid"))
if uuid == "" {
util.RespondError(w, http.StatusBadRequest, "uuid query parameter is required")
return
}

keys, err := service.ListSnapshots(uuid)
if err != nil {
log.Error("listSnapshots: %v", err, true)
util.RespondError(w, http.StatusInternalServerError, err.Error())
return
}

util.RespondJSON(w, http.StatusOK, map[string][]string{"snapshots": keys})
}

func (c *handlerContext) deleteSnapshot(w http.ResponseWriter, r *http.Request) {
log := util.GetLogger()
defer r.Body.Close()

var req ApiDeleteSnapshotRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
util.RespondError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.UUID == "" || req.SnapKey == "" {
util.RespondError(w, http.StatusBadRequest, "uuid and snapKey are required")
return
}

if err := service.DeleteSnapshot(req.UUID, req.SnapKey); err != nil {
log.Error("deleteSnapshot: %v", err, true)
util.RespondError(w, http.StatusInternalServerError, err.Error())
return
}

w.WriteHeader(http.StatusOK)
}
14 changes: 14 additions & 0 deletions client/model/snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package model

import "github.com/easy-cloud-Knet/KWS_Control/structure"

// TakeSnapshotRequest asks Core to take an external snapshot and upload to RustFS via presigned PUT URL.
type TakeSnapshotRequest struct {
UUID structure.UUID `json:"uuid"`
SnapKey string `json:"snapKey"`
PresignedURL string `json:"presignedUrl,omitempty"`
}

type TakeSnapshotResponse struct {
Message string `json:"message,omitempty"`
}
21 changes: 11 additions & 10 deletions client/model/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ type UserInfoVM struct {
}

type CreateVMRequest struct {
DomType string `json:"domType"`
DomName string `json:"domName"`
UUID structure.UUID `json:"uuid"`
OS string `json:"os"`
HardwareInfo HardwareInfo `json:"HWInfo"`
NetConf NetDefine `json:"network"`
Users []UserInfoVM `json:"users"`
SdnUUID string `json:"sdnUUID"`
MacAddr string `json:"macAddr"`
Subnettype string `json:"Subnettype"`
DomType string `json:"domType"`
DomName string `json:"domName"`
UUID structure.UUID `json:"uuid"`
OS string `json:"os"`
HardwareInfo HardwareInfo `json:"HWInfo"`
NetConf NetDefine `json:"network"`
Users []UserInfoVM `json:"users"`
SdnUUID string `json:"sdnUUID"`
MacAddr string `json:"macAddr"`
Subnettype string `json:"Subnettype"`
PresignedImageURL string `json:"presignedImageUrl,omitempty"`
}

type DomainDeleteType uint
Expand Down
158 changes: 158 additions & 0 deletions client/rustfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package client

import (
"context"
"errors"
"fmt"
"os"
"sync"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)

type RustFSClient struct {
client *s3.Client
}

var (
rustFSInstance *RustFSClient
rustFSOnce sync.Once
rustFSErr error
)

func GetRustFSClient() (*RustFSClient, error) {
rustFSOnce.Do(func() {
rustFSInstance, rustFSErr = NewRustFSClient()
})
return rustFSInstance, rustFSErr
}

// Currently RustFSClient has solid s3.Client as its field,
// when logic get complex and needs for mocking arise, replace it with interface

func NewRustFSClient() (*RustFSClient, error) {
endpoint := os.Getenv("RUSTFS_ENDPOINT")
if endpoint == "" {
endpoint = "http://localhost:9000"
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minioadmin<-- docker-compose.test.yaml 파일의 인자를 일단 덮어쓰도록 했습니다.
control 단에서 accesskey 넘어가는건 마지막 단계에서 할 거 같아요

Comment thread
kwonkwonn marked this conversation as resolved.
accessKey := os.Getenv("RUSTFS_ACCESS_KEY")
if accessKey == "" {
accessKey = "minioadmin"
}
secretKey := os.Getenv("RUSTFS_SECRET_KEY")
if secretKey == "" {
secretKey = "minioadmin"
}
//TODO: replace unstable accesskey override when testing with proper method

cfg, err := config.LoadDefaultConfig(context.Background(),
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
)
if err != nil {
return nil, err
}

client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String(endpoint)
o.UsePathStyle = true
})

return &RustFSClient{client: client}, nil
}

func (c *RustFSClient) ListBuckets(ctx context.Context) ([]string, error) {
out, err := c.client.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return nil, err
}
names := make([]string, len(out.Buckets))
for i, b := range out.Buckets {
names[i] = aws.ToString(b.Name)
}
return names, nil
}

func (c *RustFSClient) CreateBucket(ctx context.Context, bucket string) error {
_, err := c.client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
return err
}

// Refer: https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3#NewPresignClient
// Direct listing, generating bucket and presigned URL is the role for RustFSClient(control)
// This Presigned url should be passed to Core client, and core should use this url to upload/download file.
func (c *RustFSClient) PresignPutObject(ctx context.Context, bucket, key string, expires time.Duration) (string, error) {
presignClient := s3.NewPresignClient(c.client)
req, err := presignClient.PresignPutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
}, s3.WithPresignExpires(expires))
if err != nil {
return "", err
}
return req.URL, nil
}

func (c *RustFSClient) PresignGetObject(ctx context.Context, bucket, key string, expires time.Duration) (string, error) {
presignClient := s3.NewPresignClient(c.client)
req, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
}, s3.WithPresignExpires(expires))
if err != nil {
return "", err
}
return req.URL, nil
}

// HeadObject returns (true, nil) if the object exists, (false, nil) if only the object is missing,
// and (false, err) for all other errors including NoSuchBucket.
func (c *RustFSClient) HeadObject(ctx context.Context, bucket, key string) (bool, error) {
_, err := c.client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
var notFound *types.NotFound
if errors.As(err, &notFound) {
return false, nil
}
return false, fmt.Errorf("HeadObject %s/%s: %w", bucket, key, err)
}
return true, nil
}

// ListObjects returns all object keys in the bucket matching the prefix.
// Returns an empty slice when no objects match. Returns an error if the bucket does not exist.
func (c *RustFSClient) ListObjects(ctx context.Context, bucket, prefix string) ([]string, error) {
out, err := c.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
Prefix: aws.String(prefix),
})
if err != nil {
return nil, fmt.Errorf("ListObjects %s: %w", bucket, err)
}
keys := make([]string, len(out.Contents))
for i, obj := range out.Contents {
keys[i] = aws.ToString(obj.Key)
}
return keys, nil
}
Comment thread
kwonkwonn marked this conversation as resolved.

func (c *RustFSClient) DeleteObject(ctx context.Context, bucket, key string) error {
_, err := c.client.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
return fmt.Errorf("DeleteObject %s/%s: %w", bucket, key, err)
}
return nil
}
22 changes: 20 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
module github.com/easy-cloud-Knet/KWS_Control

go 1.23.0
go 1.24

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.23.0 <-> aws sdk 와 호환성 문제가 있어서 업데이트 했는데 혹시 관련해서 문제 있을까요


toolchain go1.23.4
toolchain go1.24.3

require (
github.com/aws/aws-sdk-go-v2 v1.41.7
github.com/aws/aws-sdk-go-v2/config v1.32.18
github.com/aws/aws-sdk-go-v2/credentials v1.19.17
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0
github.com/go-sql-driver/mysql v1.9.2
github.com/redis/go-redis/v9 v9.11.0
github.com/sirupsen/logrus v1.9.3
Expand All @@ -15,6 +19,20 @@ require (

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect
github.com/aws/smithy-go v1.25.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
golang.org/x/sys v0.33.0 // indirect
Expand Down
36 changes: 36 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY=
github.com/aws/aws-sdk-go-v2/config v1.32.18 h1:Hcia46bxhGgF3BaSnG8nSNCWmqTK6bj9xN9/FJ3WK6Q=
github.com/aws/aws-sdk-go-v2/config v1.32.18/go.mod h1:zEjCAYmxqDadH1WX8CdBvmLKhUEUVFgKRQG38zjDmrY=
github.com/aws/aws-sdk-go-v2/credentials v1.19.17 h1:gP2nkGsS+KMvF/jfFz2Vv2qiiOqWKyPACSzPsqHgoW8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.17/go.mod h1:Bsew3S/moG5iT77giPj1q8wb/s0RE5/QfH+ASjYtuQc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.0 h1:nDARhv/oF55bcxF7rCI/4PDxOKnVXVWwDuDwCs2I2SQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.0/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio=
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
Expand Down
Loading