Skip to content
Merged

hh #62

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
77909ca
Merge pull request #51 from easy-cloud-Knet/autoconfig
Jbchan01 Jul 29, 2025
1213caf
boot instance method added
kwonkwonn Aug 19, 2025
cb875ad
Merge pull request #53 from easy-cloud-Knet/boot
kwonkwonn Aug 19, 2025
cbfeb62
handler added
kwonkwonn Aug 19, 2025
4686c8f
Merge pull request #54 from easy-cloud-Knet/autoconfig
Jbchan01 Aug 27, 2025
abb2a69
disk size specification added
kwonkwonn Oct 1, 2025
5126e77
erge branch 'main' of https://github.com/easy-cloud-Knet/KWS_Core
kwonkwonn Oct 1, 2025
67c5ae8
feat/fetching os information per booting option added
kwonkwonn Oct 31, 2025
35b434a
mit licnese added for viability
kwonkwonn Nov 14, 2025
0d378e1
mit licnese added for viability
kwonkwonn Nov 14, 2025
52d2afa
test function added
kwonkwonn Nov 19, 2025
443987b
looking for best structure
kwonkwonn Nov 19, 2025
4da999b
base operation for adding instsance specs done, needs test and sysncr…
kwonkwonn Nov 21, 2025
00aabcd
all functions for retreving both alive,dead vm status implremented, n…
kwonkwonn Nov 22, 2025
c1c40f2
vcpu status retreving done, need to add abstraction structure to use …
kwonkwonn Nov 23, 2025
0c43e76
Merge pull request #55 from easy-cloud-Knet/vcpu_info
kwonkwonn Nov 23, 2025
a6150d1
fixed operation to be done atomic
kwonkwonn Nov 23, 2025
d7d3e0e
Merge pull request #56 from easy-cloud-Knet/vcpu_info
kwonkwonn Nov 23, 2025
cae7080
add log printing ont deleting domain than only responsing it through …
kwonkwonn Nov 24, 2025
b6bd335
retreving vcpu error while domain is inactive specified, need further…
kwonkwonn Nov 24, 2025
f97cce0
Merge pull request #58 from easy-cloud-Knet/vcpu_info
kwonkwonn Nov 24, 2025
ab12948
Implement internal snapshot logic
Jbchan01 Dec 24, 2025
56a8862
internal_snapshot
Jbchan01 Jan 4, 2026
40ce344
internal_snapshot
Jbchan01 Jan 4, 2026
bbb64ee
Merge pull request #61 from easy-cloud-Knet/snapshot
kwonkwonn Jan 5, 2026
83db3e8
remove unnecessary build file
kwonkwonn Jan 11, 2026
a327339
remove unnecessary fmt.printing from code base
kwonkwonn Jan 11, 2026
78b5aef
readme file added for Error standardization
kwonkwonn Jan 11, 2026
044de50
Merge pull request #66 from easy-cloud-Knet/feat/increase-logging-vis…
Jbchan01 Jan 14, 2026
3631a1f
Add CLAUDE.md for grepping code structure easily
kwonkwonn Feb 14, 2026
9902602
feat/ Readme Updated
kwonkwonn Feb 14, 2026
14a9cbb
refactor debugging output
kwonkwonn Feb 14, 2026
a4a8adc
refactor: wrap raw error with virerr.errorGen
kwonkwonn Feb 14, 2026
c014ae0
refactor: enhance logger middleware and fix server logger
kwonkwonn Feb 14, 2026
cf33ed1
Merge pull request #74 from easy-cloud-Knet/refatcor/enhance-logging
kwonkwonn Feb 15, 2026
6936f88
Divide current all-in-one type into seperated req/res model for each …
kwonkwonn Feb 18, 2026
3e9a5a9
Modify req/resp's name for clarity
kwonkwonn Feb 18, 2026
59862b3
Merge pull request #77 from easy-cloud-Knet/restruct_api_models
kwonkwonn Feb 22, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
KWS_*
136 changes: 136 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# CLAUDE.md — KWS_Core

## Project Overview

KWS_Core is a personal cloud management system that wraps libvirt (QEMU/KVM) with a REST API for VM lifecycle management.

