diff --git a/.github/workflows/basic-ci.yaml b/.github/workflows/basic-ci.yaml index eff113eb7..9b2f57b50 100644 --- a/.github/workflows/basic-ci.yaml +++ b/.github/workflows/basic-ci.yaml @@ -25,6 +25,9 @@ jobs: run: | make validate make validate-ci + - name: "Run unit-tests" + run: | + make unit-test job-new-installation: needs: validation runs-on: diff --git a/cmd/node-disk-manager-webhook/main.go b/cmd/node-disk-manager-webhook/main.go index 30e764701..9eaf462cb 100644 --- a/cmd/node-disk-manager-webhook/main.go +++ b/cmd/node-disk-manager-webhook/main.go @@ -5,6 +5,8 @@ import ( "fmt" "os" + ctrllh "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io" + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" "github.com/harvester/webhook/pkg/config" "github.com/harvester/webhook/pkg/server" "github.com/harvester/webhook/pkg/server/admission" @@ -32,6 +34,10 @@ type resourceCaches struct { lvmVGCache ctldiskv1.LVMVolumeGroupCache storageClassCache ctlstoragev1.StorageClassCache pvCache ctlcorev1.PersistentVolumeCache + volumeCache lhv1beta2.VolumeCache + nodeCache ctlcorev1.NodeCache + backingImageCache lhv1beta2.BackingImageCache + lhNodeCache lhv1beta2.NodeCache } func main() { @@ -117,7 +123,8 @@ func runWebhookServer(ctx context.Context, cfg *rest.Config, options *config.Opt bdMutator, } - bdValidator := blockdevice.NewBlockdeviceValidator(resourceCaches.bdCache, resourceCaches.storageClassCache, resourceCaches.pvCache) + bdValidator := blockdevice.NewBlockdeviceValidator(resourceCaches.bdCache, resourceCaches.storageClassCache, resourceCaches.pvCache, + resourceCaches.volumeCache, resourceCaches.nodeCache, resourceCaches.backingImageCache, resourceCaches.lhNodeCache) scValidator := storageclass.NewStorageClassValidator(resourceCaches.lvmVGCache) var validators = []admission.Validator{ bdValidator, @@ -156,12 +163,21 @@ func newCaches(ctx context.Context, cfg *rest.Config, threadiness int) (*resourc if err != nil { return nil, err } - starters = append(starters, disks, storageFactory, coreFactory) + lhFactory, err := ctrllh.NewFactoryFromConfig(cfg) + if err != nil { + return nil, err + } + + starters = append(starters, disks, storageFactory, coreFactory, lhFactory) resourceCaches := &resourceCaches{ bdCache: disks.Harvesterhci().V1beta1().BlockDevice().Cache(), lvmVGCache: disks.Harvesterhci().V1beta1().LVMVolumeGroup().Cache(), storageClassCache: storageFactory.Storage().V1().StorageClass().Cache(), pvCache: coreFactory.Core().V1().PersistentVolume().Cache(), + volumeCache: lhFactory.Longhorn().V1beta2().Volume().Cache(), + nodeCache: coreFactory.Core().V1().Node().Cache(), + backingImageCache: lhFactory.Longhorn().V1beta2().BackingImage().Cache(), + lhNodeCache: lhFactory.Longhorn().V1beta2().Node().Cache(), } if err := start.All(ctx, threadiness, starters...); err != nil { diff --git a/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml b/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml index 593ee1466..2039a2941 100644 --- a/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml +++ b/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml @@ -57,7 +57,7 @@ rules: resources: [ "storageclasses" ] verbs: [ "*" ] - apiGroups: [ "harvesterhci.io" ] - resources: [ "blockdevices", "lvmvolumegroups", "lvmvolumegroups/status" ] + resources: [ "blockdevices", "lvmvolumegroups", "lvmvolumegroups/status"] verbs: [ "*" ] - apiGroups: [ "apiregistration.k8s.io" ] resources: [ "apiservices" ] @@ -68,6 +68,12 @@ rules: - apiGroups: [ "admissionregistration.k8s.io" ] resources: [ "validatingwebhookconfigurations", "mutatingwebhookconfigurations" ] verbs: [ "*" ] + - apiGroups: [ "" ] + resources: [ "nodes" ] + verbs: [ "get", "watch", "list" ] + - apiGroups: ["longhorn.io"] + resources: ["volumes", "nodes", "backingimages"] + verbs: [ "get", "watch", "list" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/go.mod b/go.mod index 7fcfc2fce..8fbd2765d 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ replace ( require ( github.com/ehazlett/simplelog v0.0.0-20200226020431-d374894e92a4 github.com/harvester/go-common v0.0.0-20250109132713-e748ce72a7ba - github.com/harvester/harvester v1.5.1 + github.com/harvester/harvester v1.6.0 github.com/harvester/webhook v0.1.5 github.com/jaypipes/ghw v0.8.1-0.20210701154532-dd036bd38c40 github.com/kevinburke/ssh_config v1.2.0 @@ -52,11 +52,11 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.3.0 - golang.org/x/crypto v0.36.0 + golang.org/x/crypto v0.38.0 k8s.io/api v0.33.1 k8s.io/apimachinery v0.33.1 k8s.io/client-go v12.0.0+incompatible - k8s.io/utils v0.0.0-20241210054802-24370beab758 + k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 ) require ( @@ -65,15 +65,15 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -97,20 +97,20 @@ require ( github.com/rancher/dynamiclistener v0.6.1 // indirect github.com/rancher/wrangler v1.1.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.3 // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.30.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.33.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -123,11 +123,11 @@ require ( k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-aggregator v0.33.1 // indirect k8s.io/kube-openapi v0.32.6 // indirect - kubevirt.io/api v1.4.0 // indirect + kubevirt.io/api v1.5.0 // indirect kubevirt.io/containerized-data-importer-api v1.61.0 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index de27230f9..0fa25a684 100644 --- a/go.sum +++ b/go.sum @@ -32,12 +32,16 @@ github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQm github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= +github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= @@ -55,6 +59,8 @@ github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= @@ -65,6 +71,8 @@ github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/e github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -119,6 +127,8 @@ github.com/harvester/go-common v0.0.0-20250109132713-e748ce72a7ba h1:U9Q4hwXeqni github.com/harvester/go-common v0.0.0-20250109132713-e748ce72a7ba/go.mod h1:VPPSa9KzRB1XPPkOAz4M4263UPm5bl/Xd3jEnALh3uo= github.com/harvester/harvester v1.5.1 h1:QXpTrHpk3zsK9WmSgMICKV+RPeT9fPgo43x+TMD6O00= github.com/harvester/harvester v1.5.1/go.mod h1:4exlBYpzXDasoMiYhqklwKEHrzd8+tDpg/vX5MVKKlA= +github.com/harvester/harvester v1.6.0 h1:SlC7YQyheSXIeoc/ys/eYZ00HRXJsocHxMUEoXHI+fw= +github.com/harvester/harvester v1.6.0/go.mod h1:ICKrTYyubLljyK2s/4tSyEFC+lJyeFB5OUF1/fS/CBc= github.com/harvester/webhook v0.1.5 h1:LSredtpCpr5n6XzGRW/qMeZDiSX2W206rI9UFFSHPdY= github.com/harvester/webhook v0.1.5/go.mod h1:3KHilfYv/vsGfL0QHaXmZ/o2lR6QGqpFpmO3UBOcV9Y= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -271,6 +281,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -323,6 +335,8 @@ golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn5 golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -343,6 +357,8 @@ golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -380,8 +396,12 @@ golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -397,6 +417,8 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -447,6 +469,8 @@ golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -473,6 +497,8 @@ golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -493,9 +519,13 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 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-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -523,6 +553,8 @@ golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 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= @@ -542,6 +574,8 @@ google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -596,8 +630,12 @@ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= kubevirt.io/api v1.4.0 h1:dDLyQLSp9obzsDrv3cyL1olIc/66IWVaGiR3gfPfgT0= kubevirt.io/api v1.4.0/go.mod h1:qcnumjJeOCo+qdYXf0OjpHGMhad0SAn4i0h6IAP+6Eg= +kubevirt.io/api v1.5.0 h1:3wjUf+xYZk6OThkHqDNI+AnvO5x4aPdyIkJT0xHJ2a0= +kubevirt.io/api v1.5.0/go.mod h1:B6OBbPKntPHOOtoxyRk5YKJHekWWOiwZDb2XncitcJ0= kubevirt.io/containerized-data-importer-api v1.61.0 h1:zqgn4/ftAPRK4ljZDZcgRiW25MMS7hLwAkGqRgVsng0= kubevirt.io/containerized-data-importer-api v1.61.0/go.mod h1:SDJjLGhbPyayDqAqawcGmVNapBp0KodOQvhKPLVGCQU= kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc= @@ -612,6 +650,8 @@ sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxO sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/pkg/utils/fake/backingimage.go b/pkg/utils/fake/backingimage.go new file mode 100644 index 000000000..5cef1fd75 --- /dev/null +++ b/pkg/utils/fake/backingimage.go @@ -0,0 +1,42 @@ +package fake + +import ( + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" + lhv1 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/generic" + "k8s.io/apimachinery/pkg/labels" +) + +type FakeBackingImageCache struct { + backingImages []*lhv1.BackingImage +} + +func NewBackingImageCache(backingImagesToServe []*lhv1.BackingImage) lhv1beta2.BackingImageCache { + return &FakeBackingImageCache{ + backingImages: backingImagesToServe, + } +} + +func (f *FakeBackingImageCache) AddIndexer(indexName string, indexer generic.Indexer[*lhv1.BackingImage]) { +} + +func (f *FakeBackingImageCache) Get(namespace string, name string) (*lhv1.BackingImage, error) { + panic("unimplemented") +} + +func (f *FakeBackingImageCache) GetByIndex(indexName string, key string) ([]*lhv1.BackingImage, error) { + var matchingImages []*lhv1.BackingImage + for _, bi := range f.backingImages { + if bi.Status.DiskFileStatusMap == nil { + continue + } + if _, ok := bi.Status.DiskFileStatusMap[key]; ok { + matchingImages = append(matchingImages, bi) + } + } + return matchingImages, nil +} + +func (f *FakeBackingImageCache) List(namespace string, selector labels.Selector) ([]*lhv1.BackingImage, error) { + panic("unimplemented") +} diff --git a/pkg/utils/fake/blockdevice.go b/pkg/utils/fake/blockdevice.go new file mode 100644 index 000000000..bcffdc098 --- /dev/null +++ b/pkg/utils/fake/blockdevice.go @@ -0,0 +1,52 @@ +package fake + +import ( + diskv1 "github.com/harvester/node-disk-manager/pkg/apis/harvesterhci.io/v1beta1" + ctldiskv1 "github.com/harvester/node-disk-manager/pkg/generated/controllers/harvesterhci.io/v1beta1" + "github.com/rancher/wrangler/v3/pkg/generic" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type FakeBlockDeviceCache struct { + devices []*diskv1.BlockDevice +} + +func NewBlockDeviceCache(devicesToServe []*diskv1.BlockDevice) ctldiskv1.BlockDeviceCache { + return &FakeBlockDeviceCache{ + devices: devicesToServe, + } +} + +func (c *FakeBlockDeviceCache) AddIndexer(indexName string, indexer generic.Indexer[*diskv1.BlockDevice]) { + panic("unimplemented") +} + +func (c *FakeBlockDeviceCache) Get(namespace, name string) (*diskv1.BlockDevice, error) { + for _, device := range c.devices { + if device.Namespace == namespace && device.Name == name { + return device.DeepCopy(), nil + } + } + return nil, errors.NewNotFound(schema.GroupResource{}, name) +} + +func (c *FakeBlockDeviceCache) GetByIndex(indexName, key string) ([]*diskv1.BlockDevice, error) { + panic("unimplemented") +} + +func (c *FakeBlockDeviceCache) List(namespace string, selector labels.Selector) ([]*diskv1.BlockDevice, error) { + var matching []*diskv1.BlockDevice + + for _, device := range c.devices { + if namespace != "" && device.Namespace != namespace { + continue + } + + if selector.Matches(labels.Set(device.GetLabels())) { + matching = append(matching, device.DeepCopy()) + } + } + return matching, nil +} diff --git a/pkg/utils/fake/longhornnode.go b/pkg/utils/fake/longhornnode.go new file mode 100644 index 000000000..1802ccc17 --- /dev/null +++ b/pkg/utils/fake/longhornnode.go @@ -0,0 +1,44 @@ +package fake + +import ( + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" + lhv1 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/generic" + "k8s.io/apimachinery/pkg/labels" +) + +type FakeLonghornNodeCache struct { + nodes []*lhv1.Node +} + +func NewLonghornNodeCache(nodesToServe []*lhv1.Node) lhv1beta2.NodeCache { + return &FakeLonghornNodeCache{ + nodes: nodesToServe, + } +} + +func (f *FakeLonghornNodeCache) AddIndexer(indexName string, indexer generic.Indexer[*lhv1.Node]) { +} + +func (f *FakeLonghornNodeCache) Get(namespace string, name string) (*lhv1.Node, error) { + panic("unimplemented") +} + +func (f *FakeLonghornNodeCache) GetByIndex(indexName string, key string) ([]*lhv1.Node, error) { + var matchingNodes []*lhv1.Node + for _, node := range f.nodes { + if node.Status.DiskStatus == nil { + continue + } + // The real index is built from the keys of this map. + if _, ok := node.Status.DiskStatus[key]; ok { + matchingNodes = append(matchingNodes, node) + } + } + + return matchingNodes, nil +} + +func (f *FakeLonghornNodeCache) List(namespace string, selector labels.Selector) ([]*lhv1.Node, error) { + panic("unimplemented") +} diff --git a/pkg/utils/fake/nodecache.go b/pkg/utils/fake/nodecache.go new file mode 100644 index 000000000..3815cfc91 --- /dev/null +++ b/pkg/utils/fake/nodecache.go @@ -0,0 +1,48 @@ +package fake + +import ( + ctlcorev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" + "github.com/rancher/wrangler/v3/pkg/generic" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type FakeNodeCache struct { + nodes []*v1.Node +} + +func NewNodeCache(nodesToServe []*v1.Node) ctlcorev1.NodeCache { + return &FakeNodeCache{ + nodes: nodesToServe, + } +} + +func (c *FakeNodeCache) AddIndexer(indexName string, indexer generic.Indexer[*v1.Node]) { + panic("unimplemented") +} + +func (c *FakeNodeCache) Get(name string) (*v1.Node, error) { + for _, node := range c.nodes { + if node.Name == name { + return node.DeepCopy(), nil + } + } + return nil, errors.NewNotFound(schema.GroupResource{}, name) +} + +func (c *FakeNodeCache) GetByIndex(indexName, key string) ([]*v1.Node, error) { + panic("unimplemented") + +} + +func (c *FakeNodeCache) List(selector labels.Selector) ([]*v1.Node, error) { + var matchingNodes []*v1.Node + for _, node := range c.nodes { + if selector.Matches(labels.Set(node.GetLabels())) { + matchingNodes = append(matchingNodes, node.DeepCopy()) + } + } + return matchingNodes, nil +} diff --git a/pkg/utils/fake/persistentvolumecache.go b/pkg/utils/fake/persistentvolumecache.go new file mode 100644 index 000000000..ad39ed794 --- /dev/null +++ b/pkg/utils/fake/persistentvolumecache.go @@ -0,0 +1,47 @@ +package fake + +import ( + ctlcorev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" + "github.com/rancher/wrangler/v3/pkg/generic" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type FakePersistentVolumeCache struct { + pvs []*v1.PersistentVolume +} + +func NewPersistentVolumeCache(pvsToServe []*v1.PersistentVolume) ctlcorev1.PersistentVolumeCache { + return &FakePersistentVolumeCache{ + pvs: pvsToServe, + } +} + +func (f *FakePersistentVolumeCache) AddIndexer(indexName string, indexer generic.Indexer[*v1.PersistentVolume]) { + panic("unimplemented") +} + +func (f *FakePersistentVolumeCache) Get(name string) (*v1.PersistentVolume, error) { + for _, pv := range f.pvs { + if pv.Name == name { + return pv.DeepCopy(), nil + } + } + return nil, errors.NewNotFound(schema.GroupResource{}, name) +} + +func (f *FakePersistentVolumeCache) GetByIndex(indexName string, key string) ([]*v1.PersistentVolume, error) { + panic("unimplemented") +} + +func (f *FakePersistentVolumeCache) List(selector labels.Selector) ([]*v1.PersistentVolume, error) { + var matchingPVs []*v1.PersistentVolume + for _, pv := range f.pvs { + if selector.Matches(labels.Set(pv.Labels)) { + matchingPVs = append(matchingPVs, pv.DeepCopy()) + } + } + return matchingPVs, nil +} diff --git a/pkg/utils/fake/storageclass.go b/pkg/utils/fake/storageclass.go new file mode 100644 index 000000000..ef35d0d20 --- /dev/null +++ b/pkg/utils/fake/storageclass.go @@ -0,0 +1,48 @@ +package fake + +import ( + ctlstoragev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/storage/v1" + "github.com/rancher/wrangler/v3/pkg/generic" + storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type FakeStorageClassCache struct { + scs []*storagev1.StorageClass +} + +func NewStorageClassCache(scsToServe []*storagev1.StorageClass) ctlstoragev1.StorageClassCache { + return &FakeStorageClassCache{ + scs: scsToServe, + } +} + +func (f *FakeStorageClassCache) AddIndexer(indexName string, indexer generic.Indexer[*storagev1.StorageClass]) { + panic("unimplemented") +} + +func (f *FakeStorageClassCache) Get(name string) (*storagev1.StorageClass, error) { + for _, storageClass := range f.scs { + if storageClass.Name == name { + return storageClass.DeepCopy(), nil + } + } + return nil, errors.NewNotFound(schema.GroupResource{}, name) +} + +func (f *FakeStorageClassCache) GetByIndex(indexName string, key string) ([]*storagev1.StorageClass, error) { + panic("unimplemented") +} + +func (f *FakeStorageClassCache) List(selector labels.Selector) ([]*storagev1.StorageClass, error) { + var matchingSCs []*storagev1.StorageClass + for _, storageClass := range f.scs { + // Check if the StorageClass's labels match the provided selector. + if selector.Matches(labels.Set(storageClass.Labels)) { + matchingSCs = append(matchingSCs, storageClass.DeepCopy()) + } + } + return matchingSCs, nil +} diff --git a/pkg/utils/fake/volumecache.go b/pkg/utils/fake/volumecache.go new file mode 100644 index 000000000..894922c06 --- /dev/null +++ b/pkg/utils/fake/volumecache.go @@ -0,0 +1,50 @@ +package fake + +import ( + lhv1 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/generic" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" +) + +type FakeVolumeCache struct { + volumes []*lhv1.Volume +} + +func NewVolumeCache(volsToServe []*lhv1.Volume) lhv1beta2.VolumeCache { + return &FakeVolumeCache{ + volumes: volsToServe, + } +} + +func (f *FakeVolumeCache) AddIndexer(indexName string, indexer generic.Indexer[*lhv1.Volume]) { + panic("unimplemented") +} + +func (f *FakeVolumeCache) Get(namespace string, name string) (*lhv1.Volume, error) { + for _, volume := range f.volumes { + if volume.Namespace == namespace && volume.Name == name { + return volume.DeepCopy(), nil + } + } + return nil, errors.NewNotFound(schema.GroupResource{}, name) +} + +func (f *FakeVolumeCache) GetByIndex(indexName string, key string) ([]*lhv1.Volume, error) { + panic("unimplemented") +} + +func (f *FakeVolumeCache) List(namespace string, selector labels.Selector) ([]*lhv1.Volume, error) { + var matchingVolumes []*lhv1.Volume + for _, volume := range f.volumes { + if namespace == "" || volume.Namespace == namespace { + if selector.Matches(labels.Set(volume.Labels)) { + matchingVolumes = append(matchingVolumes, volume.DeepCopy()) + } + } + } + return matchingVolumes, nil +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 9b4cf1351..4f88cd566 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -23,6 +23,10 @@ const ( LVMCSIDriver = "lvm.driver.harvesterhci.io" // LVMTopologyNodeKey is the key of LVM topology node LVMTopologyNodeKey = "topology.lvm.csi/node" + // DiskSelectorKey is the key which points to the disk tag value + DiskSelectorKey = "diskSelector" + // LonghornSystemNamespaceName is the namespace containing longhorn components + LonghornSystemNamespaceName = "longhorn-system" ) var CmdTimeoutError error diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index c7e2afccf..13e303798 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -2,9 +2,12 @@ package blockdevice import ( "fmt" + "strings" + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" werror "github.com/harvester/webhook/pkg/error" "github.com/harvester/webhook/pkg/server/admission" + lhv1 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" ctlcorev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" ctlstoragev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/storage/v1" "github.com/sirupsen/logrus" @@ -18,19 +21,36 @@ import ( "github.com/harvester/node-disk-manager/pkg/utils" ) +const ( + BackingImageByDiskUUID = "longhorn.io/backingimage-by-diskuuid" + NodeByBlockDeviceName = "longhorn.io/node-by-blockdevice-name" +) + type Validator struct { admission.DefaultValidator BlockdeviceCache ctldiskv1.BlockDeviceCache storageClassCache ctlstoragev1.StorageClassCache pvCache ctlcorev1.PersistentVolumeCache + nodeCache ctlcorev1.NodeCache + volumeCache lhv1beta2.VolumeCache + backingImageCache lhv1beta2.BackingImageCache + lhNodeCache lhv1beta2.NodeCache } -func NewBlockdeviceValidator(blockdeviceCache ctldiskv1.BlockDeviceCache, storageClassCache ctlstoragev1.StorageClassCache, pvCache ctlcorev1.PersistentVolumeCache) *Validator { +func NewBlockdeviceValidator(blockdeviceCache ctldiskv1.BlockDeviceCache, storageClassCache ctlstoragev1.StorageClassCache, + pvCache ctlcorev1.PersistentVolumeCache, volumeCache lhv1beta2.VolumeCache, nodeCache ctlcorev1.NodeCache, + backingImageCache lhv1beta2.BackingImageCache, lhNodeCache lhv1beta2.NodeCache) *Validator { + backingImageCache.AddIndexer(BackingImageByDiskUUID, backingImageByDiskUUIDIndexer) + lhNodeCache.AddIndexer(NodeByBlockDeviceName, nodeByBlockDeviceNameIndexer) return &Validator{ BlockdeviceCache: blockdeviceCache, storageClassCache: storageClassCache, pvCache: pvCache, + volumeCache: volumeCache, + nodeCache: nodeCache, + backingImageCache: backingImageCache, + lhNodeCache: lhNodeCache, } } @@ -45,10 +65,14 @@ func (v *Validator) Create(_ *admission.Request, newObj runtime.Object) error { func (v *Validator) Update(_ *admission.Request, oldObj, newObj runtime.Object) error { newBd := newObj.(*diskv1.BlockDevice) oldBd := oldObj.(*diskv1.BlockDevice) + if err := v.validateProvisioner(newBd); err != nil { return err } - return v.validateLVMProvisioner(oldBd, newBd) + if err := v.validateLVMProvisioner(oldBd, newBd); err != nil { + return err + } + return v.validateLHDisk(oldBd, newBd) } func (v *Validator) validateProvisioner(bd *diskv1.BlockDevice) error { @@ -62,6 +86,28 @@ func (v *Validator) validateProvisioner(bd *diskv1.BlockDevice) error { return nil } +func (v *Validator) validateLHDisk(oldBd, newBd *diskv1.BlockDevice) error { + if oldBd.Spec.Provisioner != nil && newBd.Spec.Provisioner != nil && + oldBd.Spec.Provisioner.Longhorn != nil && newBd.Spec.Provisioner.Longhorn != nil && + oldBd.Spec.Provision && !newBd.Spec.Provision { + nodeList, err := v.nodeCache.List(labels.Everything()) + if err != nil { + return err + } + if len(nodeList) == 1 && len(oldBd.Status.Tags) > 0 { + err := v.validateDegradedVolumes(oldBd) + if err != nil { + return err + } + err = v.validateBackingImages(oldBd) + if err != nil { + return err + } + } + } + return nil +} + // validateLVMProvisioner will check the block device with LVM provisioner and block // if there is already have any pvc created with in the target volume group func (v *Validator) validateLVMProvisioner(oldbd, newbd *diskv1.BlockDevice) error { @@ -127,6 +173,81 @@ func (v *Validator) validateVGIsAlreadyUsed(bd *diskv1.BlockDevice) error { return nil } +func (v *Validator) validateDegradedVolumes(old *diskv1.BlockDevice) error { + volumeList, err := v.volumeCache.List(utils.LonghornSystemNamespaceName, labels.Everything()) + if err != nil { + return err + } + if len(volumeList) == 0 { + return nil + } + degradedVolumes := []string{} + for _, vol := range volumeList { + if vol.Status.Robustness == lhv1.VolumeRobustnessDegraded { + degradedVolumes = append(degradedVolumes, vol.Name) + } + } + if len(degradedVolumes) == 0 { + return nil + } + selectorDegradedVol := make(map[string][]string) + for _, name := range degradedVolumes { + pv, err := v.pvCache.Get(name) + if err != nil { + return err + } + diskSelector := "" + if pv.Spec.CSI != nil { + diskSelector = pv.Spec.CSI.VolumeAttributes[utils.DiskSelectorKey] + } + if len(diskSelector) != 0 { + selectorDegradedVol[diskSelector] = append(selectorDegradedVol[diskSelector], pv.Name) + } + } + degradedVolString := "" + for _, diskTag := range old.Status.Tags { + if val, ok := selectorDegradedVol[diskTag]; ok { + degradedVolString += fmt.Sprintf(" %s: %v", diskTag, val) + } + } + if len(degradedVolString) > 0 { + return fmt.Errorf("the following tags with volumes:%s attached to disk: %s are in degraded state; evict disk before proceeding", + degradedVolString, old.Spec.DevPath) + } + return nil +} + +func (v *Validator) validateBackingImages(old *diskv1.BlockDevice) error { + lhNode, err := v.lhNodeCache.GetByIndex(NodeByBlockDeviceName, old.Name) + if err != nil { + return fmt.Errorf("error looking up node by blockdevice name %s: %w", old.Name, err) + } + if len(lhNode) != 1 { + return nil + } + diskStatus, ok := lhNode[0].Status.DiskStatus[old.Name] + if !ok || diskStatus.DiskUUID == "" { + return nil + } + uuid := diskStatus.DiskUUID + backingImages, err := v.backingImageCache.GetByIndex(BackingImageByDiskUUID, uuid) + if err != nil { + return fmt.Errorf("error looking up backing images by disk UUID %s: %w", uuid, err) + } + var failedBackingImages []string + for _, backingImage := range backingImages { + if backingImage.Status.DiskFileStatusMap[uuid].State != lhv1.BackingImageStateReady { + failedBackingImages = append(failedBackingImages, backingImage.Name) + } + } + if len(failedBackingImages) > 0 { + failedBackingImageList := strings.Join(failedBackingImages, ",") + return fmt.Errorf("the following backingimages: %v attached to blockdevice: %v are in a %s state; make sure state is fixed before disk deletion", + failedBackingImageList, old.Name, lhv1.BackingImageStateFailed) + } + return nil +} + func (v *Validator) Resource() admission.Resource { return admission.Resource{ Names: []string{"blockdevices"}, @@ -151,3 +272,25 @@ func getLVMTopologyNodes(sc *storagev1.StorageClass) string { } return "" } + +func backingImageByDiskUUIDIndexer(bi *lhv1.BackingImage) ([]string, error) { + if bi.Spec.DiskFileSpecMap == nil { + return []string{}, nil + } + diskUUIDs := make([]string, 0, len(bi.Status.DiskFileStatusMap)) + for key := range bi.Status.DiskFileStatusMap { + diskUUIDs = append(diskUUIDs, key) + } + return diskUUIDs, nil +} + +func nodeByBlockDeviceNameIndexer(node *lhv1.Node) ([]string, error) { + if node.Status.DiskStatus == nil { + return []string{}, nil + } + blockDeviceNames := make([]string, 0, len(node.Status.DiskStatus)) + for key := range node.Status.DiskStatus { + blockDeviceNames = append(blockDeviceNames, key) + } + return blockDeviceNames, nil +} diff --git a/pkg/webhook/blockdevice/validator_test.go b/pkg/webhook/blockdevice/validator_test.go new file mode 100644 index 000000000..f004510ad --- /dev/null +++ b/pkg/webhook/blockdevice/validator_test.go @@ -0,0 +1,629 @@ +package blockdevice + +import ( + "testing" + + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" + diskv1 "github.com/harvester/node-disk-manager/pkg/apis/harvesterhci.io/v1beta1" + ctldiskv1 "github.com/harvester/node-disk-manager/pkg/generated/controllers/harvesterhci.io/v1beta1" + "github.com/harvester/node-disk-manager/pkg/utils" + "github.com/harvester/node-disk-manager/pkg/utils/fake" + lhv1 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + ctlcorev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" + ctlstoragev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/storage/v1" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestUpdate(t *testing.T) { + tests := []struct { + name string + blockDeviceToCache []*diskv1.BlockDevice + scsToCache []*storagev1.StorageClass + pvsToCache []*v1.PersistentVolume + volsToCache []*lhv1.Volume + nodesToCache []*v1.Node + biToCache []*lhv1.BackingImage + lhNodesToCache []*lhv1.Node + oldBlockDevice *diskv1.BlockDevice + newBlockDeice *diskv1.BlockDevice + expectedErr bool + }{ + { + name: "disk removal passes with empty volumes on single node with successful backing images", + nodesToCache: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + }, + volsToCache: []*lhv1.Volume{}, + pvsToCache: []*v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeAttributes: map[string]string{ + utils.DiskSelectorKey: "disk1", + }, + }, + }, + }, + }, + }, + biToCache: []*lhv1.BackingImage{ + { + ObjectMeta: metav1.ObjectMeta{Name: "ready-image"}, + Status: lhv1.BackingImageStatus{ + DiskFileStatusMap: map[string]*lhv1.BackingImageDiskFileStatus{ + "1234": {State: lhv1.BackingImageStateReady}, + }, + }, + }, + }, + lhNodesToCache: []*lhv1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Name: "harvester"}, + Status: lhv1.NodeStatus{ + DiskStatus: map[string]*lhv1.DiskStatus{ + "testbd": {DiskUUID: "1234"}, + }, + }, + }, + }, + oldBlockDevice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: true, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + newBlockDeice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: false, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + expectedErr: false, + }, + { + name: "disk removal passes with healthy volumes on single node no backing images", + nodesToCache: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + }, + volsToCache: []*lhv1.Volume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + Namespace: utils.LonghornSystemNamespaceName, + }, + Status: lhv1.VolumeStatus{ + Robustness: lhv1.VolumeRobustnessHealthy, + }, + }, + }, + pvsToCache: []*v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeAttributes: map[string]string{ + utils.DiskSelectorKey: "disk1", + }, + }, + }, + }, + }, + }, + biToCache: []*lhv1.BackingImage{}, + lhNodesToCache: []*lhv1.Node{}, + oldBlockDevice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: true, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + newBlockDeice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: false, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + expectedErr: false, + }, + { + name: "disk removal passes with empty volumes on single node with failed backing image not related to the disk", + nodesToCache: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + }, + volsToCache: []*lhv1.Volume{}, + pvsToCache: []*v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeAttributes: map[string]string{ + utils.DiskSelectorKey: "disk1", + }, + }, + }, + }, + }, + }, + biToCache: []*lhv1.BackingImage{ + { + ObjectMeta: metav1.ObjectMeta{Name: "failed-image"}, + Status: lhv1.BackingImageStatus{ + DiskFileStatusMap: map[string]*lhv1.BackingImageDiskFileStatus{ + "1234": {State: lhv1.BackingImageStateReady}, + }, + }, + }, + }, + lhNodesToCache: []*lhv1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Name: "harvester"}, + }, + }, + oldBlockDevice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: true, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + newBlockDeice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: false, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + expectedErr: false, + }, + { + name: "disk removal rejected with degraded volume on single node with successful backing image", + nodesToCache: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + }, + volsToCache: []*lhv1.Volume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + Namespace: utils.LonghornSystemNamespaceName, + }, + Status: lhv1.VolumeStatus{ + Robustness: lhv1.VolumeRobustnessDegraded, + }, + }, + }, + pvsToCache: []*v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeAttributes: map[string]string{ + utils.DiskSelectorKey: "disk1", + }, + }, + }, + }, + }, + }, + biToCache: []*lhv1.BackingImage{ + { + ObjectMeta: metav1.ObjectMeta{Name: "ready-image"}, + Status: lhv1.BackingImageStatus{ + DiskFileStatusMap: map[string]*lhv1.BackingImageDiskFileStatus{ + "1234": {State: lhv1.BackingImageStateReady}, + }, + }, + }, + }, + lhNodesToCache: []*lhv1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Name: "harvester"}, + Status: lhv1.NodeStatus{ + DiskStatus: map[string]*lhv1.DiskStatus{ + "testbd": {DiskUUID: "1234"}, + }, + }, + }, + }, + oldBlockDevice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: true, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + newBlockDeice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: false, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + expectedErr: true, + }, + { + name: "disk removal rejected with healthy volume on single node but with failed backing image", + nodesToCache: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + }, + volsToCache: []*lhv1.Volume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + Namespace: utils.LonghornSystemNamespaceName, + }, + Status: lhv1.VolumeStatus{ + Robustness: lhv1.VolumeRobustnessHealthy, + }, + }, + }, + pvsToCache: []*v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeAttributes: map[string]string{ + utils.DiskSelectorKey: "disk1", + }, + }, + }, + }, + }, + }, + biToCache: []*lhv1.BackingImage{ + { + ObjectMeta: metav1.ObjectMeta{Name: "failed-image"}, + Status: lhv1.BackingImageStatus{ + DiskFileStatusMap: map[string]*lhv1.BackingImageDiskFileStatus{ + "1234": {State: lhv1.BackingImageStateFailed}, + }, + }, + }, + }, + lhNodesToCache: []*lhv1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Name: "harvester"}, + Status: lhv1.NodeStatus{ + DiskStatus: map[string]*lhv1.DiskStatus{ + "testbd": {DiskUUID: "1234"}, + }, + }, + }, + }, + oldBlockDevice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: true, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + newBlockDeice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: false, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + expectedErr: true, + }, + { + name: "disk removal passes on multi node with healthy volume but with failed backing image", + nodesToCache: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + }, + volsToCache: []*lhv1.Volume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + Namespace: utils.LonghornSystemNamespaceName, + }, + Status: lhv1.VolumeStatus{ + Robustness: lhv1.VolumeRobustnessHealthy, + }, + }, + }, + pvsToCache: []*v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeAttributes: map[string]string{ + utils.DiskSelectorKey: "disk1", + }, + }, + }, + }, + }, + }, + biToCache: []*lhv1.BackingImage{ + { + ObjectMeta: metav1.ObjectMeta{Name: "failed-image"}, + Status: lhv1.BackingImageStatus{ + DiskFileStatusMap: map[string]*lhv1.BackingImageDiskFileStatus{ + "1234": {State: lhv1.BackingImageStateFailed}, + }, + }, + }, + }, + lhNodesToCache: []*lhv1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Name: "harvester"}, + Status: lhv1.NodeStatus{ + DiskStatus: map[string]*lhv1.DiskStatus{ + "testbd": {DiskUUID: "1234"}, + }, + }, + }, + }, + oldBlockDevice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: true, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + newBlockDeice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: false, + }, + Status: diskv1.BlockDeviceStatus{ + Tags: []string{"disk1"}, + }, + }, + expectedErr: false, + }, + { + name: "disk removal passes on default disk with no tags", + nodesToCache: []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "harvester", + }, + }, + }, + volsToCache: []*lhv1.Volume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + Namespace: utils.LonghornSystemNamespaceName, + }, + Status: lhv1.VolumeStatus{ + Robustness: lhv1.VolumeRobustnessHealthy, + }, + }, + }, + pvsToCache: []*v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "vol-1", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeAttributes: map[string]string{ + utils.DiskSelectorKey: "disk1", + }, + }, + }, + }, + }, + }, + biToCache: []*lhv1.BackingImage{}, + lhNodesToCache: []*lhv1.Node{}, + oldBlockDevice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: true, + }, + }, + newBlockDeice: &diskv1.BlockDevice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "testbd", + }, + Spec: diskv1.BlockDeviceSpec{ + Provisioner: &diskv1.ProvisionerInfo{ + Longhorn: &diskv1.LonghornProvisionerInfo{}, + }, + Provision: false, + }, + }, + expectedErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var bdCache ctldiskv1.BlockDeviceCache + var scCache ctlstoragev1.StorageClassCache + var pvCache ctlcorev1.PersistentVolumeCache + var volCache lhv1beta2.VolumeCache + var nodeCache ctlcorev1.NodeCache + var lhNodeCache lhv1beta2.NodeCache + var backingImageCache lhv1beta2.BackingImageCache + if test.blockDeviceToCache != nil { + bdCache = fake.NewBlockDeviceCache(test.blockDeviceToCache) + } + if test.scsToCache != nil { + scCache = fake.NewStorageClassCache(test.scsToCache) + } + if test.pvsToCache != nil { + pvCache = fake.NewPersistentVolumeCache(test.pvsToCache) + } + if test.volsToCache != nil { + volCache = fake.NewVolumeCache(test.volsToCache) + } + if test.nodesToCache != nil { + nodeCache = fake.NewNodeCache(test.nodesToCache) + } + if test.lhNodesToCache != nil { + lhNodeCache = fake.NewLonghornNodeCache(test.lhNodesToCache) + } + if test.biToCache != nil { + backingImageCache = fake.NewBackingImageCache(test.biToCache) + } + validator := NewBlockdeviceValidator(bdCache, scCache, pvCache, volCache, nodeCache, backingImageCache, lhNodeCache) + err := validator.Update(nil, test.oldBlockDevice, test.newBlockDeice) + if test.expectedErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/scripts/unit-test b/scripts/unit-test new file mode 100755 index 000000000..da56b8dc4 --- /dev/null +++ b/scripts/unit-test @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/.. + +echo Running unit tests +go test -v -cover ./pkg/... \ No newline at end of file diff --git a/vendor/github.com/emicklei/go-restful/v3/CHANGES.md b/vendor/github.com/emicklei/go-restful/v3/CHANGES.md index 92b78048e..6f24dfff5 100644 --- a/vendor/github.com/emicklei/go-restful/v3/CHANGES.md +++ b/vendor/github.com/emicklei/go-restful/v3/CHANGES.md @@ -1,5 +1,8 @@ # Change history of go-restful +## [v3.12.2] - 2025-02-21 + +- allow empty payloads in post,put,patch, issue #580 ( thanks @liggitt, Jordan Liggitt) ## [v3.12.1] - 2024-05-28 @@ -18,7 +21,7 @@ - fix by restoring custom JSON handler functions (Mike Beaumont #540) -## [v3.12.0] - 2023-08-19 +## [v3.11.0] - 2023-08-19 - restored behavior as <= v3.9.0 with option to change path strategy using TrimRightSlashEnabled. diff --git a/vendor/github.com/emicklei/go-restful/v3/README.md b/vendor/github.com/emicklei/go-restful/v3/README.md index 7234604e4..3fb40d198 100644 --- a/vendor/github.com/emicklei/go-restful/v3/README.md +++ b/vendor/github.com/emicklei/go-restful/v3/README.md @@ -3,7 +3,7 @@ go-restful package for building REST-style Web Services using Google Go [![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/go-restful)](https://goreportcard.com/report/github.com/emicklei/go-restful) -[![GoDoc](https://godoc.org/github.com/emicklei/go-restful?status.svg)](https://pkg.go.dev/github.com/emicklei/go-restful) +[![Go Reference](https://pkg.go.dev/badge/github.com/emicklei/go-restful.svg)](https://pkg.go.dev/github.com/emicklei/go-restful/v3) [![codecov](https://codecov.io/gh/emicklei/go-restful/branch/master/graph/badge.svg)](https://codecov.io/gh/emicklei/go-restful) - [Code examples use v3](https://github.com/emicklei/go-restful/tree/v3/examples) diff --git a/vendor/github.com/emicklei/go-restful/v3/jsr311.go b/vendor/github.com/emicklei/go-restful/v3/jsr311.go index a9b3faaa8..7f04bd905 100644 --- a/vendor/github.com/emicklei/go-restful/v3/jsr311.go +++ b/vendor/github.com/emicklei/go-restful/v3/jsr311.go @@ -65,7 +65,7 @@ func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) ma return params } -// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 +// https://download.oracle.com/otndocs/jcp/jaxrs-1.1-mrel-eval-oth-JSpec/ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) { candidates := make([]*Route, 0, 8) for i, each := range routes { @@ -126,9 +126,7 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType) } - if httpRequest.ContentLength > 0 { - return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") - } + return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") } // accept @@ -151,20 +149,9 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R for _, candidate := range previous { available = append(available, candidate.Produces...) } - // if POST,PUT,PATCH without body - method, length := httpRequest.Method, httpRequest.Header.Get("Content-Length") - if (method == http.MethodPost || - method == http.MethodPut || - method == http.MethodPatch) && (length == "" || length == "0") { - return nil, NewError( - http.StatusUnsupportedMediaType, - fmt.Sprintf("415: Unsupported Media Type\n\nAvailable representations: %s", strings.Join(available, ", ")), - ) - } return nil, NewError( http.StatusNotAcceptable, - fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")), - ) + fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", "))) } // return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil return candidates[0], nil diff --git a/vendor/github.com/emicklei/go-restful/v3/route.go b/vendor/github.com/emicklei/go-restful/v3/route.go index 306c44be7..a2056e2ac 100644 --- a/vendor/github.com/emicklei/go-restful/v3/route.go +++ b/vendor/github.com/emicklei/go-restful/v3/route.go @@ -111,6 +111,8 @@ func (r Route) matchesAccept(mimeTypesWithQuality string) bool { } // Return whether this Route can consume content with a type specified by mimeTypes (can be empty). +// If the route does not specify Consumes then return true (*/*). +// If no content type is set then return true for GET,HEAD,OPTIONS,DELETE and TRACE. func (r Route) matchesContentType(mimeTypes string) bool { if len(r.Consumes) == 0 { diff --git a/vendor/github.com/fxamacker/cbor/v2/README.md b/vendor/github.com/fxamacker/cbor/v2/README.md index af0a79507..da9f9e6f0 100644 --- a/vendor/github.com/fxamacker/cbor/v2/README.md +++ b/vendor/github.com/fxamacker/cbor/v2/README.md @@ -1,6 +1,4 @@ -# CBOR Codec in Go - - +

CBOR Codec Go logo

[fxamacker/cbor](https://github.com/fxamacker/cbor) is a library for encoding and decoding [CBOR](https://www.rfc-editor.org/info/std94) and [CBOR Sequences](https://www.rfc-editor.org/rfc/rfc8742.html). @@ -8,23 +6,26 @@ CBOR is a [trusted alternative](https://www.rfc-editor.org/rfc/rfc8949.html#name `fxamacker/cbor` is used in projects by Arm Ltd., Cisco, EdgeX Foundry, Flow Foundation, Fraunhofer‑AISEC, Kubernetes, Let's Encrypt (ISRG), Linux Foundation, Microsoft, Mozilla, Oasis Protocol, Tailscale, Teleport, [etc](https://github.com/fxamacker/cbor#who-uses-fxamackercbor). -See [Quick Start](#quick-start) and [Releases](https://github.com/fxamacker/cbor/releases/). 🆕 `UnmarshalFirst` and `DiagnoseFirst` can decode CBOR Sequences. `cbor.MarshalToBuffer()` and `UserBufferEncMode` accepts user-specified buffer. +See [Quick Start](#quick-start) and [Releases](https://github.com/fxamacker/cbor/releases/). 🆕 `UnmarshalFirst` and `DiagnoseFirst` can decode CBOR Sequences. `MarshalToBuffer` and `UserBufferEncMode` accepts user-specified buffer. ## fxamacker/cbor [![](https://github.com/fxamacker/cbor/workflows/ci/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3Aci) -[![](https://github.com/fxamacker/cbor/workflows/cover%20%E2%89%A596%25/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A596%25%22) +[![](https://github.com/fxamacker/cbor/workflows/cover%20%E2%89%A597%25/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A597%25%22) [![CodeQL](https://github.com/fxamacker/cbor/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/fxamacker/cbor/actions/workflows/codeql-analysis.yml) [![](https://img.shields.io/badge/fuzzing-passing-44c010)](#fuzzing-and-code-coverage) [![Go Report Card](https://goreportcard.com/badge/github.com/fxamacker/cbor)](https://goreportcard.com/report/github.com/fxamacker/cbor) +[![](https://img.shields.io/ossf-scorecard/github.com/fxamacker/cbor?label=openssf%20scorecard)](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) `fxamacker/cbor` is a CBOR codec in full conformance with [IETF STD 94 (RFC 8949)](https://www.rfc-editor.org/info/std94). It also supports CBOR Sequences ([RFC 8742](https://www.rfc-editor.org/rfc/rfc8742.html)) and Extended Diagnostic Notation ([Appendix G of RFC 8610](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G)). Features include full support for CBOR tags, [Core Deterministic Encoding](https://www.rfc-editor.org/rfc/rfc8949.html#name-core-deterministic-encoding), duplicate map key detection, etc. +API is mostly same as `encoding/json`, plus interfaces that simplify concurrency and CBOR options. + Design balances trade-offs between security, speed, concurrency, encoded data size, usability, etc. -
Highlights

+

🔎  Highlights

__🚀  Speed__ @@ -38,7 +39,7 @@ Codec passed multiple confidential security assessments in 2022. No vulnerabili __🗜️  Data Size__ -Struct tags (`toarray`, `keyasint`, `omitempty`) automatically reduce size of encoded structs. Encoding optionally shrinks float64→32→16 when values fit. +Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) automatically reduce size of encoded structs. Encoding optionally shrinks float64→32→16 when values fit. __:jigsaw:  Usability__ @@ -58,164 +59,201 @@ Features include CBOR [extension points](https://www.rfc-editor.org/rfc/rfc8949. `fxamacker/cbor` has configurable limits, etc. that defend against malicious CBOR data. -By contrast, `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security). - -

Example decoding with encoding/gob 💥 fatal error (out of memory)

- -```Go -// Example of encoding/gob having "fatal error: runtime: out of memory" -// while decoding 181 bytes. -package main -import ( - "bytes" - "encoding/gob" - "encoding/hex" - "fmt" -) - -// Example data is from https://github.com/golang/go/issues/24446 -// (shortened to 181 bytes). -const data = "4dffb503010102303001ff30000109010130010800010130010800010130" + - "01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" + - "860001013001ff860001013001ffb80000001eff850401010e3030303030" + - "30303030303030303001ff3000010c0104000016ffb70201010830303030" + - "3030303001ff3000010c000030ffb6040405fcff00303030303030303030" + - "303030303030303030303030303030303030303030303030303030303030" + - "30" - -type X struct { - J *X - K map[string]int -} - -func main() { - raw, _ := hex.DecodeString(data) - decoder := gob.NewDecoder(bytes.NewReader(raw)) - - var x X - decoder.Decode(&x) // fatal error: runtime: out of memory - fmt.Println("Decoding finished.") -} -``` - -


- -
- -`fxamacker/cbor` is fast at rejecting malformed CBOR data. E.g. attempts to -decode 10 bytes of malicious CBOR data to `[]byte` (with default settings): - -| Codec | Speed (ns/op) | Memory | Allocs | -| :---- | ------------: | -----: | -----: | -| fxamacker/cbor 2.5.0 | 44 ± 5% | 32 B/op | 2 allocs/op | -| ugorji/go 1.2.11 | 5353261 ± 4% | 67111321 B/op | 13 allocs/op | - -
Benchmark details

- -Latest comparison used: -- Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` -- go1.19.10, linux/amd64, i5-13600K (disabled all e-cores, DDR4 @2933) -- go test -bench=. -benchmem -count=20 - -#### Prior comparisons - -| Codec | Speed (ns/op) | Memory | Allocs | -| :---- | ------------: | -----: | -----: | -| fxamacker/cbor 2.5.0-beta2 | 44.33 ± 2% | 32 B/op | 2 allocs/op | -| fxamacker/cbor 0.1.0 - 2.4.0 | ~44.68 ± 6% | 32 B/op | 2 allocs/op | -| ugorji/go 1.2.10 | 5524792.50 ± 3% | 67110491 B/op | 12 allocs/op | -| ugorji/go 1.1.0 - 1.2.6 | 💥 runtime: | out of memory: | cannot allocate | - -- Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` -- go1.19.6, linux/amd64, i5-13600K (DDR4) -- go test -bench=. -benchmem -count=20 - -


- -
- -### Smaller Encodings with Struct Tags - -Struct tags (`toarray`, `keyasint`, `omitempty`) reduce encoded size of structs. - -
Example encoding 3-level nested Go struct to 1 byte CBOR

- -https://go.dev/play/p/YxwvfPdFQG2 - -```Go -// Example encoding nested struct (with omitempty tag) -// - encoding/json: 18 byte JSON -// - fxamacker/cbor: 1 byte CBOR -package main - -import ( - "encoding/hex" - "encoding/json" - "fmt" - - "github.com/fxamacker/cbor/v2" -) - -type GrandChild struct { - Quux int `json:",omitempty"` -} - -type Child struct { - Baz int `json:",omitempty"` - Qux GrandChild `json:",omitempty"` -} - -type Parent struct { - Foo Child `json:",omitempty"` - Bar int `json:",omitempty"` -} - -func cb() { - results, _ := cbor.Marshal(Parent{}) - fmt.Println("hex(CBOR): " + hex.EncodeToString(results)) - - text, _ := cbor.Diagnose(results) // Diagnostic Notation - fmt.Println("DN: " + text) -} - -func js() { - results, _ := json.Marshal(Parent{}) - fmt.Println("hex(JSON): " + hex.EncodeToString(results)) - - text := string(results) // JSON - fmt.Println("JSON: " + text) -} - -func main() { - cb() - fmt.Println("-------------") - js() -} -``` - -Output (DN is Diagnostic Notation): -``` -hex(CBOR): a0 -DN: {} -------------- -hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d -JSON: {"Foo":{"Qux":{}}} -``` - -


- -
- -Example using different struct tags together: +Notably, `fxamacker/cbor` is fast at rejecting malformed CBOR data. + +> [!NOTE] +> Benchmarks rejecting 10 bytes of malicious CBOR data decoding to `[]byte`: +> +> | Codec | Speed (ns/op) | Memory | Allocs | +> | :---- | ------------: | -----: | -----: | +> | fxamacker/cbor 2.7.0 | 47 ± 7% | 32 B/op | 2 allocs/op | +> | ugorji/go 1.2.12 | 5878187 ± 3% | 67111556 B/op | 13 allocs/op | +> +> Faster hardware (overclocked DDR4 or DDR5) can reduce speed difference. +> +>
🔎  Benchmark details

+> +> Latest comparison for decoding CBOR data to Go `[]byte`: +> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` +> - go1.22.7, linux/amd64, i5-13600K (DDR4-2933, disabled e-cores) +> - go test -bench=. -benchmem -count=20 +> +> #### Prior comparisons +> +> | Codec | Speed (ns/op) | Memory | Allocs | +> | :---- | ------------: | -----: | -----: | +> | fxamacker/cbor 2.5.0-beta2 | 44.33 ± 2% | 32 B/op | 2 allocs/op | +> | fxamacker/cbor 0.1.0 - 2.4.0 | ~44.68 ± 6% | 32 B/op | 2 allocs/op | +> | ugorji/go 1.2.10 | 5524792.50 ± 3% | 67110491 B/op | 12 allocs/op | +> | ugorji/go 1.1.0 - 1.2.6 | 💥 runtime: | out of memory: | cannot allocate | +> +> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` +> - go1.19.6, linux/amd64, i5-13600K (DDR4) +> - go test -bench=. -benchmem -count=20 +> +>

+ +In contrast, some codecs can crash or use excessive resources while decoding bad data. + +> [!WARNING] +> Go's `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security). +> +>
🔎  gob fatal error (out of memory) 💥 decoding 181 bytes

+> +> ```Go +> // Example of encoding/gob having "fatal error: runtime: out of memory" +> // while decoding 181 bytes (all Go versions as of Dec. 8, 2024). +> package main +> import ( +> "bytes" +> "encoding/gob" +> "encoding/hex" +> "fmt" +> ) +> +> // Example data is from https://github.com/golang/go/issues/24446 +> // (shortened to 181 bytes). +> const data = "4dffb503010102303001ff30000109010130010800010130010800010130" + +> "01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" + +> "860001013001ff860001013001ffb80000001eff850401010e3030303030" + +> "30303030303030303001ff3000010c0104000016ffb70201010830303030" + +> "3030303001ff3000010c000030ffb6040405fcff00303030303030303030" + +> "303030303030303030303030303030303030303030303030303030303030" + +> "30" +> +> type X struct { +> J *X +> K map[string]int +> } +> +> func main() { +> raw, _ := hex.DecodeString(data) +> decoder := gob.NewDecoder(bytes.NewReader(raw)) +> +> var x X +> decoder.Decode(&x) // fatal error: runtime: out of memory +> fmt.Println("Decoding finished.") +> } +> ``` +> +> +>

+ +### Smaller Encodings with Struct Tag Options + +Struct tags automatically reduce encoded size of structs and improve speed. + +We can write less code by using struct tag options: +- `toarray`: encode without field names (decode back to original struct) +- `keyasint`: encode field names as integers (decode back to original struct) +- `omitempty`: omit empty fields when encoding +- `omitzero`: omit zero-value fields when encoding ![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags") -API is mostly same as `encoding/json`, plus interfaces that simplify concurrency for CBOR options. +> [!NOTE] +> `fxamacker/cbor` can encode a 3-level nested Go struct to 1 byte! +> - `encoding/json`: 18 bytes of JSON +> - `fxamacker/cbor`: 1 byte of CBOR +> +>
🔎  Encoding 3-level nested Go struct with omitempty

+> +> https://go.dev/play/p/YxwvfPdFQG2 +> +> ```Go +> // Example encoding nested struct (with omitempty tag) +> // - encoding/json: 18 byte JSON +> // - fxamacker/cbor: 1 byte CBOR +> +> package main +> +> import ( +> "encoding/hex" +> "encoding/json" +> "fmt" +> +> "github.com/fxamacker/cbor/v2" +> ) +> +> type GrandChild struct { +> Quux int `json:",omitempty"` +> } +> +> type Child struct { +> Baz int `json:",omitempty"` +> Qux GrandChild `json:",omitempty"` +> } +> +> type Parent struct { +> Foo Child `json:",omitempty"` +> Bar int `json:",omitempty"` +> } +> +> func cb() { +> results, _ := cbor.Marshal(Parent{}) +> fmt.Println("hex(CBOR): " + hex.EncodeToString(results)) +> +> text, _ := cbor.Diagnose(results) // Diagnostic Notation +> fmt.Println("DN: " + text) +> } +> +> func js() { +> results, _ := json.Marshal(Parent{}) +> fmt.Println("hex(JSON): " + hex.EncodeToString(results)) +> +> text := string(results) // JSON +> fmt.Println("JSON: " + text) +> } +> +> func main() { +> cb() +> fmt.Println("-------------") +> js() +> } +> ``` +> +> Output (DN is Diagnostic Notation): +> ``` +> hex(CBOR): a0 +> DN: {} +> ------------- +> hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d +> JSON: {"Foo":{"Qux":{}}} +> ``` +> +>

+ ## Quick Start __Install__: `go get github.com/fxamacker/cbor/v2` and `import "github.com/fxamacker/cbor/v2"`. +> [!TIP] +> +> Tinygo users can try beta/experimental branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta). +> +>
🔎  More about tinygo feature branch +> +> ### Tinygo +> +> Branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta) is based on fxamacker/cbor v2.7.0 and it can be compiled using tinygo v0.33 (also compiles with golang/go). +> +> It passes unit tests (with both go1.22 and tinygo v0.33) and is considered beta/experimental for tinygo. +> +> :warning: The `feature/cbor-tinygo-beta` branch does not get fuzz tested yet. +> +> Changes in this feature branch only affect tinygo compiled software. Summary of changes: +> - default `DecOptions.MaxNestedLevels` is reduced to 16 (was 32). User can specify higher limit but 24+ crashes tests when compiled with tinygo v0.33. +> - disabled decoding CBOR tag data to Go interface because tinygo v0.33 is missing needed feature. +> - encoding error message can be different when encoding function type. +> +> Related tinygo issues: +> - https://github.com/tinygo-org/tinygo/issues/4277 +> - https://github.com/tinygo-org/tinygo/issues/4458 +> +>
+ + ### Key Points This library can encode and decode CBOR (RFC 8949) and CBOR Sequences (RFC 8742). @@ -252,16 +290,17 @@ rest, err = cbor.UnmarshalFirst(b, &v) // decode []byte b to v // DiagnoseFirst translates first CBOR data item to text and returns remaining bytes. text, rest, err = cbor.DiagnoseFirst(b) // decode []byte b to Diagnostic Notation text -// NOTE: Unmarshal returns ExtraneousDataError if there are remaining bytes, -// but new funcs UnmarshalFirst and DiagnoseFirst do not. +// NOTE: Unmarshal() returns ExtraneousDataError if there are remaining bytes, but +// UnmarshalFirst() and DiagnoseFirst() allow trailing bytes. ``` -__IMPORTANT__: 👉 CBOR settings allow trade-offs between speed, security, encoding size, etc. - -- Different CBOR libraries may use different default settings. -- CBOR-based formats or protocols usually require specific settings. - -For example, WebAuthn uses "CTAP2 Canonical CBOR" which is available as a preset. +> [!IMPORTANT] +> CBOR settings allow trade-offs between speed, security, encoding size, etc. +> +> - Different CBOR libraries may use different default settings. +> - CBOR-based formats or protocols usually require specific settings. +> +> For example, WebAuthn uses "CTAP2 Canonical CBOR" which is available as a preset. ### Presets @@ -312,9 +351,9 @@ err = em.MarshalToBuffer(v, &buf) // encode v to provided buf ### Struct Tags -Struct tags (`toarray`, `keyasint`, `omitempty`) reduce encoded size of structs. +Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) reduce encoded size of structs. -
Example encoding 3-level nested Go struct to 1 byte CBOR

+

🔎  Example encoding 3-level nested Go struct to 1 byte CBOR

https://go.dev/play/p/YxwvfPdFQG2 @@ -382,13 +421,13 @@ JSON: {"Foo":{"Qux":{}}}

-
Example using several struct tags

+

🔎  Example using struct tag options

![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags")

-Struct tags simplify use of CBOR-based protocols that require CBOR arrays or maps with integer keys. +Struct tag options simplify use of CBOR-based protocols that require CBOR arrays or maps with integer keys. ### CBOR Tags @@ -404,7 +443,7 @@ em, err := opts.EncModeWithSharedTags(ts) // mutable shared CBOR tags `TagSet` and modes using it are safe for concurrent use. Equivalent API is available for `DecMode`. -
Example using TagSet and TagOptions

+

🔎  Example using TagSet and TagOptions

```go // Use signedCWT struct defined in "Decoding CWT" example. @@ -430,7 +469,7 @@ if err := dm.Unmarshal(data, &v); err != nil { em, _ := cbor.EncOptions{}.EncModeWithTags(tags) // Marshal signedCWT with tag number. -if data, err := cbor.Marshal(v); err != nil { +if data, err := em.Marshal(v); err != nil { return err } ``` @@ -439,7 +478,7 @@ if data, err := cbor.Marshal(v); err != nil { ### Functions and Interfaces -

Functions and interfaces at a glance

+

🔎  Functions and interfaces at a glance

Common functions with same API as `encoding/json`: - `Marshal`, `Unmarshal` @@ -472,11 +511,24 @@ Default limits may need to be increased for systems handling very large data (e. ## Status -v2.7.0 (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality. +v2.8.0 (March 30, 2025) is a small release primarily to add `omitzero` option to struct field tags and fix bugs. It passed fuzz tests (billions of executions) and is production quality. + +v2.8.0 and v2.7.1 fixes these 3 functions (when called directly by user apps) to use same error handling on bad inputs as `cbor.Unmarshal()`: +- `ByteString.UnmarshalCBOR()` +- `RawTag.UnmarshalCBOR()` +- `SimpleValue.UnmarshalCBOR()` + +The above 3 `UnmarshalCBOR()` functions were initially created for internal use and are deprecated now, so please use `Unmarshal()` or `UnmarshalFirst()` instead. To preserve backward compatibility, these deprecated functions were added to fuzz tests and will not be removed in v2. + +The minimum version of Go required to build: +- v2.8.0 requires go 1.20. +- v2.7.1 and older releases require go 1.17. For more details, see [release notes](https://github.com/fxamacker/cbor/releases). -### Prior Release +### Prior Releases + +v2.7.0 (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality. [v2.6.0](https://github.com/fxamacker/cbor/releases/tag/v2.6.0) (February 2024) adds important new features, optimizations, and bug fixes. It is especially useful to systems that need to convert data between CBOR and JSON. New options and optimizations improve handling of bignum, integers, maps, and strings. @@ -489,7 +541,7 @@ See [v2.5.0 release notes](https://github.com/fxamacker/cbor/releases/tag/v2.5.0 See ["Version and API Changes"](https://github.com/fxamacker/cbor#versions-and-api-changes) section for more info about version numbering, etc.