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 [](https://goreportcard.com/report/github.com/emicklei/go-restful) -[](https://pkg.go.dev/github.com/emicklei/go-restful) +[](https://pkg.go.dev/github.com/emicklei/go-restful/v3) [](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 - - +