- **Module**: `github.com/easy-cloud-Knet/KWS_Core`
- **Language**: Go 1.24.0
- **Key dependencies**: `libvirt.org/go/libvirt`, `go.uber.org/zap`, `github.com/shirou/gopsutil`, `gopkg.in/yaml.v3`

## Build & Run

```bash
make conf # First-time setup (installs Go, dependencies, monitoring)
make build # Compile → produces KWS_Core binary in project root
make run # Build + execute locally
make clean # Remove binary
```

**Deploy**: `sudo systemctl start kws_core`
**Logs**: `/var/log/kws/{YYYYMMDD}.log` + stdout

## Architecture

```
HTTP Server (:8080)
API Handlers (api/) — methods on InstHandler, JSON request/response
Service Layer (vm/service/) — creation, termination, status, snapshot
Domain Controller (DomCon/) — in-memory VM registry, thread-safe
Libvirt (QEMU/KVM)
```

**Key directories**:
| Directory | Purpose |
|-----------|---------|
| `api/` | HTTP handlers, request/response types, generic `BaseResponse[T]` |
| `server/` | HTTP mux setup, route registration, logger middleware |
| `DomCon/` | In-memory domain map, mutex-guarded, domain seeking/caching |
| `DomCon/domain_status/` | CPU/memory accounting with atomic ops |
| `vm/service/creation/` | VM creation via `VMCreator` interface + factory |
| `vm/service/termination/` | Shutdown/deletion via `DomainTermination`/`DomainDeletion` interfaces |
| `vm/service/status/` | Host and VM metrics, `DataTypeHandler` router |
| `vm/service/snapshot/` | Snapshot lifecycle management |
| `vm/parsor/` | XML config generation, cloud-init YAML parsing |
| `error/` | Custom `VirError` types, `ErrorDescriptor`, `ErrorGen`/`ErrorJoin` |
| `logger/` | zap-based structured logger, HTTP middleware |
| `net/` | Network configuration parsing |

**Entry point**: `main.go` → init logger → create DomListControl → connect libvirt → load domains → start server

## Code Patterns (must follow)

### Handler pattern
All HTTP endpoints are receiver methods on `InstHandler` (`api/type.go`). Standard flow:
1. Create param struct + `ResponseGen[T](message)`
2. Decode body with `HttpDecoder(r, param)`
3. Validate / retrieve domain
4. Call service layer
5. Return `resp.ResponseWriteOK(w, data)` or `resp.ResponseWriteErr(w, err, status)`

### Factory pattern
Object creation uses `*Factory()` functions. Examples:
- `DomSeekUUIDFactory()` — domain lookup
- `DomainTerminatorFactory()` / `DomainDeleterFactory()` — shutdown/delete
- `LocalConfFactory()` / `LocalCreatorFactory()` — VM creation
- `DomListConGen()` — domain list controller

### Interface-first design
Define interfaces before implementations:
- `VMCreator` — `CreateVM() (*libvirt.Domain, error)`
- `DomainTermination` — `TerminateDomain() (*libvirt.Domain, error)`
- `DomainDeletion` — `DeleteDomain() (*libvirt.Domain, error)`
- `DataTypeHandler` — `GetInfo(*domCon.Domain) error`
- `DomainSeeker` — `ReturnDomain() (*Domain, error)`

### Thread safety
- `sync.Mutex` on `DomListControl` (list-level) and `Domain` (domain-level)
- `atomic.AddInt64` for CPU counter operations in `DomainListStatus`
- Lock → defer Unlock pattern throughout

### Error handling
Use the custom error package (`error/Error.go`), not raw `errors.New()`:
```go
// Create a typed error
virerr.ErrorGen(virerr.DomainGenerationError, fmt.Errorf("detail: %w", err))

// Chain errors with context
virerr.ErrorJoin(existingErr, fmt.Errorf("additional context"))
```
Predefined error constants: `NoSuchDomain`, `DomainGenerationError`, `LackCapacityCPU`, `LackCapacityRAM`, `InvalidUUID`, `InvalidParameter`, etc.

### VM storage convention
All VM artifacts live in `/var/lib/kws/{uuid}/` — disk images (`{uuid}.qcow2`), cloud-init ISO (`cidata.iso`), config files.

## Logging Rules

