From 0839ea5a06a3253ba07444105af1f55a090081e4 Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 12:59:31 +0800 Subject: [PATCH 1/9] build: bump dependencies, golangci-lint v2.14.0, gofumpt v0.12.0, goimports v0.51.0 --- .github/workflows/lint.yml | 2 +- Makefile | 6 +-- go.mod | 40 +++++++------- go.sum | 107 ++++++++++++++++++------------------- 4 files changed, 75 insertions(+), 80 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dac95429..89c606c4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v9.3.0 with: - version: v2.13.2 + version: v2.14.0 - name: Check formatting (gofumpt + goimports) run: golangci-lint fmt --diff diff --git a/Makefile b/Makefile index 4b0609e7..8b4a4bd8 100644 --- a/Makefile +++ b/Makefile @@ -21,15 +21,15 @@ $(LOCALBIN): mkdir -p $(LOCALBIN) ## Tool versions -GOLANGCILINT_VERSION ?= v2.13.2 +GOLANGCILINT_VERSION ?= v2.14.0 GOLANGCILINT_ROOT := $(LOCALBIN)/golangci-lint-$(GOLANGCILINT_VERSION) GOLANGCILINT := $(GOLANGCILINT_ROOT)/golangci-lint -GOFUMPT_VERSION ?= v0.11.0 +GOFUMPT_VERSION ?= v0.12.0 GOFUMPT_ROOT := $(LOCALBIN)/gofumpt-$(GOFUMPT_VERSION) GOFMT := $(GOFUMPT_ROOT)/gofumpt -GOIMPORTS_VERSION ?= v0.49.0 +GOIMPORTS_VERSION ?= v0.51.0 GOIMPORTS_ROOT := $(LOCALBIN)/goimports-$(GOIMPORTS_VERSION) GOIMPORTS := $(GOIMPORTS_ROOT)/goimports diff --git a/go.mod b/go.mod index 325482dc..6313e451 100644 --- a/go.mod +++ b/go.mod @@ -12,61 +12,59 @@ require ( github.com/gofrs/flock v0.13.1 github.com/google/go-containerregistry v0.22.1 github.com/google/uuid v1.6.0 - github.com/klauspost/compress v1.20.0 + github.com/klauspost/compress v1.20.3 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 - github.com/projecteru2/core v0.1.5 + github.com/projecteru2/core v0.1.7 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 - golang.org/x/sync v0.23.0 - golang.org/x/sys v0.48.0 - modernc.org/sqlite v1.59.0 + golang.org/x/sync v0.24.0 + golang.org/x/sys v0.49.0 + modernc.org/sqlite v1.60.1 ) require ( - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/errors v1.14.0 // indirect github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect github.com/cockroachdb/redact v1.1.8 // indirect - github.com/docker/cli v29.8.0+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.8 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/getsentry/sentry-go v0.49.0 // indirect + github.com/docker/cli v29.9.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.10 // indirect + github.com/dustin/go-humanize v1.1.0 // indirect + github.com/getsentry/sentry-go v0.50.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-colorable v0.1.16 // indirect github.com/mattn/go-isatty v0.0.24 // indirect - github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/ncruces/go-strftime v1.1.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.16.0 // indirect github.com/rs/zerolog v1.35.1 // indirect - github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/sirupsen/logrus v1.10.1 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/sirupsen/logrus v1.10.2 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/trace v1.45.0 // indirect + go.opentelemetry.io/otel v1.47.0 // indirect + go.opentelemetry.io/otel/trace v1.47.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/mod v0.40.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/text v0.43.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260918162117-cecb64721679 // indirect - google.golang.org/grpc v1.83.2 // indirect + google.golang.org/grpc v1.84.0 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gotest.tools/v3 v3.5.2 // indirect - modernc.org/libc v1.75.7 // indirect + modernc.org/libc v1.79.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.12.1 // indirect ) diff --git a/go.sum b/go.sum index 41e85f94..ce4e163e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc h1:58s5YzGPNAP7+e1uemaL0ax/NPSeJOHdRpRIowd+JDk= +github.com/Azure/go-ansiterm v0.0.0-20261013165250-51075c6ea6bc/go.mod h1:DMNF6Kxv+EcuUvVuNkvC3f+BjEEmc/Fn9aU+qiD8zpU= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -20,20 +20,20 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/docker/cli v29.8.0+incompatible h1:ih0c2jq/nN7QfES8zIfwSzIhRScjs4ehER+kZ60aeSk= -github.com/docker/cli v29.8.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= -github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/cli v29.9.1+incompatible h1:E4YGy7TMkPk7mFsXTVUyjHxVeJdjTDfIS0X4Q0+ioQ0= +github.com/docker/cli v29.9.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker-credential-helpers v0.9.10 h1:LCWwv7UmS7fQVmdKDWeMuureOeY5kkbC5kZfkzxkS6k= +github.com/docker/docker-credential-helpers v0.9.10/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= +github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/getsentry/sentry-go v0.49.0 h1:Ehejknu1l023Ub7QoRBVLAI7g3Jnhqku4oWx4B4Sh5s= -github.com/getsentry/sentry-go v0.49.0/go.mod h1:nuMJAoCfe1u0Bts2ocyNI+TW8HT84vRMqwA5Qq/SKUI= +github.com/getsentry/sentry-go v0.50.0 h1:gkO59rn/i0RCAXUUTflC5QVbTQGPXbaFabs2olVzc9g= +github.com/getsentry/sentry-go v0.50.0/go.mod h1:+xsDuN+SoRN/xfs1hdq78wnrE3omIZf+FCuh9FOW1C8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= @@ -62,20 +62,20 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= -github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4= +github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= -github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-colorable v0.1.16 h1:+2YjeAe7xP1OQbZW1jERPWWCDLlNqFNCvjiqGiAt84A= +github.com/mattn/go-colorable v0.1.16/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= -github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/ncruces/go-strftime v1.1.0 h1:vtPmPWMkb539RGV9WpWT9EwJGvrOCKvMNI4obWL47Hk= +github.com/ncruces/go-strftime v1.1.0/go.mod h1:yVaq7iwCN09vHRrKBrrflXT0M9tTMG2KmZ9Fj7xeG/g= github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY= github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk= github.com/onsi/gomega v1.38.1 h1:FaLA8GlcpXDwsb7m0h2A9ew2aTk3vnZMlzFgg5tz/pk= @@ -91,8 +91,8 @@ github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTw github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/projecteru2/core v0.1.5 h1:cS3xfqa6I1bMXtGdbu2A2UzhjiHaP2mogjQWFTkSEF0= -github.com/projecteru2/core v0.1.5/go.mod h1:MDMJmmlJ3ZVvFQl5BnRcKV1/AVCHyiWoI4BI+sCnuYM= +github.com/projecteru2/core v0.1.7 h1:f2EdndjWFBYX6pqLZ68VcFYMkXZu6RivotaKUGCNwDQ= +github.com/projecteru2/core v0.1.7/go.mod h1:iVfdX4r9/qD52il7DbV18BFEp8fgoUMBp/60WMqFxPc= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -101,12 +101,10 @@ github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52Z github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q= -github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -132,18 +130,18 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM= +go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU= +go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= +go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw= +go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY= +go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU= +go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM= +go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= -go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= @@ -152,8 +150,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= -golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg= +golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -163,34 +161,33 @@ golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= -golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs= +golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20260918162117-cecb64721679 h1:KmqdJU4vrNcxy/6qdg3JduZtalEXrJLspVltnR1cE+8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260918162117-cecb64721679/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -198,20 +195,20 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8= -modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w= -modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I= +modernc.org/cc/v4 v4.30.3 h1:aDq8gIX1X7+VFgO/JYYji6pLWL2H4tv3g8WatT3KCys= +modernc.org/cc/v4 v4.30.3/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.37.0 h1:BAuJCUtIHIxFIYaFhqXIw5YaoMBlNCUlFovejHfMip4= +modernc.org/ccgo/v4 v4.37.0/go.mod h1:/8627VhwWtAbqUXmPORxFQU5SgLrAx2jl9seLw9GBhY= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc= -modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.2.0 h1:/Y3LGDMQaoYf55PebP1qsZ5FDGgAZyG1RYKEHeQzh1o= +modernc.org/gc/v3 v3.2.0/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.75.7 h1:o3DTP9/0p9pKmY2WCKQaySW6wIiZhNM7wc2lUoyhfew= -modernc.org/libc v1.75.7/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ= +modernc.org/libc v1.79.0 h1:9Dvvv++bAMSkSuBzuOFAatbTV5OIoq6Y6FohmD3e7q8= +modernc.org/libc v1.79.0/go.mod h1:uT3kR61NsbeKJSLqq83GJwSnZMVLlSjyAz4DfTaBy1M= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g= @@ -220,8 +217,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A= -modernc.org/sqlite v1.59.0/go.mod h1:+paeT2A3iPRHkQDwG7oA6Tk0zQd5woMEI8q7orfry8k= +modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw= +modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From 48a5e2813363d3c01de60aa8b7b2b62faa1b554a Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:01:56 +0800 Subject: [PATCH 2/9] fix(hypervisor): skip a delete-time VMM scan match whose cmdline is gone The scan that feeds the orphan sweep predates the force stop. A process that exited during the stop but is not yet reaped, such as Cloud Hypervisor's direct-boot pty child, still answers kill(0), so the sweep logged "killed orphan VMM" for a process TerminateProcess then left alone. Match the cmdline instead, as the start path does. --- hypervisor/stop.go | 2 +- hypervisor/stop_linux_test.go | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 hypervisor/stop_linux_test.go diff --git a/hypervisor/stop.go b/hypervisor/stop.go index 0171e042..2bafcf46 100644 --- a/hypervisor/stop.go +++ b/hypervisor/stop.go @@ -141,7 +141,7 @@ func (b *Backend) deleteOneLocked(ctx context.Context, id string, force bool, st } for _, pid := range procScan.Find(sockPath) { // procScan predates the stop: only a still-live match is a real orphan worth killing. - if !utils.IsProcessAlive(pid) { + if !utils.VerifyProcessCmdline(pid, b.Conf.BinaryName(), sockPath) { continue } if termErr := utils.TerminateProcess(ctx, pid, b.Conf.BinaryName(), sockPath, b.Conf.TerminateGracePeriod()); termErr != nil { diff --git a/hypervisor/stop_linux_test.go b/hypervisor/stop_linux_test.go new file mode 100644 index 00000000..48feb0bb --- /dev/null +++ b/hypervisor/stop_linux_test.go @@ -0,0 +1,68 @@ +package hypervisor + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "slices" + "strings" + "testing" + "time" + + "github.com/projecteru2/core/log" + coretypes "github.com/projecteru2/core/types" + + "github.com/cocoonstack/cocoon/utils" +) + +func TestDeleteSkipsAScannedVMMThatExitedBeforeTheKill(t *testing.T) { + ctx := t.Context() + logPath := filepath.Join(t.TempDir(), "delete.log") + if err := log.SetupLog(ctx, &coretypes.ServerLogConfig{Level: "info", Filename: logPath}, ""); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = log.SetupLog(ctx, &coretypes.ServerLogConfig{Level: "info"}, "") }) + + b, _ := newMeteringTestBackend(t) + const id = "vm-zombie-scan" + dir := seedStoppedVMWithDirs(t, b, id) + sockPath := SocketPath(dir) + + child := exec.Command("/bin/sh", "-c", "sleep 60; :", "sh", sockPath) + child.Args[0] = b.Conf.BinaryName() + if err := child.Start(); err != nil { + t.Fatalf("start fake VMM child: %v", err) + } + t.Cleanup(func() { _ = child.Wait() }) + scan, err := utils.ScanProcsByBinary(b.Conf.BinaryName()) + if err != nil { + t.Fatalf("scan: %v", err) + } + if !slices.Contains(scan.Find(sockPath), child.Process.Pid) { + t.Fatalf("scan misses the live fake VMM child %d", child.Process.Pid) + } + _ = child.Process.Kill() + deadline := time.Now().Add(2 * time.Second) + for utils.VerifyProcessCmdline(child.Process.Pid, b.Conf.BinaryName(), sockPath) { + if time.Now().After(deadline) { + t.Fatal("killed child kept its cmdline") + } + time.Sleep(10 * time.Millisecond) + } + if !utils.IsProcessAlive(child.Process.Pid) { + t.Fatal("precondition: the unreaped child must linger as a zombie") + } + + rec := recordOf(t, b, id) + if err := b.deleteOneLocked(ctx, id, false, nil, rec, scan); err != nil { + t.Fatalf("deleteOneLocked: %v", err) + } + out, err := os.ReadFile(logPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + t.Fatalf("read log: %v", err) + } + if strings.Contains(string(out), "killed orphan VMM") { + t.Errorf("delete reported killing an already-exited VMM:\n%s", out) + } +} From dc973d4892842d0ba335cc267c7966018a5c6969 Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:02:20 +0800 Subject: [PATCH 3/9] fix(firecracker): validate the disk detach name before locking the VM Cloud Hypervisor rejects an invalid detach name up front, while Firecracker took the ops lock and queried the VMM first, then reported success for a name that could never have been attached. Validate it the same way on both backends. --- hypervisor/firecracker/extend.go | 12 ++++++------ hypervisor/firecracker/extend_test.go | 8 ++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/hypervisor/firecracker/extend.go b/hypervisor/firecracker/extend.go index f03078ea..e686f7c0 100644 --- a/hypervisor/firecracker/extend.go +++ b/hypervisor/firecracker/extend.go @@ -100,19 +100,19 @@ func (fc *Firecracker) DiskAttach(ctx context.Context, vmRef string, spec disk.S } func (fc *Firecracker) DiskDetach(ctx context.Context, vmRef, name string) error { - if name == "" { - return fmt.Errorf("name is required") + if err := disk.ValidateName(name); err != nil { + return err } - hc, _, _, unlock, err := fc.lockedDeviceOp(ctx, vmRef, disk.ErrUnsupportedBackend) + id, err := hotDiskID(name) if err != nil { return err } - defer unlock() - cfg, err := getVMConfig(ctx, hc) + hc, _, _, unlock, err := fc.lockedDeviceOp(ctx, vmRef, disk.ErrUnsupportedBackend) if err != nil { return err } - id, err := hotDiskID(name) + defer unlock() + cfg, err := getVMConfig(ctx, hc) if err != nil { return err } diff --git a/hypervisor/firecracker/extend_test.go b/hypervisor/firecracker/extend_test.go index a71b33ce..93c8f6f2 100644 --- a/hypervisor/firecracker/extend_test.go +++ b/hypervisor/firecracker/extend_test.go @@ -101,6 +101,14 @@ func TestHotDiskID(t *testing.T) { } } +func TestDiskDetachRejectsAnInvalidNameBeforeLocking(t *testing.T) { + for _, name := range []string{"", "Bad/Name", "my-disk"} { + if err := (&Firecracker{}).DiskDetach(t.Context(), "vm1", name); err == nil { + t.Errorf("DiskDetach(%q) = nil, want a name rejection", name) + } + } +} + func TestRefuseHotAttachedDisks(t *testing.T) { srv := newFCStub(t, fcVMConfig{Drives: []fcDrive{{DriveID: "drive_0"}, {DriveID: hotDiskIDPrefix + "scratch", PathOnHost: "/vols/s.raw"}}}) err := refuseHotAttachedDisks(t.Context(), srv.client) From 7db98cdd419c0b725b0a231c81dfbaa563b3d8b6 Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:03:31 +0800 Subject: [PATCH 4/9] refactor: modern-go and reuse sweep DoWithRetry ranges over its attempts, the arm64 kernel gunzip check reuses utils.GzipMagic, storebench seeds through slices.Chunk, the image index write reuses the bytes it already marshaled for the diff, and size backfill reads the record's own data dir like the missing-dir check. The TypeRegA branch was dead: archive/tar rewrites it to TypeReg on read. --- cmd/storebench/main.go | 6 +++--- images/oci/boot.go | 7 +++++-- images/store.go | 2 +- snapshot/localfile/gc.go | 3 +-- utils/http.go | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cmd/storebench/main.go b/cmd/storebench/main.go index 6162a913..dba76a9e 100644 --- a/cmd/storebench/main.go +++ b/cmd/storebench/main.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "runtime/pprof" + "slices" "strconv" "time" @@ -182,10 +183,9 @@ func runMicro(ctx context.Context, engine, op string, n, ops int, dir string) er for i := range n { seed = append(seed, fmt.Sprintf("S%023d", i)) } - for base := 0; base < n; base += 1000 { - hi := min(base+1000, n) + for chunk := range slices.Chunk(seed, 1000) { if err := store.Update(ctx, meta.Scope{Write: ns}, meta.CommitDurable, func(w meta.Writer) error { - for _, id := range seed[base:hi] { + for _, id := range chunk { if err := w.PutRaw(ctx, ns, hypervisor.TableRecords, id, benchPayload, false); err != nil { return err } diff --git a/images/oci/boot.go b/images/oci/boot.go index 62deb377..f99af829 100644 --- a/images/oci/boot.go +++ b/images/oci/boot.go @@ -3,6 +3,7 @@ package oci import ( "archive/tar" "bufio" + "bytes" "cmp" "compress/gzip" "context" @@ -17,6 +18,8 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/projecteru2/core/log" + + "github.com/cocoonstack/cocoon/utils" ) // maxKernelBytes bounds arm64 gzip decompression against a bomb; real kernels are under 100 MiB. @@ -108,7 +111,7 @@ func scanBootFiles(ctx context.Context, r io.Reader, workDir, namePrefix string) return "", "", fmt.Errorf("read tar entry: %w", readErr) } - if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA { //nolint:staticcheck // tar.TypeRegA is deprecated but still present in real archives + if hdr.Typeflag != tar.TypeReg { continue } @@ -147,7 +150,7 @@ func scanBootFiles(ctx context.Context, r io.Reader, workDir, namePrefix string) if isKernel && runtime.GOARCH == "arm64" { br := bufio.NewReader(tr) src = br - if magic, _ := br.Peek(2); len(magic) == 2 && magic[0] == 0x1f && magic[1] == 0x8b { + if magic, _ := br.Peek(len(utils.GzipMagic)); bytes.Equal(magic, utils.GzipMagic) { var gzErr error if gz, gzErr = gzip.NewReader(br); gzErr != nil { _ = f.Close() diff --git a/images/store.go b/images/store.go index 689d7ed6..040c8310 100644 --- a/images/store.go +++ b/images/store.go @@ -72,7 +72,7 @@ func (s *Store[E]) applyDiff(ctx context.Context, w meta.Writer, fn func(*Index[ if prev, ok := before[id]; ok && string(prev) == string(raw) { continue } - if err := c.Upsert(ctx, w, id, entry); err != nil { + if err := w.PutRaw(ctx, s.ns, TableRecords, id, raw, false); err != nil { return err } } diff --git a/snapshot/localfile/gc.go b/snapshot/localfile/gc.go index d07332a5..7b4ec9fe 100644 --- a/snapshot/localfile/gc.go +++ b/snapshot/localfile/gc.go @@ -318,14 +318,13 @@ func logEvictRow(ctx context.Context, logger *log.Fields, verb, id string, m sna // backfillSizeBytes computes + persists SizeBytes for records missing it so future GC skips du. func backfillSizeBytes(ctx context.Context, lf *LocalFile, records map[string]snapshotMeta) { - conf := lf.conf logger := log.WithFunc("gc.snapshot") var changed bool for id, m := range records { if m.sizeBytes > 0 { continue } - actual, err := utils.DirSize(conf.SnapshotDataDir(id)) + actual, err := utils.DirSize(m.dataDir) if err != nil { logger.Warnf(ctx, "DirSize for %s: %v", id, err) continue diff --git a/utils/http.go b/utils/http.go index 5115da8e..9824679a 100644 --- a/utils/http.go +++ b/utils/http.go @@ -90,7 +90,7 @@ func CheckSocket(socketPath string) error { func DoWithRetry[T any](ctx context.Context, fn func() (T, error)) (T, error) { var zero T var lastErr error - for i := 0; i <= MaxRetries; i++ { + for i := range MaxRetries + 1 { result, err := fn() if err == nil { return result, nil From a4e6979fe9fb44e28a3a80ce83694a1b8f1be19c Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:04:25 +0800 Subject: [PATCH 5/9] test: drop dead fixtures and duplicate stubs The gc policy tests parse the real gc command instead of a hand-copied flag set, the CH tests share the in-process handler transport instead of a second TCP stub client, the clone config test drops a flag the function never reads, and the ReverseLayers tests compare with slices.Equal. --- cmd/core/vmconfig_test.go | 1 - cmd/others/handler_test.go | 18 ++++++------------ daemon/reconcile_test.go | 3 +-- hypervisor/cloudhypervisor/netresize_test.go | 19 ++----------------- hypervisor/cloudhypervisor/snapshot_test.go | 2 +- hypervisor/snapshot_test.go | 17 ++++------------- 6 files changed, 14 insertions(+), 46 deletions(-) diff --git a/cmd/core/vmconfig_test.go b/cmd/core/vmconfig_test.go index 0abb7f3b..3e6731a1 100644 --- a/cmd/core/vmconfig_test.go +++ b/cmd/core/vmconfig_test.go @@ -115,7 +115,6 @@ func TestCloneVMConfigKnobFlagsOverrideSnapshot(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cmd := &cobra.Command{} cmd.Flags().String("name", "c", "") - cmd.Flags().Int("nics", 0, "") cmd.Flags().Int("queue-size", 0, "") cmd.Flags().Int("disk-queue-size", 0, "") cmd.Flags().Int("cpu-weight", 0, "") diff --git a/cmd/others/handler_test.go b/cmd/others/handler_test.go index 0f29507b..57a7d57e 100644 --- a/cmd/others/handler_test.go +++ b/cmd/others/handler_test.go @@ -8,7 +8,7 @@ import ( ) func TestParseSnapshotPolicy_Defaults(t *testing.T) { - cmd := buildGCCmd() + cmd := gcCommand() if err := cmd.ParseFlags(nil); err != nil { t.Fatal(err) } @@ -33,7 +33,7 @@ func TestParseSnapshotPolicy_NegativeRejected(t *testing.T) { } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - cmd := buildGCCmd() + cmd := gcCommand() if err := cmd.ParseFlags(tt.args); err != nil { t.Fatal(err) } @@ -57,7 +57,7 @@ func TestParseSnapshotPolicy_SubFlagRequiresSnapshot(t *testing.T) { } for _, args := range cases { t.Run(args[0], func(t *testing.T) { - cmd := buildGCCmd() + cmd := gcCommand() if err := cmd.ParseFlags(args); err != nil { t.Fatal(err) } @@ -70,7 +70,7 @@ func TestParseSnapshotPolicy_SubFlagRequiresSnapshot(t *testing.T) { } func TestParseSnapshotPolicy_HappyPath(t *testing.T) { - cmd := buildGCCmd() + cmd := gcCommand() if err := cmd.ParseFlags([]string{ "--snapshot", "--snapshot-keep=10", @@ -89,12 +89,6 @@ func TestParseSnapshotPolicy_HappyPath(t *testing.T) { } } -func buildGCCmd() *cobra.Command { - cmd := &cobra.Command{Use: "gc"} - cmd.Flags().Bool("snapshot", false, "") - cmd.Flags().Int("snapshot-keep", 0, "") - cmd.Flags().Duration("snapshot-age", 0, "") - cmd.Flags().String("snapshot-size", "", "") - cmd.Flags().Bool("snapshot-dry-run", false, "") - return cmd +func gcCommand() *cobra.Command { + return Commands(Handler{})[0] } diff --git a/daemon/reconcile_test.go b/daemon/reconcile_test.go index e8c736d4..a52c0f53 100644 --- a/daemon/reconcile_test.go +++ b/daemon/reconcile_test.go @@ -409,8 +409,7 @@ func newFake() *fakeSupervisor { func newTestDaemon(t *testing.T, f *fakeSupervisor) *Daemon { t.Helper() - d := New(Config{RootDir: t.TempDir()}, nil, []Supervisor{f}) - return d + return New(Config{RootDir: t.TempDir()}, nil, []Supervisor{f}) } func runningRec(id string, gen uint64) *hypervisor.VMRecord { diff --git a/hypervisor/cloudhypervisor/netresize_test.go b/hypervisor/cloudhypervisor/netresize_test.go index 30036d48..3bd90847 100644 --- a/hypervisor/cloudhypervisor/netresize_test.go +++ b/hypervisor/cloudhypervisor/netresize_test.go @@ -5,11 +5,8 @@ import ( "encoding/json/jsontext" "encoding/json/v2" "errors" - "net" "net/http" - "net/http/httptest" "slices" - "strings" "sync" "testing" "time" @@ -67,7 +64,7 @@ func TestConvergeOrphanedPause(t *testing.T) { defer mu.Unlock() _ = json.MarshalWrite(w, chVMInfoResponse{State: state}) }) - hc := newStubHTTPClient(t, mux) + hc := &http.Client{Transport: handlerTransport{mux}} info, err := getVMInfo(t.Context(), hc) if err != nil { @@ -92,18 +89,6 @@ func TestConvergeOrphanedPause(t *testing.T) { } } -func newStubHTTPClient(t *testing.T, mux *http.ServeMux) *http.Client { - t.Helper() - srv := httptest.NewServer(mux) - t.Cleanup(srv.Close) - addr := strings.TrimPrefix(srv.URL, "http://") - return &http.Client{Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("tcp", addr) - }, - }} -} - func newCHStubClient(t *testing.T, nets []chNet, stickyIDs ...string) (*http.Client, func() []string) { t.Helper() var mu sync.Mutex @@ -139,7 +124,7 @@ func newCHStubClient(t *testing.T, nets []chNet, stickyIDs ...string) (*http.Cli } _ = json.MarshalWrite(w, chVMInfoResponse{Config: chVMInfoConfig{Nets: live}, DeviceTree: tree}) }) - hc := newStubHTTPClient(t, mux) + hc := &http.Client{Transport: handlerTransport{mux}} return hc, func() []string { mu.Lock() defer mu.Unlock() diff --git a/hypervisor/cloudhypervisor/snapshot_test.go b/hypervisor/cloudhypervisor/snapshot_test.go index 57fb5afc..55312aa2 100644 --- a/hypervisor/cloudhypervisor/snapshot_test.go +++ b/hypervisor/cloudhypervisor/snapshot_test.go @@ -41,7 +41,7 @@ func TestSnapshotPauseRefusesHotAttachedBeforePausing(t *testing.T) { mu.Unlock() w.WriteHeader(http.StatusNoContent) }) - hc := newStubHTTPClient(t, mux) + hc := &http.Client{Transport: handlerTransport{mux}} err := (&CloudHypervisor{}).snapshotSpec(t.Context()).Pause(&hypervisor.VMRecord{}, hc) diff --git a/hypervisor/snapshot_test.go b/hypervisor/snapshot_test.go index c74b34fb..97003a86 100644 --- a/hypervisor/snapshot_test.go +++ b/hypervisor/snapshot_test.go @@ -134,14 +134,8 @@ func TestReverseLayers_Order(t *testing.T) { {Role: types.StorageRoleLayer, Serial: "L2"}, } got := ReverseLayers(configs, func(_ int, sc *types.StorageConfig) string { return sc.Serial }) - want := []string{"L2", "L1", "L0"} - if len(got) != len(want) { - t.Fatalf("got %d, want %d", len(got), len(want)) - } - for i := range want { - if got[i] != want[i] { - t.Errorf("got[%d] = %q, want %q", i, got[i], want[i]) - } + if want := []string{"L2", "L1", "L0"}; !slices.Equal(got, want) { + t.Errorf("got %v, want %v", got, want) } } @@ -152,11 +146,8 @@ func TestReverseLayers_IndexCallback(t *testing.T) { {Role: types.StorageRoleLayer}, } got := ReverseLayers(configs, func(idx int, _ *types.StorageConfig) int { return idx }) - want := []int{2, 1, 0} - for i := range want { - if got[i] != want[i] { - t.Errorf("got[%d] = %d, want %d", i, got[i], want[i]) - } + if want := []int{2, 1, 0}; !slices.Equal(got, want) { + t.Errorf("got %v, want %v", got, want) } } From 6c1073b4c5ef8fff9e6e075ee5c6a92adaca69a9 Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:04:46 +0800 Subject: [PATCH 6/9] review: contiguous small-struct fields, stray blank lines, stale omitempty comment --- hypervisor/firecracker/api.go | 2 +- meta/sqlite/maintenance_test.go | 1 - meta/sqlite/multiprocess_test.go | 1 - types/snapshot.go | 9 ++++----- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/hypervisor/firecracker/api.go b/hypervisor/firecracker/api.go index d5aadc0b..1525a2b1 100644 --- a/hypervisor/firecracker/api.go +++ b/hypervisor/firecracker/api.go @@ -92,7 +92,7 @@ type fcNetworkOverride struct { HostDevName string `json:"host_dev_name"` } -// pointer+omitempty keeps the field off the wire for FC < v1.16. +// a nil override keeps the field off the wire for FC < v1.16. type fcVsockOverride struct { UDSPath string `json:"uds_path"` } diff --git a/meta/sqlite/maintenance_test.go b/meta/sqlite/maintenance_test.go index af724c24..b5e0ec67 100644 --- a/meta/sqlite/maintenance_test.go +++ b/meta/sqlite/maintenance_test.go @@ -83,7 +83,6 @@ func TestBackupCrashSteps(t *testing.T) { } err := Backup(ctx, filepath.Join(dir, DBFileName), dest) if step != "renamed" { - if !errors.Is(err, errCrash) { t.Fatalf("step %s: want injected crash, got %v", step, err) } diff --git a/meta/sqlite/multiprocess_test.go b/meta/sqlite/multiprocess_test.go index 557d6086..84a1903a 100644 --- a/meta/sqlite/multiprocess_test.go +++ b/meta/sqlite/multiprocess_test.go @@ -169,7 +169,6 @@ func TestKillStormAtomicity(t *testing.T) { if id, ok := strings.CutPrefix(sc.Text(), "ACK "); ok { acked[id] = struct{}{} got++ - if got >= 3+round*2 { break } diff --git a/types/snapshot.go b/types/snapshot.go index 1f099cf0..4b6837bc 100644 --- a/types/snapshot.go +++ b/types/snapshot.go @@ -34,9 +34,8 @@ type Snapshot struct { // SnapshotExport is the envelope written as snapshot.json inside an export archive. type SnapshotExport struct { - Config SnapshotConfig `json:"config"` - Version int `json:"version"` - Files []string `json:"files,omitempty"` // data-dir files the export carries; absent on exports older than the manifest - - DiskAttachFiles []string `json:"disk_attach_files,omitempty"` // the subset `vm disk attach --from-dir` reads + Config SnapshotConfig `json:"config"` + Version int `json:"version"` + Files []string `json:"files,omitempty"` // data-dir files the export carries; absent on exports older than the manifest + DiskAttachFiles []string `json:"disk_attach_files,omitempty"` // the subset `vm disk attach --from-dir` reads } From dec372fdb9910d4d430d5bcbee6cb773ede55eea Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:05:18 +0800 Subject: [PATCH 7/9] docs: point install links at v0.7.2, cover snapshot disk attach and debug-ignored cgroup flags --- README.md | 8 ++++---- docs/cli.md | 9 +++++---- docs/devices.md | 4 ++-- docs/index.md | 2 +- docs/install.md | 6 +++--- docs/known-issues.md | 6 +++--- docs/snapshots.md | 2 +- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5e25b336..5e758b98 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Lightweight MicroVM engine with dual hypervisor backends: [Cloud Hypervisor](htt - **Docker-like CLI** — `create`, `run`, `start`, `stop`, `list`, `inspect`, `console`, `exec`, `rm`, `clone`, `status` - **Snapshot & clone** — capture a running VM (memory, disks, config) and clone it into new VMs with fresh network identity; clones restore memory copy-on-write by default (page cache shared across sibling clones) and hold shared source leases so parallel clone bursts survive concurrent source removal; restore and atomic hibernate in place; portable export/import between hosts - **CNI networking** — multi-queue virtio-net TAPs wired via TC redirect (no bridge in the data path), multi-NIC, bridge mode, NIC hot-resize -- **Data disks & runtime attach** — extra virtio-blk disks at create/clone time; hot-plug vhost-user-fs shares and VFIO PCI devices (CH); external raw disks and NIC resize on CH or Firecracker `--pci` VMs +- **Data disks & runtime attach** — extra virtio-blk disks at create/clone time; hot-plug vhost-user-fs shares and VFIO PCI devices (CH); external raw disks and NIC resize on CH or Firecracker `--pci` VMs; a private copy of a snapshot's COW disk on CH - **Windows guests** — UEFI + Hyper-V enlightenments via the cocoonstack CH/firmware forks - **Firecracker backend** — `--fc` for ~125ms boots and <5 MiB per-VM overhead (OCI images only) - **Daemon-optional architecture** — one hypervisor process per VM, every command standalone; `cocoon daemon` optionally adopts running VMs to converge crashes as they happen; modular lock-safe GC with snapshot LRU eviction @@ -19,12 +19,12 @@ Lightweight MicroVM engine with dual hypervisor backends: [Cloud Hypervisor](htt ## Quick Start ```bash -# Install the binary (Linux amd64; arm64: cocoon_0.7.1_Linux_arm64.tar.gz) -curl -fsSL -o cocoon.tar.gz https://github.com/cocoonstack/cocoon/releases/download/v0.7.1/cocoon_0.7.1_Linux_x86_64.tar.gz +# Install the binary (Linux amd64; arm64: cocoon_0.7.2_Linux_arm64.tar.gz) +curl -fsSL -o cocoon.tar.gz https://github.com/cocoonstack/cocoon/releases/download/v0.7.2/cocoon_0.7.2_Linux_x86_64.tar.gz tar -xzf cocoon.tar.gz && install -m 0755 cocoon /usr/local/bin/ # One-time environment setup (installs CH, firmware, CNI plugins) -curl -fsSL -o cocoon-check https://raw.githubusercontent.com/cocoonstack/cocoon/refs/tags/v0.7.1/doctor/check.sh +curl -fsSL -o cocoon-check https://raw.githubusercontent.com/cocoonstack/cocoon/refs/tags/v0.7.2/doctor/check.sh install -m 0755 cocoon-check /usr/local/bin/ && sudo cocoon-check --upgrade # Pull an image and run a VM diff --git a/docs/cli.md b/docs/cli.md index 9a0f6911..a09a048b 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -36,8 +36,8 @@ cocoon │ │ ├── attach [flags] VM Attach a VFIO PCI device (CH only) │ │ └── detach [flags] VM Detach a VFIO PCI device by --id │ ├── disk -│ │ ├── attach [flags] VM Hot-attach an existing raw disk file (CH, or FC with --pci) -│ │ └── detach [flags] VM Detach a hot-attached disk by --name (keeps the file) +│ │ ├── attach [flags] VM Hot-attach a raw disk file (CH, or FC with --pci) or a snapshot's COW disk copy (CH) +│ │ └── detach [flags] VM Detach a hot-attached disk by --name (keeps a --path file, removes a snapshot copy) │ ├── net [flags] VM Resize NIC count on a running VM (CH, or FC with --pci) │ └── debug [flags] IMAGE Generate hypervisor launch command (dry run) ├── snapshot @@ -294,8 +294,9 @@ Applies to `cocoon vm debug`: `vm debug` prints the VMM launch command only. It does not prepare a netns or a TAP, so it emits no `--net` and no `ip=`, and `--nics`, `--queue-size`, -`--network` and `--bridge` are ignored — setting any of them warns on stderr, -like `--data-disk`. `--max-cpu`, `--balloon`, `--cow` and `--ch` shape the Cloud +`--network`, `--bridge` and the cgroup CPU knobs (`--cpu-weight`, +`--cpu-quota-us`, `--cpu-period-us`, `--cpu-burst-us`, `--cpuset-cpus`) are +ignored — setting any of them warns on stderr, like `--data-disk`. `--max-cpu`, `--balloon`, `--cow` and `--ch` shape the Cloud Hypervisor command only; `vm debug --fc` prints the Firecracker REST sequence and ignores all four. Queue placement happens at launch against the store, so the printed command carries no `queue_affinity` whatever `--cpuset-cpus` says. diff --git a/docs/devices.md b/docs/devices.md index b56fd76d..2b627f11 100644 --- a/docs/devices.md +++ b/docs/devices.md @@ -1,6 +1,6 @@ # Runtime Device Attach -Hot-plug vhost-user-fs shares, VFIO PCI devices, and external raw disks on a running VM (Cloud Hypervisor; raw disks also on Firecracker VMs created with `--pci`). +Hot-plug vhost-user-fs shares, VFIO PCI devices, external raw disks, and copies of a snapshot's COW disk on a running VM (Cloud Hypervisor; raw disks also on Firecracker VMs created with `--pci`). ## Overview @@ -8,7 +8,7 @@ Cocoon can hot-plug three classes of external resources onto a running VM: - **Vhost-user-fs** — a file share served by an external `virtiofsd` (or compatible backend) over a Unix socket. Attach surfaces a virtio-fs device in the guest, accessible via `mount -t virtiofs /mnt/...`. - **VFIO PCI passthrough** — a host PCI device bound to `vfio-pci` (GPU, NIC, NVMe). Attach hands the device to the guest with IOMMU isolation. -- **Data disks** — an existing raw disk file, surfaced as `/dev/disk/by-id/virtio-`. Cocoon never creates or deletes the backing file: it can outlive any VM and be re-attached elsewhere (a persistent volume). +- **Data disks** — an existing raw disk file, surfaced as `/dev/disk/by-id/virtio-`. Cocoon never creates or deletes the backing file: it can outlive any VM and be re-attached elsewhere (a persistent volume). On Cloud Hypervisor the disk can instead be a private read-write copy of a snapshot's COW disk (`--from-snapshot` / `--from-dir`), which cocoon creates in the VM's run dir and removes on detach, stop, or `vm rm`. All three attaches are **runtime-only**: the device lives only for the current VM process and is gone after stop/restart — re-attach after the next start. cocoon refuses `snapshot save` and `vm hibernate` while any of the three is attached — umount an external disk in-guest, detach, capture, then re-attach after the wake/restore; on both backends the refusal arrives before the guest is paused. Cloud Hypervisor does not enforce this itself: it captures a snapshot that names the vhost-user socket, and restoring that snapshot either fails once the backend is gone or hangs against a fresh one. External volumes are host state cocoon does not own: it never creates, copies, or deletes the backing file, and snapshot, restore, and clone carry no trace of them. All mutating verbs on one VM (attach/detach, net resize, snapshot, hibernate, restore, stop) serialize on a per-VM lock, so concurrent invocations cannot interleave with a capture window. While the VM runs, `vm inspect` reports the live attach set under `.attached_devices` (`fs`, `devices`, `disks`). diff --git a/docs/index.md b/docs/index.md index 9283a934..b6456ec7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,7 +54,7 @@ cocoon CLI ──► images: OCI (EROFS layers, direct boot) | cloudimg (qcow2, - **TC redirect I/O path** — veth ↔ TAP wired via ingress qdisc + mirred redirect (no bridge in the data path) - **DNS configuration** — custom DNS servers injected into VMs via kernel cmdline (OCI) or cloud-init network-config (cloudimg) - **Cloud-init metadata** — automatic NoCloud cidata FAT12 disk for cloudimg VMs (hostname, configurable user/password via `--user`/`--password`, multi-NIC Netplan v2 network-config); cidata is automatically skipped on subsequent boots -- **User data disks** — `--data-disk` attaches additional virtio-blk disks per VM, with optional ext4 mkfs at create time, cloud-init `mounts:` auto-mount on cloudimg+CH (via `/dev/disk/by-id/virtio-`), per-disk DirectIO override, and 1:1 inheritance through snapshot/clone/restore; `vm clone --data-disk` adds fresh disks to a clone and `vm disk attach/detach` hot-plugs existing raw files on a running VM (CH, or Firecracker with `--pci`) +- **User data disks** — `--data-disk` attaches additional virtio-blk disks per VM, with optional ext4 mkfs at create time, cloud-init `mounts:` auto-mount on cloudimg+CH (via `/dev/disk/by-id/virtio-`), per-disk DirectIO override, and 1:1 inheritance through snapshot/clone/restore; `vm clone --data-disk` adds fresh disks to a clone and `vm disk attach/detach` hot-plugs existing raw files on a running VM (CH, or Firecracker with `--pci`) or a private copy of a snapshot's COW disk (CH) - **Copy-on-write clone restore** — Cloud Hypervisor clones of plain private-anon snapshots default to `mmap` memory restore: no eager copy, page cache shared across sibling clones; hugepages/shared snapshots fall back to eager copy with a warning - **Hugepages** — opt-in via `vm create --hugepages` (Cloud Hypervisor only); backs VM memory with hugetlbfs at the cost of the mmap restore fast path for that VM's snapshots (never supported on Firecracker, whose snapshots cannot restore from hugetlbfs) - **Mergeable memory** — opt-in `--mergeable` (Cloud Hypervisor only) madvises guest memory `MADV_MERGEABLE` so host KSM can dedup identical pages across VMs; excludes `--hugepages`/`--shared-memory` diff --git a/docs/install.md b/docs/install.md index 4932b05e..5baa77da 100644 --- a/docs/install.md +++ b/docs/install.md @@ -24,10 +24,10 @@ Download pre-built binaries from [GitHub Releases](https://github.com/cocoonstac ```bash # Linux amd64 -curl -fsSL -o cocoon.tar.gz https://github.com/cocoonstack/cocoon/releases/download/v0.7.1/cocoon_0.7.1_Linux_x86_64.tar.gz +curl -fsSL -o cocoon.tar.gz https://github.com/cocoonstack/cocoon/releases/download/v0.7.2/cocoon_0.7.2_Linux_x86_64.tar.gz # Linux arm64 -curl -fsSL -o cocoon.tar.gz https://github.com/cocoonstack/cocoon/releases/download/v0.7.1/cocoon_0.7.1_Linux_arm64.tar.gz +curl -fsSL -o cocoon.tar.gz https://github.com/cocoonstack/cocoon/releases/download/v0.7.2/cocoon_0.7.2_Linux_arm64.tar.gz tar -xzf cocoon.tar.gz install -m 0755 cocoon /usr/local/bin/ @@ -54,7 +54,7 @@ Cocoon ships a diagnostic script that checks your environment and can auto-insta ```bash # Get script -curl -fsSL -o cocoon-check https://raw.githubusercontent.com/cocoonstack/cocoon/refs/tags/v0.7.1/doctor/check.sh +curl -fsSL -o cocoon-check https://raw.githubusercontent.com/cocoonstack/cocoon/refs/tags/v0.7.2/doctor/check.sh install -m 0755 cocoon-check /usr/local/bin/ # Check only — reports PASS/FAIL for each requirement diff --git a/docs/known-issues.md b/docs/known-issues.md index 1e2e789a..ebf0b3bb 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -246,9 +246,9 @@ On CNI plugins with strict per-veth MAC enforcement (Cilium eBPF, Calico eBPF), `cocoon vm fs attach` only works on CH VMs that were created with `--shared-memory`. CH's `memory shared=on` is fixed at VM creation: backend processes (e.g. virtiofsd) need to mmap guest memory via the negotiated memfd, and the memory model cannot be flipped on a running VM. If `--shared-memory` was omitted at create time, the only path is to recreate the VM. Cocoon's preflight reads `vm.info` and surfaces a clear error rather than letting CH return a vague rejection. -## Snapshotting a VM with attached vhost-user-fs / VFIO is refused +## Snapshotting a VM with attached vhost-user-fs / VFIO / hot-attached disks is refused -Cloud Hypervisor captures a snapshot of a VM that holds a vhost-user-fs share or a VFIO PCI passthrough device, but the result cannot be restored: it names a backend socket that is gone (restore fails after 60s) or hangs against a fresh one. Cocoon refuses the capture itself — the pre-flight reads the live `vm.info` before pausing the guest and fails with `hot-attached vhost-user-fs "": detach before snapshot or hibernate` (or `hot-attached device "": ...`) before anything is captured or recorded. `cocoon vm fs detach` / `cocoon vm device detach` first to clear runtime devices, then snapshot. +Cloud Hypervisor captures a snapshot of a VM that holds a vhost-user-fs share or a VFIO PCI passthrough device, but the result cannot be restored: it names a backend socket that is gone (restore fails after 60s) or hangs against a fresh one. Cocoon refuses the capture itself — the pre-flight reads the live `vm.info` before pausing the guest and fails with `hot-attached vhost-user-fs "": detach before snapshot or hibernate` (or `hot-attached device "": ...`, `hot-attached disk "": ...`) before anything is captured or recorded. `cocoon vm fs detach` / `cocoon vm device detach` / `cocoon vm disk detach` first to clear runtime devices, then snapshot. ## Snapshot lease files are never reclaimed (fixed in v0.6.8) @@ -262,7 +262,7 @@ On v0.6.7 and older every `snapshot save` leaves that zero-byte file behind and ## Runtime attached devices do not survive VM stop / clone / restore -Attaches via `cocoon vm fs attach` and `cocoon vm device attach` are runtime-only — they live in the CH process state and are never written into the VM record, sidecar, or snapshot. After `vm stop`, `vm clone`, or `vm restore`, the user must re-run the attach commands. `vm inspect` reflects the live CH `vm.info` for running VMs and omits `attached_devices` for stopped VMs. This is by design: cocoon does not own the backend (virtiofsd / vfio-pci binding) and cannot guarantee the resource still exists across host events. +Attaches via `cocoon vm fs attach`, `cocoon vm device attach` and `cocoon vm disk attach` are runtime-only — they live in the CH process state and are never written into the VM record, sidecar, or snapshot. After `vm stop`, `vm clone`, or `vm restore`, the user must re-run the attach commands. `vm inspect` reflects the live CH `vm.info` for running VMs and omits `attached_devices` for stopped VMs. This is by design: cocoon does not own the backend (virtiofsd / vfio-pci binding) and cannot guarantee the resource still exists across host events. ## virtiofsd is a single-shot daemon diff --git a/docs/snapshots.md b/docs/snapshots.md index 80b8e150..4bdc159c 100644 --- a/docs/snapshots.md +++ b/docs/snapshots.md @@ -112,7 +112,7 @@ cocoon vm clone imported-snap cocoon snapshot export my-snap -o - | ssh host2 cocoon snapshot import --name my-snap ``` -The archive contains the snapshot config, VM config, COW disk, memory ranges, and device state — sparse files carry cocoon's own pax records so their holes survive the round-trip — everything needed to reconstruct the snapshot on a different machine. The envelope also lists the files the archive carries, and `snapshot import` refuses an archive that ends before every listed file has landed — a plain tar cut at an entry boundary otherwise reads as complete. `snapshot export --to-dir` writes the same files as a directory that `vm clone --from-dir` and `vm restore --from-dir` consume without a tar round-trip. Do not repack an export with a third-party tar: dropping cocoon's sparse records can rebuild a short, shifted disk. `--from-dir` and `snapshot import` reject a `cow.raw` whose size differs from the envelope's recorded storage size. +The archive contains the snapshot config, VM config, COW disk, memory ranges, and device state — sparse files carry cocoon's own pax records so their holes survive the round-trip — everything needed to reconstruct the snapshot on a different machine. The envelope also lists the files the archive carries, and `snapshot import` refuses an archive that ends before every listed file has landed — a plain tar cut at an entry boundary otherwise reads as complete. `snapshot export --to-dir` writes the same files as a directory that `vm clone --from-dir`, `vm restore --from-dir` and `vm disk attach --from-dir` (a copy of the COW disk on a running VM of the same image; see [Devices](devices.md)) consume without a tar round-trip. Do not repack an export with a third-party tar: dropping cocoon's sparse records can rebuild a short, shifted disk. `--from-dir` and `snapshot import` reject a `cow.raw` whose size differs from the envelope's recorded storage size. #### Cross-Node Clone From 45c24ead4950c3df2ddebce8453080e3085366e1 Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:06:41 +0800 Subject: [PATCH 8/9] refactor: image index diff writes raw rows both ways, zombie test polls with utils.WaitFor --- hypervisor/stop_linux_test.go | 10 ++++------ images/store.go | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/hypervisor/stop_linux_test.go b/hypervisor/stop_linux_test.go index 48feb0bb..e758ef51 100644 --- a/hypervisor/stop_linux_test.go +++ b/hypervisor/stop_linux_test.go @@ -43,12 +43,10 @@ func TestDeleteSkipsAScannedVMMThatExitedBeforeTheKill(t *testing.T) { t.Fatalf("scan misses the live fake VMM child %d", child.Process.Pid) } _ = child.Process.Kill() - deadline := time.Now().Add(2 * time.Second) - for utils.VerifyProcessCmdline(child.Process.Pid, b.Conf.BinaryName(), sockPath) { - if time.Now().After(deadline) { - t.Fatal("killed child kept its cmdline") - } - time.Sleep(10 * time.Millisecond) + if err := utils.WaitFor(ctx, 2*time.Second, 10*time.Millisecond, func() (bool, error) { + return !utils.VerifyProcessCmdline(child.Process.Pid, b.Conf.BinaryName(), sockPath), nil + }); err != nil { + t.Fatalf("killed child kept its cmdline: %v", err) } if !utils.IsProcessAlive(child.Process.Pid) { t.Fatal("precondition: the unreaped child must linger as a zombie") diff --git a/images/store.go b/images/store.go index 040c8310..b9f64db4 100644 --- a/images/store.go +++ b/images/store.go @@ -53,10 +53,9 @@ func (s *Store[E]) applyDiff(ctx context.Context, w meta.Writer, fn func(*Index[ if err := fn(idx); err != nil { return err } - c := meta.NewCollection[E](s.ns, TableRecords) for id := range before { if idx.Images[id] == nil { - if err := c.Delete(ctx, w, id); err != nil { + if err := w.DeleteRaw(ctx, s.ns, TableRecords, id, false); err != nil { return err } } From 35566a35872aa2b4953d92a81baeccd3250c7ddc Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 18 Oct 2026 13:15:40 +0800 Subject: [PATCH 9/9] docs: runtime-only attaches live in the VMM state on either backend --- docs/known-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/known-issues.md b/docs/known-issues.md index ebf0b3bb..cdcfd836 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -262,7 +262,7 @@ On v0.6.7 and older every `snapshot save` leaves that zero-byte file behind and ## Runtime attached devices do not survive VM stop / clone / restore -Attaches via `cocoon vm fs attach`, `cocoon vm device attach` and `cocoon vm disk attach` are runtime-only — they live in the CH process state and are never written into the VM record, sidecar, or snapshot. After `vm stop`, `vm clone`, or `vm restore`, the user must re-run the attach commands. `vm inspect` reflects the live CH `vm.info` for running VMs and omits `attached_devices` for stopped VMs. This is by design: cocoon does not own the backend (virtiofsd / vfio-pci binding) and cannot guarantee the resource still exists across host events. +Attaches via `cocoon vm fs attach`, `cocoon vm device attach` and `cocoon vm disk attach` are runtime-only — they live in the VMM process state and are never written into the VM record, sidecar, or snapshot. After `vm stop`, `vm clone`, or `vm restore`, the user must re-run the attach commands. `vm inspect` reflects the live VMM state for running VMs and omits `attached_devices` for stopped VMs. This is by design: cocoon does not own the backend (virtiofsd, the vfio-pci binding, an external disk file) and cannot guarantee the resource still exists across host events. ## virtiofsd is a single-shot daemon