- Use `*zap.Logger` injected via `InstHandler`. **Never** use `fmt.Println`, `fmt.Printf`, or `log.*` for runtime logs.
- Log only at **API/handler** and **service** layers. Lower layers return errors; callers decide how to log.
- **Levels**: Debug (dev detail), Info (operations), Warn (recoverable), Error (with `zap.Error(err)`), Fatal/Panic (bootstrap only).
- **Standard fields**: `component`, `uuid`, `request_id`, `error`.
- Prefix error logs with where the error occurred for debugging context.
- Never log raw pointers or entire internal structs.

```go
// Good
logger.Error("failed to create vm", zap.String("uuid", uuid), zap.Error(err))

// Bad
fmt.Println("error:", err)
```

## Behavioral Guidelines

### Think before coding
- State assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them — don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.

### Simplicity first
- Minimum code that solves the problem. No speculative features.
- No abstractions for single-use code. No "flexibility" that wasn't requested.
- No error handling for impossible scenarios.

### Surgical changes
- Touch only what you must. Don't "improve" adjacent code or formatting.
- Match existing style, even if you'd do it differently.
- Remove imports/variables/functions that YOUR changes made unused.
- Every changed line should trace directly to the request.

### Goal-driven execution
- Transform tasks into verifiable goals before implementing.
- For multi-step tasks, state a brief plan with verification steps.
13 changes: 13 additions & 0 deletions DomCon/DomainUpdate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package domCon

// Di injector for domain xml unparse and update

// func (DC *DomListControl) UpdateDomainStatus(Dom *Domain, logger *zap.Logger) error {

// domcnf, err := domStatus.XMLUnparse(Dom.Domain)
// if err != nil {
// logger.Error("failed to unparse domain XML", zap.Error(err))
// return err
// }

// }
18 changes: 18 additions & 0 deletions DomCon/control_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package domCon
import (
"sync"

domStatus "github.com/easy-cloud-Knet/KWS_Core/DomCon/domain_status"
"libvirt.org/go/libvirt"
)

Expand All @@ -11,20 +12,37 @@ import (
// DomainList 에서 uuid형태로 각각의 도메인을 관리






type DomListControl struct {
DomainList map[string]*Domain
domainListMutex sync.Mutex
DomainListStatus * domStatus.DomainListStatus
}
// 각 도메인을 관리하는 인메모리 구조체
// mutex 를 통해 동시성 제어
// 메모리 누수방지 + libvirt 접근 최소화 위해 libvirt.Domain 포인터를 보유



type Domain struct {
Domain *libvirt.Domain
domainMutex sync.Mutex
}
// 각 도메인의 상태변경시에 사용하는 구조체
// mutex 를 통해 동시성 제어



type DomainSeekingByUUID struct {
LibvirtInst *libvirt.Connect
UUID string
}
// 도메인 탐색 인터페이스
// 인메모리 도메인 리스트에 없을 경우 libvirt 에서 도메인을 찾아 반환하는 역할


type DomainSeeker interface {
ReturnDomain() (*Domain, error)
Expand Down
52 changes: 40 additions & 12 deletions DomCon/domain_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"sync"

domStatus "github.com/easy-cloud-Knet/KWS_Core/DomCon/domain_status"
virerr "github.com/easy-cloud-Knet/KWS_Core/error"
"go.uber.org/zap"
"libvirt.org/go/libvirt"
Expand All @@ -20,46 +21,61 @@ func DomListConGen() *DomListControl {
return &DomListControl{
domainListMutex: sync.Mutex{},
DomainList: make(map[string]*Domain),
DomainListStatus: &domStatus.DomainListStatus{},
}
}// 전역적으로 사용되는 도메인 리스트 컨트롤러 생성


func (DC *DomListControl) AddNewDomain(domain *Domain, uuid string) error {
DC.domainListMutex.Lock()
defer DC.domainListMutex.Unlock()

DC.DomainList[uuid] = domain
vcpu, err :=domain.Domain.GetMaxVcpus()
if err != nil {
return virerr.ErrorGen(virerr.DomainGenerationError, fmt.Errorf("error while getting vcpu count during adding new domain: %w", err))
}
DC.DomainListStatus.AddAllocatedCPU(int(vcpu))
return nil
}

func (DC *DomListControl) AddNewDomain(domain *Domain, uuid string) {
func (DC *DomListControl) AddExistingDomain(domain *Domain, uuid string) {
DC.domainListMutex.Lock()
defer DC.domainListMutex.Unlock()

DC.DomainList[uuid] = domain
}
// Exstring Domain only called from initial booting, and adding specs is not its role

func (DC *DomListControl) GetDomain(uuid string, LibvirtInst *libvirt.Connect) (*Domain, error) {
fmt.Println(DC)
DC.domainListMutex.Lock()
domain, Exist := DC.DomainList[uuid]
DC.domainListMutex.Unlock()
if !Exist {
DomainSeeker := DomSeekUUIDFactory(LibvirtInst, uuid)
dom, err := DomainSeeker.ReturnDomain()
if err != nil {
fmt.Println(err)
return nil, err
}
fmt.Println(dom)
DC.AddNewDomain(dom, uuid)
return dom, nil
}
fmt.Println(domain)

return domain, nil
}

func (DC *DomListControl) DeleteDomain(Domain *libvirt.Domain, uuid string) error {
func (DC *DomListControl) DeleteDomain(Domain *libvirt.Domain, uuid string, vcpu int) error {
DC.domainListMutex.Lock()
delete(DC.DomainList, uuid)
Domain.Free()
DC.domainListMutex.Unlock()
DC.DomainListStatus.TakeAllocatedCPU(vcpu)
return nil
}

func (DC *DomListControl) FindAndDeleteDomain(LibvirtInst *libvirt.Connect, uuid string) error {
//아직 활용처가 없어서, vcpu 삭제를 추가하지 않았음/
// DeleteDomain 함수의 TakeAllocatedCPU 호출을 참고..
DC.domainListMutex.Lock()
domain, Exist := DC.DomainList[uuid]
DC.domainListMutex.Unlock()
Expand Down Expand Up @@ -90,24 +106,34 @@ func (DC *DomListControl) retrieveDomainsByState(LibvirtInst *libvirt.Connect, s
return err
}

DC.domainListMutex.Lock()
defer DC.domainListMutex.Unlock()

dataDog := domStatus.NewDataDog(state)
wg:= &sync.WaitGroup{}
for _, dom := range domains {
uuid, err := dom.GetUUIDString()
if err != nil {
logger.Sugar().Error("Failed to get UUID for domain", err)
continue
}

DC.DomainList[uuid] = &Domain{
NewDom:= &Domain{
Domain: &dom,
domainMutex: sync.Mutex{},
}
// logger.Infof("Added domain: UUID=%s", uuid)
DC.AddExistingDomain(NewDom,uuid)

wg.Add(1)
go func(targetDom libvirt.Domain) {
defer wg.Done()
retrieveFunc := dataDog.Retreive(&targetDom, DC.DomainListStatus, *logger)
if retrieveFunc != nil {
logger.Sugar().Errorf("Failed to retrieve status for domain UUID=%s: %v", uuid, retrieveFunc)
}

}(dom)
logger.Sugar().Infof("Added domain: UUID=%s", uuid)
}

wg.Wait()

logger.Sugar().Infof("Total %d domains added (state: %d)", len(domains), state)
return nil
}
Expand Down Expand Up @@ -139,3 +165,5 @@ func (DC *DomListControl) GetAllUUIDs() []string {
}
return uuids
}

////////////////////////////////////////////////
34 changes: 34 additions & 0 deletions DomCon/domain_status/cpu_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package domStatus

import (
"runtime"
"sync/atomic"
)

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

func (dls *DomainListStatus) AddAllocatedCPU(vcpu int) error {
atomic.AddInt64(&dls.VcpuAllocated, int64(vcpu))
return nil
}

func (dls *DomainListStatus) AddSleepingCPU(vcpu int) error {
atomic.AddInt64(&dls.VcpuSleeping, int64(vcpu))
return nil
}
func (dls *DomainListStatus) TakeAllocatedCPU(vcpu int) error {

atomic.AddInt64(&dls.VcpuAllocated, -int64(vcpu))
return nil
}

func (dls *DomainListStatus) TakeSleepingCPU(vcpu int) error {

atomic.AddInt64(&dls.VcpuSleeping, -int64(vcpu))
return nil
}


Loading