1
0
Fork 0
mirror of https://github.com/kastenhq/kubestr.git synced 2024-12-14 11:57:56 +00:00

CSI snapshot restore (#37)

* starting with the validation

* Create application and use mocks

* Added snapshotter

* cleanup and full execution test

* last bit of wiring

* Nil checks for comments

* removing debug print

* unused variable
This commit is contained in:
Sirish Bathina 2020-12-07 11:16:53 -10:00 committed by GitHub
parent 8077b22799
commit 8d5674a59e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 4241 additions and 46 deletions

View file

@ -20,6 +20,8 @@ import (
"fmt"
"time"
"github.com/kastenhq/kubestr/pkg/csi"
csitypes "github.com/kastenhq/kubestr/pkg/csi/types"
"github.com/kastenhq/kubestr/pkg/fio"
"github.com/kastenhq/kubestr/pkg/kubestr"
"github.com/spf13/cobra"
@ -40,19 +42,36 @@ var (
},
}
fioCheckerStorageClass string
fioCheckerSize string
fioCheckerNamespace string
fioCheckerFilePath string
fioCheckerTestName string
fioCmd = &cobra.Command{
storageClass string
namespace string
fioCheckerSize string
fioCheckerFilePath string
fioCheckerTestName string
fioCmd = &cobra.Command{
Use: "fio",
Short: "Runs an fio test",
Long: `Run an fio test`,
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
Fio(ctx, output, fioCheckerStorageClass, fioCheckerSize, fioCheckerNamespace, fioCheckerTestName, fioCheckerFilePath)
Fio(ctx, output, storageClass, fioCheckerSize, namespace, fioCheckerTestName, fioCheckerFilePath)
},
}
csiCheckVolumeSnapshotClass string
csiCheckRunAsUser int64
csiCheckContainerImage string
csiCheckCleanup bool
csiCheckSkipCFSCheck bool
csiCheckCmd = &cobra.Command{
Use: "csicheck",
Short: "Runs the CSI snapshot restore check",
Long: "Validates a CSI provisioners ability to take a snapshot of an application and restore it",
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
CSICheck(ctx, output, namespace, storageClass, csiCheckVolumeSnapshotClass, csiCheckRunAsUser, csiCheckContainerImage, csiCheckCleanup, csiCheckSkipCFSCheck)
},
}
)
@ -61,13 +80,23 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "Options(json)")
rootCmd.AddCommand(fioCmd)
fioCmd.Flags().StringVarP(&fioCheckerStorageClass, "storageclass", "c", "", "The name of a storageclass. (Required)")
fioCmd.Flags().StringVarP(&storageClass, "storageclass", "s", "", "The name of a Storageclass. (Required)")
_ = fioCmd.MarkFlagRequired("storageclass")
fioCmd.Flags().StringVarP(&fioCheckerSize, "size", "s", fio.DefaultPVCSize, "The size of the volume used to run FIO.")
fioCmd.Flags().StringVarP(&fioCheckerNamespace, "namespace", "n", fio.DefaultNS, "The namespace used to run FIO.")
fioCmd.Flags().StringVarP(&fioCheckerSize, "size", "z", fio.DefaultPVCSize, "The size of the volume used to run FIO.")
fioCmd.Flags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace used to run FIO.")
fioCmd.Flags().StringVarP(&fioCheckerFilePath, "fiofile", "f", "", "The path to a an fio config file.")
fioCmd.Flags().StringVarP(&fioCheckerTestName, "testname", "t", "", "The Name of a predefined kubestr fio test. Options(default-fio)")
// //rootCmd.AddCommand(provCmd)
rootCmd.AddCommand(csiCheckCmd)
csiCheckCmd.Flags().StringVarP(&storageClass, "storageclass", "s", "", "The name of a Storageclass. (Required)")
_ = csiCheckCmd.MarkFlagRequired("storageclass")
csiCheckCmd.Flags().StringVarP(&csiCheckVolumeSnapshotClass, "volumesnapshotclass", "v", "", "The name of a VolumeSnapshotClass. (Required)")
_ = csiCheckCmd.MarkFlagRequired("volumesnapshotclass")
csiCheckCmd.Flags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace used to run the check.")
csiCheckCmd.Flags().StringVarP(&csiCheckContainerImage, "image", "i", "", "The container image used to create a pod.")
csiCheckCmd.Flags().BoolVarP(&csiCheckCleanup, "cleanup", "c", true, "Clean up the objects created by tool")
csiCheckCmd.Flags().Int64VarP(&csiCheckRunAsUser, "runAsUser", "u", 0, "Runs the CSI check using pods as a user (int)")
csiCheckCmd.Flags().BoolVarP(&csiCheckSkipCFSCheck, "skipCFScheck", "k", false, "Use this flag to skip validating the ability to clone a snapshot.")
}
// Execute executes the main command
@ -117,14 +146,17 @@ func Baseline(ctx context.Context, output string) {
// Fio executes the FIO test.
func Fio(ctx context.Context, output, storageclass, size, namespace, jobName, fioFilePath string) {
p, err := kubestr.NewKubestr()
cli, err := kubestr.LoadKubeCli()
if err != nil {
fmt.Println(err.Error())
return
}
fioRunner := &fio.FIOrunner{
Cli: cli,
}
testName := "FIO test results"
var result *kubestr.TestOutput
if fioResult, err := p.Fio.RunFio(ctx, &fio.RunFIOArgs{
if fioResult, err := fioRunner.RunFio(ctx, &fio.RunFIOArgs{
StorageClass: storageclass,
Size: size,
Namespace: namespace,
@ -143,3 +175,51 @@ func Fio(ctx context.Context, output, storageclass, size, namespace, jobName, fi
}
result.Print()
}
func CSICheck(ctx context.Context, output,
namespace string,
storageclass string,
volumesnapshotclass string,
runAsUser int64,
containerImage string,
cleanup bool,
skipCFScheck bool,
) {
testName := "CSI checker test"
kubecli, err := kubestr.LoadKubeCli()
if err != nil {
fmt.Printf("Failed to load kubeCLi (%s)", err.Error())
return
}
dyncli, err := kubestr.LoadDynCli()
if err != nil {
fmt.Printf("Failed to load kubeCLi (%s)", err.Error())
return
}
csiCheckRunner := &csi.SnapshotRestoreRunner{
KubeCli: kubecli,
DynCli: dyncli,
}
var result *kubestr.TestOutput
csiCheckResult, err := csiCheckRunner.RunSnapshotRestore(ctx, &csitypes.CSISnapshotRestoreArgs{
StorageClass: storageclass,
VolumeSnapshotClass: volumesnapshotclass,
Namespace: namespace,
RunAsUser: runAsUser,
ContainerImage: containerImage,
Cleanup: cleanup,
SkipCFSCheck: skipCFScheck,
})
if err != nil {
result = kubestr.MakeTestOutput(testName, kubestr.StatusError, err.Error(), csiCheckResult)
} else {
result = kubestr.MakeTestOutput(testName, kubestr.StatusOK, "CSI application successfully snapshotted and restored.", csiCheckResult)
}
if output == "json" {
jsonRes, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(jsonRes))
return
}
result.Print()
}

View file

@ -28,12 +28,11 @@ Name | CSI Driver Name | Compatible with CSI Version(s) | Description | Persiste
[Datatom-InfinityCSI](https://github.com/datatom-infinity/infinity-csi) | `csi-infiblock-plugin` | v0.3, v1.0.0, v1.1.0 | A Container Storage Interface (CSI) Driver for DATATOM Infinity storage | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Topology
[Datatom-InfinityCSI (filesystem)](https://github.com/datatom-infinity/infinity-csi) | `csi-infifs-plugin` | v0.3, v1.0.0, v1.1.0 | A Container Storage Interface (CSI) Driver for DATATOM Infinity filesystem storage | Persistent | Read/Write Multiple Pods | Yes | Expansion
[Datera](https://github.com/Datera/datera-csi) | `dsp.csi.daterainc.io` | v1.0 | A Container Storage Interface (CSI) Driver for Datera Data Services Platform (DSP) | Persistent | Read/Write Single Pod | Yes |Snapshot
[Dell EMC Isilon](https://github.com/dell/csi-isilon) | `csi-isilon.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC Isilon](https://www.delltechnologies.com/en-us/storage/isilon/index.htm) | Persistent | Read/Write Multiple Pods | Yes | Snapshot
[Dell EMC PowerMax](https://github.com/dell/csi-powermax) | `csi-powermax.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC PowerMax](https://www.delltechnologies.com/en-us/storage/powermax.htm) | Persistent | Read/Write Single Pod | Yes | Snapshot
[Dell EMC PowerStore](https://github.com/dell/csi-powerstore) | `csi-powerstore.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC PowerStore](https://www.delltechnologies.com/en-us/storage/powerstore-storage-appliance.htm) | Persistent | Read/Write Single Pod | Yes |
[Dell EMC Unity](https://github.com/dell/csi-unity) | `csi-unity.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC Unity](https://www.delltechnologies.com/en-us/storage/unity.htm) | Persistent | Read/Write Single Pod | Yes | Snapshot
[Dell EMC VxFlexOS](https://github.com/dell/csi-vxflexos) | `csi-vxflexos.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC VxFlexOS](https://www.delltechnologies.com/en-us/hyperconverged-infrastructure/vxflex.htm) | Persistent | Read/Write Single Pod | Yes | Snapshot
[Dell EMC XtremIO](https://github.com/dell/csi-xtremio-deploy) | `csi-xtremio.dellemc.com` | v1.0 | A Container Storage Interface (CSI) Driver for [Dell EMC XtremIO](https://www.delltechnologies.com/en-us/storage/xtremio-all-flash.htm) | Persistent | Read/Write Single Pod | Yes | Snapshot
[Dell EMC PowerScale](https://github.com/dell/csi-powerscale) | `csi-isilon.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC PowerScale](https://www.delltechnologies.com/en-us/storage/powerscale.htm) | Persistent | Read/Write Multiple Pods | Yes | Snapshot, Expansion, Cloning
[Dell EMC PowerMax](https://github.com/dell/csi-powermax) | `csi-powermax.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC PowerMax](https://www.delltechnologies.com/en-us/storage/powermax.htm) | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Cloning
[Dell EMC PowerStore](https://github.com/dell/csi-powerstore) | `csi-powerstore.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC PowerStore](https://www.delltechnologies.com/en-us/storage/powerstore-storage-appliance.htm) | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Cloning
[Dell EMC Unity](https://github.com/dell/csi-unity) | `csi-unity.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC Unity](https://www.delltechnologies.com/en-us/storage/unity.htm) | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Cloning
[Dell EMC VxFlexOS](https://github.com/dell/csi-vxflexos) | `csi-vxflexos.dellemc.com` | v1.1 | A Container Storage Interface (CSI) Driver for [Dell EMC VxFlexOS](https://www.delltechnologies.com/en-us/hyperconverged-infrastructure/vxflex.htm) | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Topology
[democratic-csi](https://github.com/democratic-csi/democratic-csi) | `org.democratic-csi.[X]` | v1.0,v1.1,v1.2 | Generic CSI plugin supporting zfs based solutions ([FreeNAS](https://www.freenas.org/) / [TrueNAS](https://www.truenas.com/) and [ZoL](https://zfsonlinux.org/) solutions such as [Ubuntu](https://ubuntu.com/)) | Persistent and Ephemeral | Read/Write Single Pod (Block Volume) <br/><br/> Read/Write Multiple Pods (File Volume) | Yes | Raw Block, Snapshot, Expansion, Cloning
[Diamanti-CSI](https://diamanti.com/use-cases/io-acceleration/#csi) | `dcx.csi.diamanti.com` | v1.0 | A Container Storage Interface (CSI) Driver for Diamanti DCX Platform | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion
[DigitalOcean Block Storage](https://github.com/digitalocean/csi-digitalocean) | `dobs.csi.digitalocean.com` | v0.3, v1.0 | A Container Storage Interface (CSI) Driver for DigitalOcean Block Storage | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion
@ -46,7 +45,7 @@ Name | CSI Driver Name | Compatible with CSI Version(s) | Description | Persiste
[GlusterFS](https://github.com/gluster/gluster-csi-driver) | `org.gluster.glusterfs` | v0.3, v1.0 | A Container Storage Interface (CSI) Driver for GlusterFS | Persistent | Read/Write Multiple Pods | Yes | Snapshot
[Gluster VirtBlock](https://github.com/gluster/gluster-csi-driver) | `org.gluster.glustervirtblock` | v0.3, v1.0 | A Container Storage Interface (CSI) Driver for Gluster Virtual Block volumes | Persistent | Read/Write Single Pod | Yes |
[Hammerspace CSI](https://github.com/hammer-space/csi-plugin) | `com.hammerspace.csi` | v0.3, v1.0 | A Container Storage Interface (CSI) Driver for Hammerspace Storage | Persistent | Read/Write Multiple Pods | Yes | Raw Block, Snapshot
[Hedvig](https://documentation.commvault.com/commvault/hedvig/others/pdf/Hedvig_CSI_User_Guide.pdf) | `io.hedvig.csi` | v1.0 | A Container Storage Interface (CSI) Driver for Hedvig | Persistent | Read/Write Multiple Pods | Yes | Snapshot
[Hedvig](https://documentation.commvault.com/commvault/hedvig/others/pdf/Hedvig_CSI_User_Guide.pdf) | `io.hedvig.csi` | v1.0 | A Container Storage Interface (CSI) Driver for Hedvig | Persistent | Read/Write Multiple Pods | Yes | Raw Block, Snapshot, Expansion
[Hetzner Cloud Volumes CSI](https://github.com/hetznercloud/csi-driver) | `csi.hetzner.cloud` | v0.3, v1.0 | A Container Storage Interface (CSI) Driver for Hetzner Cloud Volumes | Persistent | Read/Write Single Pod | Yes | Raw Block, Expansion
[Hitachi Vantara](https://knowledge.hitachivantara.com/Documents/Adapters_and_Drivers/Storage_Adapters_and_Drivers/Containers) | `hspc.csi.hitachi.com` | v1.2 | A Container Storage Interface (CSI) Driver for VSP series Storage | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Cloning
[HPE](https://github.com/hpe-storage/csi-driver) | `csi.hpe.com` | v1.0, v1.1, v1.2 | A [multi-platform](https://scod.hpedev.io/csi_driver) Container Storage Interface (CSI) driver. Supports [HPE Nimble Storage](https://hpe.com/storage/nimble), [HPE Primera](https://hpe.com/storage/primera) and [HPE 3PAR](https://hpe.com/storage/3par) | Persistent and Ephemeral | Read/Write Multiple Pods | Yes | Raw Block, Snapshot, Expansion, Cloning
@ -60,6 +59,7 @@ Name | CSI Driver Name | Compatible with CSI Version(s) | Description | Persiste
[Intel PMEM-CSI](https://github.com/intel/pmem-csi) | `pmem-csi.intel.com` | v1.0 | A Container Storage Interface (CSI) driver for [PMEM](https://pmem.io/) from Intel | Persistent and Ephemeral | Read/Write Single Pod | Yes | Raw Block
[JuiceFS](https://github.com/juicedata/juicefs-csi-driver) | `csi.juicefs.com` | v0.3, v1.0 | A Container Storage Interface (CSI) Driver for JuiceFS File System | Persistent | Read/Write Multiple Pod | Yes |
[kaDalu](https://github.com/kadalu/kadalu) | `org.kadalu.gluster` | v0.3 | A CSI Driver (and operator) for GlusterFS | Persistent | Read/Write Multiple Pods | Yes |
[KumoScale Block Storage](https://github.com/KioxiaAmerica/kumoscale-csi) | `kumoscale.kioxia.com` | v1.0 | A Container Storage Interface (CSI) Driver for KumoScale Block Storage | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Topology
[Linode Block Storage](https://github.com/linode/linode-blockstorage-csi-driver) | `linodebs.csi.linode.com` | v1.0 | A Container Storage Interface (CSI) Driver for Linode Block Storage | Persistent | Read/Write Single Pod | Yes |
[LINSTOR](https://github.com/LINBIT/linstor-csi) | `io.drbd.linstor-csi` | v1.1 | A Container Storage Interface (CSI) Driver for [LINSTOR](https://www.linbit.com/en/linstor/) volumes | Persistent | Read/Write Single Pod | Yes | Snapshot
[Longhorn](https://github.com/longhorn/longhorn) | `driver.longhorn.io` | v1.1 | A Container Storage Interface (CSI) Driver for [Longhorn](https://longhorn.io/) volumes | Persistent | Read/Write Single Node | Yes | Raw Block
@ -75,13 +75,14 @@ Name | CSI Driver Name | Compatible with CSI Version(s) | Description | Persiste
[OpenSDS](https://github.com/opensds/nbp/tree/master/csi) | `csi-opensdsplugin` | v1.0 | A Container Storage Interface (CSI) Driver for [OpenSDS]((https://www.opensds.io/)) | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot
[Open-E](https://github.com/open-e/JovianDSS-KubernetesCSI) | `com.open-e.joviandss.csi` | v1.0 | A Container Storage Interface (CSI) Driver for Open-E JovianDSS Storage | Persistent | Read/Write Single Pod | Yes | Snapshot, Cloning
[Portworx](https://github.com/libopenstorage/openstorage/tree/master/csi) | `pxd.openstorage.org` | v0.3, v1.1 | A Container Storage Interface (CSI) Driver for [Portworx](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/csi/) | Persistent | Read/Write Multiple Pods | Yes | Snapshot, Expansion
[Pure Storage CSI](https://github.com/purestorage/pso-csi)| `pure-csi` | v1.0, v1.1, v1.2 | A Container Storage Interface (CSI) Driver for Pure Storage's [Pure Service Orchestrator](https://purestorage.com/containers) | Persistent | Read/Write Multiple Pods | Yes | Snapshot, Cloning, Raw Block, Topology, Expansion
[QingCloud CSI](https://github.com/yunify/qingcloud-csi)| `disk.csi.qingcloud.com` | v1.1 | A Container Storage Interface (CSI) Driver for QingCloud Block Storage | Persistent | Read/Write Single Pod | Yes | Snapshot, Expansion, Cloning
[QingStor CSI](https://github.com/yunify/qingstor-csi) | `csi-neonsan` | v0.3 | A Container Storage Interface (CSI) Driver for NeonSAN storage system | Persistent | Read/Write Single Pod | Yes | Snapshot
[Pure Storage CSI](https://github.com/purestorage/pso-csi)| `pure-csi` | v1.0, v1.1, v1.2, v1.3 | A Container Storage Interface (CSI) Driver for Pure Storage's [Pure Service Orchestrator](https://purestorage.com/containers) | Persistent and Ephemeral | Read/Write Multiple Pods | Yes | Snapshot, Cloning, Raw Block, Topology, Expansion
[QingCloud CSI](https://github.com/yunify/qingcloud-csi)| `disk.csi.qingcloud.com` | v1.1 | A Container Storage Interface (CSI) Driver for QingCloud Block Storage | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Cloning
[QingStor CSI](https://github.com/yunify/qingstor-csi) | `neonsan.csi.qingstor.com` | v0.3, v1.1 | A Container Storage Interface (CSI) Driver for NeonSAN storage system | Persistent | Read/Write Multiple Pods | Yes | Raw Block, Snapshot, Expansion, Cloning
[Quobyte](https://github.com/quobyte/quobyte-csi) | `quobyte-csi` | v0.2 | A Container Storage Interface (CSI) Driver for Quobyte | Persistent | Read/Write Multiple Pods | Yes |
[ROBIN](https://get.robin.io/) | `robin` | v0.3, v1.0 | A Container Storage Interface (CSI) Driver for [ROBIN](https://docs.robin.io) | Persistent | Read/Write Multiple Pods | Yes | Raw Block, Snapshot, Cloning
[SandStone](https://github.com/sandstone-storage/sandstone-csi-driver) | `csi-sandstone-plugin` | v1.0 | A Container Storage Interface (CSI) Driver for SandStone USP | Persistent | Read/Write Multiple Pods | Yes | Raw Block, Snapshot, Expansion, Cloning
[Sangfor-EDS](https://github.com/evan37717/sangfor-eds-csi) | `eds.csi.sangfor.com` | v1.0 | A Container Storage Interface (CSI) Driver for Sangfor Distributed File Storage(EDS) | Persistent | Read/Write Multiple Pods | Yes |
[Sangfor-EDS-Block-Storage](https://github.com/eds-wzc/sangfor-eds-csi) | `eds.csi.block.sangfor.com` | v1.0 | A Container Storage Interface (CSI) Driver for Sangfor Block Storage(EDS) | Persistent | Read/Write Single Pod | Yes |
[SeaweedFS](https://github.com/seaweedfs/seaweedfs-csi-driver) | `seaweedfs-csi-driver` | v1.0 | A Container Storage Interface (CSI Driver for [SeaweedFS](https://github.com/chrislusf/seaweedfs)) | Persistent | Read/Write Multiple Pods | Yes |
[Secrets Store CSI Driver](https://github.com/kubernetes-sigs/secrets-store-csi-driver) | `secrets-store.csi.k8s.io` | v0.0.10 | A Container Storage Interface (CSI) Driver for mounting secrets, keys, and certs stored in enterprise-grade external secrets stores as volumes. | Ephemeral | N/A | N/A |
[SmartX](http://www.smartx.com/?locale=en) | `csi-smtx-plugin` | v1.0 | A Container Storage Interface (CSI) Driver for SmartX ZBS Storage | Persistent | Read/Write Multiple Pods | Yes | Snapshot, Expansion
@ -97,7 +98,9 @@ Name | CSI Driver Name | Compatible with CSI Version(s) | Description | Persiste
[XSKY-EBS](https://xsky-storage.github.io/xsky-csi-driver/csi-block.html) | `csi.block.xsky.com` | v1.0 | A Container Storage Interface (CSI) Driver for XSKY Distributed Block Storage (X-EBS) | Persistent | Read/Write Single Pod | Yes | Raw Block, Snapshot, Expansion, Cloning
[XSKY-EUS](https://xsky-storage.github.io/xsky-csi-driver/csi-fs.html) | `csi.fs.xsky.com` | v1.0 | A Container Storage Interface (CSI) Driver for XSKY Distributed File Storage (X-EUS) | Persistent | Read/Write Multiple Pods | Yes |
[Vault](https://github.com/kubevault/csi-driver) | `secrets.csi.kubevault.com` | v1.0 | A Container Storage Interface (CSI) Driver for mounting HashiCorp Vault secrets as volumes. | Ephemeral | N/A | N/A |
[vSphere](https://github.com/kubernetes-sigs/vsphere-csi-driver) | `csi.vsphere.vmware.com` | v2.0.0 | A Container Storage Interface (CSI) Driver for VMware vSphere | Persistent | Read/Write Single Pod (Block Volume) <br/><br/> Read/Write Multiple Pods (File Volume) | Yes | Raw Block,<br/><br/>Expansion (Block Volume),<br/><br/>Topology Aware (Block Volume)
[Veritas InfoScale Volumes](https://www.veritas.com/solution/virtualization/containers.html) | `org.veritas.infoscale` | v1.2 | A Container Storage Interface (CSI) Driver for Veritas InfoScale volumes | Persistent | Read/Write Multiple Pods | Yes | Snapshot, Expansion, Cloning
[vSphere](https://github.com/kubernetes-sigs/vsphere-csi-driver) | `csi.vsphere.vmware.com` | v1.0 | A Container Storage Interface (CSI) Driver for VMware vSphere | Persistent | Read/Write Single Pod (Block Volume) <br/><br/> Read/Write Multiple Pods (File Volume) | Yes | Raw Block,<br/><br/>Expansion (Block Volume),<br/><br/>Topology Aware (Block Volume)
[Vultr Block Storage](https://github.com/vultr/vultr-csi) | `block.csi.vultr.com` | v1.2 | A Container Storage Interface (CSI) Driver for Vultr Block Storage | Persistent | Read/Write Single Pod | Yes |
[WekaIO](https://github.com/weka/csi-wekafs) | `csi.weka.io` | v1.0 | A Container Storage Interface (CSI) Driver for mounting WekaIO WekaFS filesystem as volumes | Persistent | Read/Write Multiple Pods | Yes |
[Yandex.Cloud](https://github.com/flant/yandex-csi-driver) | `yandex.csi.flant.com` | v1.2 | A Container Storage Interface (CSI) plugin for Yandex.Cloud Compute Disks | Persistent | Read/Write Single Pod | Yes |
[YanRongYun](http://www.yanrongyun.com/) | ? | v1.0 | A Container Storage Interface (CSI) Driver for YanRong YRCloudFile Storage | Persistent | Read/Write Multiple Pods | Yes |

5
go.mod
View file

@ -8,10 +8,13 @@ replace (
)
require (
github.com/golang/mock v1.4.4
github.com/kanisterio/kanister v0.0.0-00010101000000-000000000000
github.com/kubernetes-csi/external-snapshotter v1.2.2
github.com/moby/buildkit v0.8.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
k8s.io/api v0.19.0
k8s.io/apimachinery v0.19.0
k8s.io/client-go v0.19.0

623
go.sum

File diff suppressed because it is too large Load diff

11
pkg/csi/csi.go Normal file
View file

@ -0,0 +1,11 @@
package csi
import (
"context"
"github.com/kastenhq/kubestr/pkg/csi/types"
)
type CSI interface {
RunSnapshotRestore(ctx context.Context, args *types.CSISnapshotRestoreArgs) (*types.CSISnapshotRestoreResults, error)
}

View file

@ -0,0 +1,49 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: ApiVersionFetcher)
// Package mocks is a generated GoMock package.
package mocks
import (
gomock "github.com/golang/mock/gomock"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
reflect "reflect"
)
// MockApiVersionFetcher is a mock of ApiVersionFetcher interface
type MockApiVersionFetcher struct {
ctrl *gomock.Controller
recorder *MockApiVersionFetcherMockRecorder
}
// MockApiVersionFetcherMockRecorder is the mock recorder for MockApiVersionFetcher
type MockApiVersionFetcherMockRecorder struct {
mock *MockApiVersionFetcher
}
// NewMockApiVersionFetcher creates a new mock instance
func NewMockApiVersionFetcher(ctrl *gomock.Controller) *MockApiVersionFetcher {
mock := &MockApiVersionFetcher{ctrl: ctrl}
mock.recorder = &MockApiVersionFetcherMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockApiVersionFetcher) EXPECT() *MockApiVersionFetcherMockRecorder {
return m.recorder
}
// GetCSISnapshotGroupVersion mocks base method
func (m *MockApiVersionFetcher) GetCSISnapshotGroupVersion() (*v1.GroupVersionForDiscovery, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCSISnapshotGroupVersion")
ret0, _ := ret[0].(*v1.GroupVersionForDiscovery)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCSISnapshotGroupVersion indicates an expected call of GetCSISnapshotGroupVersion
func (mr *MockApiVersionFetcherMockRecorder) GetCSISnapshotGroupVersion() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCSISnapshotGroupVersion", reflect.TypeOf((*MockApiVersionFetcher)(nil).GetCSISnapshotGroupVersion))
}

View file

@ -0,0 +1,80 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: ApplicationCreator)
// Package mocks is a generated GoMock package.
package mocks
import (
context "context"
gomock "github.com/golang/mock/gomock"
types "github.com/kastenhq/kubestr/pkg/csi/types"
v1 "k8s.io/api/core/v1"
reflect "reflect"
)
// MockApplicationCreator is a mock of ApplicationCreator interface
type MockApplicationCreator struct {
ctrl *gomock.Controller
recorder *MockApplicationCreatorMockRecorder
}
// MockApplicationCreatorMockRecorder is the mock recorder for MockApplicationCreator
type MockApplicationCreatorMockRecorder struct {
mock *MockApplicationCreator
}
// NewMockApplicationCreator creates a new mock instance
func NewMockApplicationCreator(ctrl *gomock.Controller) *MockApplicationCreator {
mock := &MockApplicationCreator{ctrl: ctrl}
mock.recorder = &MockApplicationCreatorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockApplicationCreator) EXPECT() *MockApplicationCreatorMockRecorder {
return m.recorder
}
// CreatePVC mocks base method
func (m *MockApplicationCreator) CreatePVC(arg0 context.Context, arg1 *types.CreatePVCArgs) (*v1.PersistentVolumeClaim, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreatePVC", arg0, arg1)
ret0, _ := ret[0].(*v1.PersistentVolumeClaim)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreatePVC indicates an expected call of CreatePVC
func (mr *MockApplicationCreatorMockRecorder) CreatePVC(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePVC", reflect.TypeOf((*MockApplicationCreator)(nil).CreatePVC), arg0, arg1)
}
// CreatePod mocks base method
func (m *MockApplicationCreator) CreatePod(arg0 context.Context, arg1 *types.CreatePodArgs) (*v1.Pod, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreatePod", arg0, arg1)
ret0, _ := ret[0].(*v1.Pod)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreatePod indicates an expected call of CreatePod
func (mr *MockApplicationCreatorMockRecorder) CreatePod(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePod", reflect.TypeOf((*MockApplicationCreator)(nil).CreatePod), arg0, arg1)
}
// WaitForPodReady mocks base method
func (m *MockApplicationCreator) WaitForPodReady(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WaitForPodReady", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// WaitForPodReady indicates an expected call of WaitForPodReady
func (mr *MockApplicationCreatorMockRecorder) WaitForPodReady(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForPodReady", reflect.TypeOf((*MockApplicationCreator)(nil).WaitForPodReady), arg0, arg1, arg2)
}

View file

@ -0,0 +1,81 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: ArgumentValidator)
// Package mocks is a generated GoMock package.
package mocks
import (
context "context"
gomock "github.com/golang/mock/gomock"
v1 "k8s.io/api/storage/v1"
v10 "k8s.io/apimachinery/pkg/apis/meta/v1"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
reflect "reflect"
)
// MockArgumentValidator is a mock of ArgumentValidator interface
type MockArgumentValidator struct {
ctrl *gomock.Controller
recorder *MockArgumentValidatorMockRecorder
}
// MockArgumentValidatorMockRecorder is the mock recorder for MockArgumentValidator
type MockArgumentValidatorMockRecorder struct {
mock *MockArgumentValidator
}
// NewMockArgumentValidator creates a new mock instance
func NewMockArgumentValidator(ctrl *gomock.Controller) *MockArgumentValidator {
mock := &MockArgumentValidator{ctrl: ctrl}
mock.recorder = &MockArgumentValidatorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockArgumentValidator) EXPECT() *MockArgumentValidatorMockRecorder {
return m.recorder
}
// ValidateNamespace mocks base method
func (m *MockArgumentValidator) ValidateNamespace(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateNamespace", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateNamespace indicates an expected call of ValidateNamespace
func (mr *MockArgumentValidatorMockRecorder) ValidateNamespace(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateNamespace", reflect.TypeOf((*MockArgumentValidator)(nil).ValidateNamespace), arg0, arg1)
}
// ValidateStorageClass mocks base method
func (m *MockArgumentValidator) ValidateStorageClass(arg0 context.Context, arg1 string) (*v1.StorageClass, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateStorageClass", arg0, arg1)
ret0, _ := ret[0].(*v1.StorageClass)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateStorageClass indicates an expected call of ValidateStorageClass
func (mr *MockArgumentValidatorMockRecorder) ValidateStorageClass(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateStorageClass", reflect.TypeOf((*MockArgumentValidator)(nil).ValidateStorageClass), arg0, arg1)
}
// ValidateVolumeSnapshotClass mocks base method
func (m *MockArgumentValidator) ValidateVolumeSnapshotClass(arg0 context.Context, arg1 string, arg2 *v10.GroupVersionForDiscovery) (*unstructured.Unstructured, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateVolumeSnapshotClass", arg0, arg1, arg2)
ret0, _ := ret[0].(*unstructured.Unstructured)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateVolumeSnapshotClass indicates an expected call of ValidateVolumeSnapshotClass
func (mr *MockArgumentValidatorMockRecorder) ValidateVolumeSnapshotClass(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateVolumeSnapshotClass", reflect.TypeOf((*MockArgumentValidator)(nil).ValidateVolumeSnapshotClass), arg0, arg1, arg2)
}

View file

@ -0,0 +1,76 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: Cleaner)
// Package mocks is a generated GoMock package.
package mocks
import (
context "context"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockCleaner is a mock of Cleaner interface
type MockCleaner struct {
ctrl *gomock.Controller
recorder *MockCleanerMockRecorder
}
// MockCleanerMockRecorder is the mock recorder for MockCleaner
type MockCleanerMockRecorder struct {
mock *MockCleaner
}
// NewMockCleaner creates a new mock instance
func NewMockCleaner(ctrl *gomock.Controller) *MockCleaner {
mock := &MockCleaner{ctrl: ctrl}
mock.recorder = &MockCleanerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCleaner) EXPECT() *MockCleanerMockRecorder {
return m.recorder
}
// DeletePVC mocks base method
func (m *MockCleaner) DeletePVC(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeletePVC", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// DeletePVC indicates an expected call of DeletePVC
func (mr *MockCleanerMockRecorder) DeletePVC(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePVC", reflect.TypeOf((*MockCleaner)(nil).DeletePVC), arg0, arg1, arg2)
}
// DeletePod mocks base method
func (m *MockCleaner) DeletePod(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeletePod", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// DeletePod indicates an expected call of DeletePod
func (mr *MockCleanerMockRecorder) DeletePod(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePod", reflect.TypeOf((*MockCleaner)(nil).DeletePod), arg0, arg1, arg2)
}
// DeleteSnapshot mocks base method
func (m *MockCleaner) DeleteSnapshot(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteSnapshot", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteSnapshot indicates an expected call of DeleteSnapshot
func (mr *MockCleanerMockRecorder) DeleteSnapshot(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSnapshot", reflect.TypeOf((*MockCleaner)(nil).DeleteSnapshot), arg0, arg1, arg2)
}

View file

@ -0,0 +1,48 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: DataValidator)
// Package mocks is a generated GoMock package.
package mocks
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockDataValidator is a mock of DataValidator interface
type MockDataValidator struct {
ctrl *gomock.Controller
recorder *MockDataValidatorMockRecorder
}
// MockDataValidatorMockRecorder is the mock recorder for MockDataValidator
type MockDataValidatorMockRecorder struct {
mock *MockDataValidator
}
// NewMockDataValidator creates a new mock instance
func NewMockDataValidator(ctrl *gomock.Controller) *MockDataValidator {
mock := &MockDataValidator{ctrl: ctrl}
mock.recorder = &MockDataValidatorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDataValidator) EXPECT() *MockDataValidatorMockRecorder {
return m.recorder
}
// FetchPodData mocks base method
func (m *MockDataValidator) FetchPodData(arg0, arg1 string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FetchPodData", arg0, arg1)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FetchPodData indicates an expected call of FetchPodData
func (mr *MockDataValidatorMockRecorder) FetchPodData(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchPodData", reflect.TypeOf((*MockDataValidator)(nil).FetchPodData), arg0, arg1)
}

View file

@ -0,0 +1,81 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: SnapshotCreator)
// Package mocks is a generated GoMock package.
package mocks
import (
context "context"
gomock "github.com/golang/mock/gomock"
snapshot "github.com/kanisterio/kanister/pkg/kube/snapshot"
v1alpha1 "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
types "github.com/kastenhq/kubestr/pkg/csi/types"
reflect "reflect"
)
// MockSnapshotCreator is a mock of SnapshotCreator interface
type MockSnapshotCreator struct {
ctrl *gomock.Controller
recorder *MockSnapshotCreatorMockRecorder
}
// MockSnapshotCreatorMockRecorder is the mock recorder for MockSnapshotCreator
type MockSnapshotCreatorMockRecorder struct {
mock *MockSnapshotCreator
}
// NewMockSnapshotCreator creates a new mock instance
func NewMockSnapshotCreator(ctrl *gomock.Controller) *MockSnapshotCreator {
mock := &MockSnapshotCreator{ctrl: ctrl}
mock.recorder = &MockSnapshotCreatorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockSnapshotCreator) EXPECT() *MockSnapshotCreatorMockRecorder {
return m.recorder
}
// CreateFromSourceCheck mocks base method
func (m *MockSnapshotCreator) CreateFromSourceCheck(arg0 context.Context, arg1 snapshot.Snapshotter, arg2 *types.CreateFromSourceCheckArgs) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFromSourceCheck", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// CreateFromSourceCheck indicates an expected call of CreateFromSourceCheck
func (mr *MockSnapshotCreatorMockRecorder) CreateFromSourceCheck(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFromSourceCheck", reflect.TypeOf((*MockSnapshotCreator)(nil).CreateFromSourceCheck), arg0, arg1, arg2)
}
// CreateSnapshot mocks base method
func (m *MockSnapshotCreator) CreateSnapshot(arg0 context.Context, arg1 snapshot.Snapshotter, arg2 *types.CreateSnapshotArgs) (*v1alpha1.VolumeSnapshot, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSnapshot", arg0, arg1, arg2)
ret0, _ := ret[0].(*v1alpha1.VolumeSnapshot)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateSnapshot indicates an expected call of CreateSnapshot
func (mr *MockSnapshotCreatorMockRecorder) CreateSnapshot(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSnapshot", reflect.TypeOf((*MockSnapshotCreator)(nil).CreateSnapshot), arg0, arg1, arg2)
}
// NewSnapshotter mocks base method
func (m *MockSnapshotCreator) NewSnapshotter() (snapshot.Snapshotter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewSnapshotter")
ret0, _ := ret[0].(snapshot.Snapshotter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// NewSnapshotter indicates an expected call of NewSnapshotter
func (mr *MockSnapshotCreatorMockRecorder) NewSnapshotter() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSnapshotter", reflect.TypeOf((*MockSnapshotCreator)(nil).NewSnapshotter))
}

View file

@ -0,0 +1,124 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: SnapshotRestoreStepper)
// Package mocks is a generated GoMock package.
package mocks
import (
context "context"
gomock "github.com/golang/mock/gomock"
v1alpha1 "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
types "github.com/kastenhq/kubestr/pkg/csi/types"
v1 "k8s.io/api/core/v1"
reflect "reflect"
)
// MockSnapshotRestoreStepper is a mock of SnapshotRestoreStepper interface
type MockSnapshotRestoreStepper struct {
ctrl *gomock.Controller
recorder *MockSnapshotRestoreStepperMockRecorder
}
// MockSnapshotRestoreStepperMockRecorder is the mock recorder for MockSnapshotRestoreStepper
type MockSnapshotRestoreStepperMockRecorder struct {
mock *MockSnapshotRestoreStepper
}
// NewMockSnapshotRestoreStepper creates a new mock instance
func NewMockSnapshotRestoreStepper(ctrl *gomock.Controller) *MockSnapshotRestoreStepper {
mock := &MockSnapshotRestoreStepper{ctrl: ctrl}
mock.recorder = &MockSnapshotRestoreStepperMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockSnapshotRestoreStepper) EXPECT() *MockSnapshotRestoreStepperMockRecorder {
return m.recorder
}
// Cleanup mocks base method
func (m *MockSnapshotRestoreStepper) Cleanup(arg0 context.Context, arg1 *types.CSISnapshotRestoreResults) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Cleanup", arg0, arg1)
}
// Cleanup indicates an expected call of Cleanup
func (mr *MockSnapshotRestoreStepperMockRecorder) Cleanup(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cleanup", reflect.TypeOf((*MockSnapshotRestoreStepper)(nil).Cleanup), arg0, arg1)
}
// CreateApplication mocks base method
func (m *MockSnapshotRestoreStepper) CreateApplication(arg0 context.Context, arg1 *types.CSISnapshotRestoreArgs, arg2 string) (*v1.Pod, *v1.PersistentVolumeClaim, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateApplication", arg0, arg1, arg2)
ret0, _ := ret[0].(*v1.Pod)
ret1, _ := ret[1].(*v1.PersistentVolumeClaim)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// CreateApplication indicates an expected call of CreateApplication
func (mr *MockSnapshotRestoreStepperMockRecorder) CreateApplication(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateApplication", reflect.TypeOf((*MockSnapshotRestoreStepper)(nil).CreateApplication), arg0, arg1, arg2)
}
// RestoreApplication mocks base method
func (m *MockSnapshotRestoreStepper) RestoreApplication(arg0 context.Context, arg1 *types.CSISnapshotRestoreArgs, arg2 *v1alpha1.VolumeSnapshot) (*v1.Pod, *v1.PersistentVolumeClaim, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RestoreApplication", arg0, arg1, arg2)
ret0, _ := ret[0].(*v1.Pod)
ret1, _ := ret[1].(*v1.PersistentVolumeClaim)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// RestoreApplication indicates an expected call of RestoreApplication
func (mr *MockSnapshotRestoreStepperMockRecorder) RestoreApplication(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestoreApplication", reflect.TypeOf((*MockSnapshotRestoreStepper)(nil).RestoreApplication), arg0, arg1, arg2)
}
// SnapshotApplication mocks base method
func (m *MockSnapshotRestoreStepper) SnapshotApplication(arg0 context.Context, arg1 *types.CSISnapshotRestoreArgs, arg2 *v1.PersistentVolumeClaim, arg3 string) (*v1alpha1.VolumeSnapshot, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SnapshotApplication", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*v1alpha1.VolumeSnapshot)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SnapshotApplication indicates an expected call of SnapshotApplication
func (mr *MockSnapshotRestoreStepperMockRecorder) SnapshotApplication(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SnapshotApplication", reflect.TypeOf((*MockSnapshotRestoreStepper)(nil).SnapshotApplication), arg0, arg1, arg2, arg3)
}
// ValidateArgs mocks base method
func (m *MockSnapshotRestoreStepper) ValidateArgs(arg0 context.Context, arg1 *types.CSISnapshotRestoreArgs) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateArgs", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateArgs indicates an expected call of ValidateArgs
func (mr *MockSnapshotRestoreStepperMockRecorder) ValidateArgs(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateArgs", reflect.TypeOf((*MockSnapshotRestoreStepper)(nil).ValidateArgs), arg0, arg1)
}
// ValidateData mocks base method
func (m *MockSnapshotRestoreStepper) ValidateData(arg0 context.Context, arg1 *v1.Pod, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateData", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateData indicates an expected call of ValidateData
func (mr *MockSnapshotRestoreStepperMockRecorder) ValidateData(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateData", reflect.TypeOf((*MockSnapshotRestoreStepper)(nil).ValidateData), arg0, arg1, arg2)
}

627
pkg/csi/snapshot_restore.go Normal file
View file

@ -0,0 +1,627 @@
package csi
import (
"context"
"fmt"
"time"
kankube "github.com/kanisterio/kanister/pkg/kube"
kansnapshot "github.com/kanisterio/kanister/pkg/kube/snapshot"
"github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
"github.com/kastenhq/kubestr/pkg/csi/types"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
sv1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
)
const (
// SnapGroupName describes the snapshot group name
SnapGroupName = "snapshot.storage.k8s.io"
// VolumeSnapshotClassResourcePlural describes volume snapshot classses
VolumeSnapshotClassResourcePlural = "volumesnapshotclasses"
// VolSnapClassAlphaDriverKey describes alpha driver key
VolSnapClassAlphaDriverKey = "snapshotter"
// VolSnapClassBetaDriverKey describes beta driver key
VolSnapClassBetaDriverKey = "driver"
alphaVersion = "snapshot.storage.k8s.io/v1alpha1"
betaVersion = "snapshot.storage.k8s.io/v1beta1"
originalPVCGenerateName = "kubestr-csi-original-pvc"
originalPodGenerateName = "kubestr-csi-original-pod"
clonedPVCGenerateName = "kubestr-csi-cloned-pvc"
clonedPodGenerateName = "kubestr-csi-cloned-pod"
createdByLabel = "created-by-kubestr-csi"
DefaultPodImage = "ghcr.io/kastenhq/kubestr:latest"
clonePrefix = "kubestr-clone-"
snapshotPrefix = "kubestr-snapshot-"
)
type SnapshotRestoreRunner struct {
KubeCli kubernetes.Interface
DynCli dynamic.Interface
srSteps SnapshotRestoreStepper
}
func (r *SnapshotRestoreRunner) RunSnapshotRestore(ctx context.Context, args *types.CSISnapshotRestoreArgs) (*types.CSISnapshotRestoreResults, error) {
r.srSteps = &snapshotRestoreSteps{
validateOps: &validateOperations{
kubeCli: r.KubeCli,
dynCli: r.DynCli,
},
versionFetchOps: &apiVersionFetch{
kubeCli: r.KubeCli,
},
createAppOps: &applicationCreate{
kubeCli: r.KubeCli,
},
dataValidatorOps: &validateData{
kubeCli: r.KubeCli,
},
snapshotCreateOps: &snapshotCreate{
kubeCli: r.KubeCli,
dynCli: r.DynCli,
},
cleanerOps: &cleanse{
kubeCli: r.KubeCli,
dynCli: r.DynCli,
},
}
return r.RunSnapshotRestoreHelper(ctx, args)
}
func (r *SnapshotRestoreRunner) RunSnapshotRestoreHelper(ctx context.Context, args *types.CSISnapshotRestoreArgs) (*types.CSISnapshotRestoreResults, error) {
results := &types.CSISnapshotRestoreResults{}
var err error
if r.KubeCli == nil || r.DynCli == nil {
return results, fmt.Errorf("cli uninitialized")
}
if err := r.srSteps.ValidateArgs(ctx, args); err != nil {
return results, errors.Wrap(err, "Failed to validate arguments.")
}
data := time.Now().Format("20060102150405")
fmt.Println("Creating application")
results.OriginalPod, results.OriginalPVC, err = r.srSteps.CreateApplication(ctx, args, data)
if err == nil {
if results.OriginalPod != nil && results.OriginalPVC != nil {
fmt.Printf(" -> Created pod (%s) and pvc (%s)\n", results.OriginalPod.Name, results.OriginalPVC.Name)
}
err = r.srSteps.ValidateData(ctx, results.OriginalPod, data)
}
snapName := snapshotPrefix + data
if err == nil {
fmt.Println("Taking a snapshot")
results.Snapshot, err = r.srSteps.SnapshotApplication(ctx, args, results.OriginalPVC, snapName)
}
if err == nil {
if results.Snapshot != nil {
fmt.Printf(" -> Created snapshot (%s)\n", results.Snapshot.Name)
}
fmt.Println("Restoring application")
results.ClonedPod, results.ClonedPVC, err = r.srSteps.RestoreApplication(ctx, args, results.Snapshot)
}
if err == nil {
if results.ClonedPod != nil && results.ClonedPVC != nil {
fmt.Printf(" -> Restored pod (%s) and pvc (%s)\n", results.ClonedPod.Name, results.ClonedPVC.Name)
}
err = r.srSteps.ValidateData(ctx, results.ClonedPod, data)
}
if args.Cleanup {
fmt.Println("Cleaning up resources")
r.srSteps.Cleanup(ctx, results)
}
return results, err
}
//go:generate mockgen -destination=mocks/mock_snapshot_restore_stepper.go -package=mocks . SnapshotRestoreStepper
type SnapshotRestoreStepper interface {
ValidateArgs(ctx context.Context, args *types.CSISnapshotRestoreArgs) error
CreateApplication(ctx context.Context, args *types.CSISnapshotRestoreArgs, data string) (*v1.Pod, *v1.PersistentVolumeClaim, error)
ValidateData(ctx context.Context, pod *v1.Pod, data string) error
SnapshotApplication(ctx context.Context, args *types.CSISnapshotRestoreArgs, pvc *v1.PersistentVolumeClaim, snapshotName string) (*v1alpha1.VolumeSnapshot, error)
RestoreApplication(ctx context.Context, args *types.CSISnapshotRestoreArgs, snapshot *v1alpha1.VolumeSnapshot) (*v1.Pod, *v1.PersistentVolumeClaim, error)
Cleanup(ctx context.Context, results *types.CSISnapshotRestoreResults)
}
type snapshotRestoreSteps struct {
validateOps ArgumentValidator
versionFetchOps ApiVersionFetcher
createAppOps ApplicationCreator
dataValidatorOps DataValidator
snapshotCreateOps SnapshotCreator
cleanerOps Cleaner
}
func (s *snapshotRestoreSteps) ValidateArgs(ctx context.Context, args *types.CSISnapshotRestoreArgs) error {
if err := args.Validate(); err != nil {
return errors.Wrap(err, "Failed to validate input arguments")
}
if err := s.validateOps.ValidateNamespace(ctx, args.Namespace); err != nil {
return errors.Wrap(err, "Failed to validate Namespace")
}
sc, err := s.validateOps.ValidateStorageClass(ctx, args.StorageClass)
if err != nil {
return errors.Wrap(err, "Failed to validate Storageclass")
}
groupVersion, err := s.versionFetchOps.GetCSISnapshotGroupVersion()
if err != nil {
return errors.Wrap(err, "Failed to fetch groupVersion")
}
uVSC, err := s.validateOps.ValidateVolumeSnapshotClass(ctx, args.VolumeSnapshotClass, groupVersion)
if err != nil {
return errors.Wrap(err, "Failed to validate VolumeSnapshotClass")
}
vscDriver := getDriverNameFromUVSC(*uVSC, groupVersion.GroupVersion)
if sc.Provisioner != vscDriver {
return fmt.Errorf("StorageClass provisioner (%s) and VolumeSnapshotClass driver (%s) are different.", sc.Provisioner, vscDriver)
}
return nil
}
func (s *snapshotRestoreSteps) CreateApplication(ctx context.Context, args *types.CSISnapshotRestoreArgs, genString string) (*v1.Pod, *v1.PersistentVolumeClaim, error) {
pvcArgs := &types.CreatePVCArgs{
GenerateName: originalPVCGenerateName,
StorageClass: args.StorageClass,
Namespace: args.Namespace,
}
pvc, err := s.createAppOps.CreatePVC(ctx, pvcArgs)
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to create PVC")
}
podArgs := &types.CreatePodArgs{
GenerateName: originalPodGenerateName,
PVCName: pvc.Name,
Namespace: args.Namespace,
Cmd: fmt.Sprintf("echo '%s' >> /data/out.txt; sync; tail -f /dev/null", genString),
RunAsUser: args.RunAsUser,
ContainerImage: args.ContainerImage,
}
pod, err := s.createAppOps.CreatePod(ctx, podArgs)
if err != nil {
return nil, pvc, errors.Wrap(err, "Failed to create POD")
}
if err = s.createAppOps.WaitForPodReady(ctx, args.Namespace, pod.Name); err != nil {
return pod, pvc, errors.Wrap(err, "Pod failed to become ready")
}
return pod, pvc, nil
}
func (s *snapshotRestoreSteps) ValidateData(ctx context.Context, pod *v1.Pod, data string) error {
podData, err := s.dataValidatorOps.FetchPodData(pod.Name, pod.Namespace)
if err != nil {
return errors.Wrap(err, "Failed to fetch data from pod")
}
if podData != data {
return fmt.Errorf("string didn't match (%s , %s)", podData, data)
}
return nil
}
func (s *snapshotRestoreSteps) SnapshotApplication(ctx context.Context, args *types.CSISnapshotRestoreArgs, pvc *v1.PersistentVolumeClaim, snapshotName string) (*v1alpha1.VolumeSnapshot, error) {
snapshotter, err := s.snapshotCreateOps.NewSnapshotter()
if err != nil {
return nil, errors.Wrap(err, "Failed to load snapshotter")
}
createSnapshotArgs := &types.CreateSnapshotArgs{
Namespace: args.Namespace,
PVCName: pvc.Name,
VolumeSnapshotClass: args.VolumeSnapshotClass,
SnapshotName: snapshotName,
}
snapshot, err := s.snapshotCreateOps.CreateSnapshot(ctx, snapshotter, createSnapshotArgs)
if err != nil {
return nil, errors.Wrap(err, "Failed to create Snapshot")
}
if !args.SkipCFSCheck {
cfsArgs := &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: args.VolumeSnapshotClass,
SnapshotName: snapshot.Name,
Namespace: args.Namespace,
}
if err = s.snapshotCreateOps.CreateFromSourceCheck(ctx, snapshotter, cfsArgs); err != nil {
return snapshot, errors.Wrap(err, "Failed to create duplicate snapshot from source. To skip check use '--skipcfs=true' option.")
}
}
return snapshot, nil
}
func (s *snapshotRestoreSteps) RestoreApplication(ctx context.Context, args *types.CSISnapshotRestoreArgs, snapshot *v1alpha1.VolumeSnapshot) (*v1.Pod, *v1.PersistentVolumeClaim, error) {
snapshotAPIGroup := "snapshot.storage.k8s.io"
snapshotKind := "VolumeSnapshot"
dataSource := &v1.TypedLocalObjectReference{
APIGroup: &snapshotAPIGroup,
Kind: snapshotKind,
Name: snapshot.Name,
}
pvcArgs := &types.CreatePVCArgs{
GenerateName: clonedPVCGenerateName,
StorageClass: args.StorageClass,
Namespace: args.Namespace,
DataSource: dataSource,
RestoreSize: snapshot.Status.RestoreSize,
}
pvc, err := s.createAppOps.CreatePVC(ctx, pvcArgs)
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to restore PVC")
}
podArgs := &types.CreatePodArgs{
GenerateName: clonedPodGenerateName,
PVCName: pvc.Name,
Namespace: args.Namespace,
Cmd: "tail -f /dev/null",
RunAsUser: args.RunAsUser,
ContainerImage: args.ContainerImage,
}
pod, err := s.createAppOps.CreatePod(ctx, podArgs)
if err != nil {
return nil, pvc, errors.Wrap(err, "Failed to create restored Pod")
}
if err = s.createAppOps.WaitForPodReady(ctx, args.Namespace, pod.Name); err != nil {
return pod, pvc, errors.Wrap(err, "Pod failed to become ready")
}
return pod, pvc, nil
}
func (s *snapshotRestoreSteps) Cleanup(ctx context.Context, results *types.CSISnapshotRestoreResults) {
if results == nil {
return
}
if results.OriginalPVC != nil {
_ = s.cleanerOps.DeletePVC(ctx, results.OriginalPVC.Name, results.OriginalPVC.Namespace)
}
if results.OriginalPod != nil {
_ = s.cleanerOps.DeletePod(ctx, results.OriginalPod.Name, results.OriginalPod.Namespace)
}
if results.ClonedPVC != nil {
_ = s.cleanerOps.DeletePVC(ctx, results.ClonedPVC.Name, results.ClonedPVC.Namespace)
}
if results.ClonedPod != nil {
_ = s.cleanerOps.DeletePod(ctx, results.ClonedPod.Name, results.ClonedPod.Namespace)
}
if results.Snapshot != nil {
_ = s.cleanerOps.DeleteSnapshot(ctx, results.Snapshot.Name, results.Snapshot.Namespace)
}
}
//go:generate mockgen -destination=mocks/mock_argument_validator.go -package=mocks . ArgumentValidator
type ArgumentValidator interface {
ValidateNamespace(ctx context.Context, namespace string) error
ValidateStorageClass(ctx context.Context, storageClass string) (*sv1.StorageClass, error)
ValidateVolumeSnapshotClass(ctx context.Context, volumeSnapshotClass string, groupVersion *metav1.GroupVersionForDiscovery) (*unstructured.Unstructured, error)
}
type validateOperations struct {
kubeCli kubernetes.Interface
dynCli dynamic.Interface
}
func (o *validateOperations) ValidateNamespace(ctx context.Context, namespace string) error {
if o.kubeCli == nil {
return fmt.Errorf("kubeCli not initialized")
}
_, err := o.kubeCli.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
return err
}
func (o *validateOperations) ValidateStorageClass(ctx context.Context, storageClass string) (*sv1.StorageClass, error) {
if o.kubeCli == nil {
return nil, fmt.Errorf("kubeCli not initialized")
}
return o.kubeCli.StorageV1().StorageClasses().Get(ctx, storageClass, metav1.GetOptions{})
}
func (o *validateOperations) ValidateVolumeSnapshotClass(ctx context.Context, volumeSnapshotClass string, groupVersion *metav1.GroupVersionForDiscovery) (*unstructured.Unstructured, error) {
if o.dynCli == nil {
return nil, fmt.Errorf("dynCli not initialized")
}
VolSnapClassGVR := schema.GroupVersionResource{Group: SnapGroupName, Version: groupVersion.Version, Resource: VolumeSnapshotClassResourcePlural}
return o.dynCli.Resource(VolSnapClassGVR).Get(ctx, volumeSnapshotClass, metav1.GetOptions{})
}
//go:generate mockgen -destination=mocks/mock_application_creator.go -package=mocks . ApplicationCreator
type ApplicationCreator interface {
CreatePVC(ctx context.Context, args *types.CreatePVCArgs) (*v1.PersistentVolumeClaim, error)
CreatePod(ctx context.Context, args *types.CreatePodArgs) (*v1.Pod, error)
WaitForPodReady(ctx context.Context, namespace string, podName string) error
}
type applicationCreate struct {
kubeCli kubernetes.Interface
}
func (c *applicationCreate) CreatePVC(ctx context.Context, args *types.CreatePVCArgs) (*v1.PersistentVolumeClaim, error) {
if c.kubeCli == nil {
return nil, fmt.Errorf("kubeCli not initialized")
}
if err := args.Validate(); err != nil {
return nil, err
}
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
GenerateName: args.GenerateName,
Namespace: args.Namespace,
Labels: map[string]string{
createdByLabel: "yes",
},
},
Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
StorageClassName: &args.StorageClass,
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("1Gi"),
},
},
},
}
if args.DataSource != nil {
pvc.Spec.DataSource = args.DataSource
}
if args.RestoreSize != nil && !args.RestoreSize.IsZero() {
pvc.Spec.Resources.Requests[v1.ResourceStorage] = *args.RestoreSize
}
pvcRes, err := c.kubeCli.CoreV1().PersistentVolumeClaims(args.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
if err != nil {
return pvc, err
}
return pvcRes, nil
}
func (c *applicationCreate) CreatePod(ctx context.Context, args *types.CreatePodArgs) (*v1.Pod, error) {
if c.kubeCli == nil {
return nil, fmt.Errorf("kubeCli not initialized")
}
if err := args.Validate(); err != nil {
return nil, err
}
if args.ContainerImage == "" {
args.ContainerImage = DefaultPodImage
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: args.GenerateName,
Namespace: args.Namespace,
Labels: map[string]string{
createdByLabel: "yes",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: args.GenerateName,
Image: args.ContainerImage,
Command: []string{"/bin/sh"},
Args: []string{"-c", args.Cmd},
VolumeMounts: []v1.VolumeMount{{
Name: "persistent-storage",
MountPath: "/data",
}},
}},
Volumes: []v1.Volume{{
Name: "persistent-storage",
VolumeSource: v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: args.PVCName,
},
}},
},
},
}
if args.RunAsUser > 0 {
pod.Spec.SecurityContext = &v1.PodSecurityContext{
RunAsUser: &args.RunAsUser,
FSGroup: &args.RunAsUser,
}
}
podRes, err := c.kubeCli.CoreV1().Pods(args.Namespace).Create(ctx, pod, metav1.CreateOptions{})
if err != nil {
return pod, err
}
return podRes, nil
}
func (c *applicationCreate) WaitForPodReady(ctx context.Context, namespace string, podName string) error {
if c.kubeCli == nil {
return fmt.Errorf("kubeCli not initialized")
}
err := kankube.WaitForPodReady(ctx, c.kubeCli, namespace, podName)
return err
}
//go:generate mockgen -destination=mocks/mock_snapshot_creator.go -package=mocks . SnapshotCreator
type SnapshotCreator interface {
NewSnapshotter() (kansnapshot.Snapshotter, error)
CreateSnapshot(ctx context.Context, snapshotter kansnapshot.Snapshotter, args *types.CreateSnapshotArgs) (*v1alpha1.VolumeSnapshot, error)
CreateFromSourceCheck(ctx context.Context, snapshotter kansnapshot.Snapshotter, args *types.CreateFromSourceCheckArgs) error
}
type snapshotCreate struct {
kubeCli kubernetes.Interface
dynCli dynamic.Interface
}
func (c *snapshotCreate) NewSnapshotter() (kansnapshot.Snapshotter, error) {
if c.kubeCli == nil {
return nil, fmt.Errorf("kubeCli not initialized")
}
if c.dynCli == nil {
return nil, fmt.Errorf("dynCli not initialized")
}
return kansnapshot.NewSnapshotter(c.kubeCli, c.dynCli)
}
func (c *snapshotCreate) CreateSnapshot(ctx context.Context, snapshotter kansnapshot.Snapshotter, args *types.CreateSnapshotArgs) (*v1alpha1.VolumeSnapshot, error) {
if snapshotter == nil || args == nil {
return nil, fmt.Errorf("snapshotter or args are empty")
}
if err := args.Validate(); err != nil {
return nil, err
}
err := snapshotter.Create(ctx, args.SnapshotName, args.Namespace, args.PVCName, &args.VolumeSnapshotClass, true)
if err != nil {
return nil, errors.Wrapf(err, "CSI Driver failed to create snapshot for PVC (%s) in Namspace (%s)", args.PVCName, args.Namespace)
}
snap, err := snapshotter.Get(ctx, args.SnapshotName, args.Namespace)
if err != nil {
return nil, errors.Wrapf(err, "Failed to get CSI snapshot (%s) in Namespace (%s)", args.SnapshotName, args.Namespace)
}
return snap, nil
}
func (c *snapshotCreate) CreateFromSourceCheck(ctx context.Context, snapshotter kansnapshot.Snapshotter, args *types.CreateFromSourceCheckArgs) error {
if c.dynCli == nil {
return fmt.Errorf("dynCli not initialized")
}
if snapshotter == nil || args == nil {
return fmt.Errorf("snapshotter or args are nil")
}
if err := args.Validate(); err != nil {
return err
}
targetSnapClassName := clonePrefix + args.VolumeSnapshotClass
err := snapshotter.CloneVolumeSnapshotClass(args.VolumeSnapshotClass, targetSnapClassName, kansnapshot.DeletionPolicyRetain, nil)
if err != nil {
return errors.Wrapf(err, "Failed to create a VolumeSnapshotClass to use to restore the snapshot")
}
defer func() {
_ = c.dynCli.Resource(v1alpha1.VolSnapClassGVR).Delete(ctx, targetSnapClassName, metav1.DeleteOptions{})
}()
snapSrc, err := snapshotter.GetSource(ctx, args.SnapshotName, args.Namespace)
if err != nil {
return errors.Wrapf(err, "Failed to get source snapshot source (%s)", args.SnapshotName)
}
snapshotCFSCloneName := clonePrefix + args.SnapshotName
// test the CreateFromSource API
defer func() {
_, _ = snapshotter.Delete(context.Background(), snapshotCFSCloneName, args.Namespace)
}()
src := &kansnapshot.Source{
Handle: snapSrc.Handle,
Driver: snapSrc.Driver,
VolumeSnapshotClassName: targetSnapClassName,
}
err = snapshotter.CreateFromSource(ctx, src, snapshotCFSCloneName, args.Namespace, true)
if err != nil {
return errors.Wrapf(err, "Failed to clone snapshot from source (%s)", snapshotCFSCloneName)
}
return nil
}
//go:generate mockgen -destination=mocks/mock_cleaner.go -package=mocks . Cleaner
type Cleaner interface {
DeletePVC(ctx context.Context, pvcName string, namespace string) error
DeletePod(ctx context.Context, podName string, namespace string) error
DeleteSnapshot(ctx context.Context, snapshotName string, namespace string) error
}
type cleanse struct {
kubeCli kubernetes.Interface
dynCli dynamic.Interface
}
func (c *cleanse) DeletePVC(ctx context.Context, pvcName string, namespace string) error {
if c.kubeCli == nil {
return fmt.Errorf("kubeCli not initialized")
}
return c.kubeCli.CoreV1().PersistentVolumeClaims(namespace).Delete(ctx, pvcName, metav1.DeleteOptions{})
}
func (c *cleanse) DeletePod(ctx context.Context, podName string, namespace string) error {
if c.kubeCli == nil {
return fmt.Errorf("kubeCli not initialized")
}
return c.kubeCli.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
}
func (c *cleanse) DeleteSnapshot(ctx context.Context, snapshotName string, namespace string) error {
if c.dynCli == nil {
return fmt.Errorf("dynCli not initialized")
}
return c.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Delete(ctx, snapshotName, metav1.DeleteOptions{})
}
//go:generate mockgen -destination=mocks/mock_api_version_fetcher.go -package=mocks . ApiVersionFetcher
type ApiVersionFetcher interface {
GetCSISnapshotGroupVersion() (*metav1.GroupVersionForDiscovery, error)
}
type apiVersionFetch struct {
kubeCli kubernetes.Interface
}
func (p *apiVersionFetch) GetCSISnapshotGroupVersion() (*metav1.GroupVersionForDiscovery, error) {
if p.kubeCli == nil {
return nil, fmt.Errorf("kubeCli not initialized")
}
groups, _, err := p.kubeCli.Discovery().ServerGroupsAndResources()
if err != nil {
return nil, err
}
for _, group := range groups {
if group.Name == SnapGroupName {
return &group.PreferredVersion, nil
}
}
return nil, fmt.Errorf("Snapshot API group not found")
}
//go:generate mockgen -destination=mocks/mock_data_validator.go -package=mocks . DataValidator
type DataValidator interface {
FetchPodData(podName string, podNamespace string) (string, error)
}
type validateData struct {
kubeCli kubernetes.Interface
}
func (p *validateData) FetchPodData(podName string, podNamespace string) (string, error) {
if p.kubeCli == nil {
return "", fmt.Errorf("kubeCli not initialized")
}
stdout, _, err := kankube.Exec(p.kubeCli, podNamespace, podName, "", []string{"sh", "-c", "cat /data/out.txt"}, nil)
return stdout, err
}
func getDriverNameFromUVSC(vsc unstructured.Unstructured, version string) string {
var driverName interface{}
var ok bool
switch version {
case alphaVersion:
driverName, ok = vsc.Object[VolSnapClassAlphaDriverKey]
if !ok {
return ""
}
case betaVersion:
driverName, ok = vsc.Object[VolSnapClassBetaDriverKey]
if !ok {
return ""
}
}
driver, ok := driverName.(string)
if !ok {
return ""
}
return driver
}

View file

@ -0,0 +1,943 @@
package csi
import (
"context"
"errors"
"fmt"
kansnapshot "github.com/kanisterio/kanister/pkg/kube/snapshot"
"github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
"github.com/kastenhq/kubestr/pkg/csi/types"
. "gopkg.in/check.v1"
v1 "k8s.io/api/core/v1"
sv1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
discoveryfake "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/dynamic"
fakedynamic "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
)
func (s *CSITestSuite) TestGetDriverNameFromUVSC(c *C) {
for _, tc := range []struct {
vsc unstructured.Unstructured
version string
expOut string
}{
{
vsc: unstructured.Unstructured{
Object: map[string]interface{}{
VolSnapClassAlphaDriverKey: "p2",
},
},
version: alphaVersion,
expOut: "p2",
},
{
vsc: unstructured.Unstructured{
Object: map[string]interface{}{},
},
version: alphaVersion,
expOut: "",
},
{
vsc: unstructured.Unstructured{
Object: map[string]interface{}{
VolSnapClassBetaDriverKey: "p2",
},
},
version: betaVersion,
expOut: "p2",
},
{
vsc: unstructured.Unstructured{
Object: map[string]interface{}{},
},
version: betaVersion,
expOut: "",
},
{
vsc: unstructured.Unstructured{
Object: map[string]interface{}{
VolSnapClassBetaDriverKey: map[string]string{},
},
},
version: betaVersion,
expOut: "",
},
} {
driverName := getDriverNameFromUVSC(tc.vsc, tc.version)
c.Assert(driverName, Equals, tc.expOut)
}
}
func (s *CSITestSuite) TestGetCSISnapshotGroupVersion(c *C) {
for _, tc := range []struct {
cli kubernetes.Interface
resources []*metav1.APIResourceList
errChecker Checker
gvChecker Checker
}{
{
cli: fake.NewSimpleClientset(),
resources: []*metav1.APIResourceList{
{
GroupVersion: "/////",
},
},
errChecker: NotNil,
gvChecker: IsNil,
},
{
cli: fake.NewSimpleClientset(),
resources: []*metav1.APIResourceList{
{
GroupVersion: "snapshot.storage.k8s.io/v1alpha1",
},
},
errChecker: IsNil,
gvChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
resources: []*metav1.APIResourceList{
{
GroupVersion: "notrbac.authorization.k8s.io/v1",
},
},
errChecker: NotNil,
gvChecker: IsNil,
},
{
cli: nil,
resources: nil,
errChecker: NotNil,
gvChecker: IsNil,
},
} {
cli := tc.cli
if cli != nil {
cli.Discovery().(*discoveryfake.FakeDiscovery).Resources = tc.resources
}
p := &apiVersionFetch{kubeCli: cli}
gv, err := p.GetCSISnapshotGroupVersion()
c.Check(err, tc.errChecker)
c.Check(gv, tc.gvChecker)
}
}
func (s *CSITestSuite) TestValidateNamespace(c *C) {
ctx := context.Background()
ops := &validateOperations{
kubeCli: fake.NewSimpleClientset(),
}
err := ops.ValidateNamespace(ctx, "ns")
c.Check(err, NotNil)
ops = &validateOperations{
kubeCli: fake.NewSimpleClientset(&v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "ns",
},
}),
}
err = ops.ValidateNamespace(ctx, "ns")
c.Check(err, IsNil)
ops = &validateOperations{
kubeCli: nil,
}
err = ops.ValidateNamespace(ctx, "ns")
c.Check(err, NotNil)
}
func (s *CSITestSuite) TestValidateStorageClass(c *C) {
ctx := context.Background()
ops := &validateOperations{
kubeCli: fake.NewSimpleClientset(),
}
sc, err := ops.ValidateStorageClass(ctx, "sc")
c.Check(err, NotNil)
c.Check(sc, IsNil)
ops = &validateOperations{
kubeCli: fake.NewSimpleClientset(&sv1.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "sc",
},
}),
}
sc, err = ops.ValidateStorageClass(ctx, "sc")
c.Check(err, IsNil)
c.Check(sc, NotNil)
ops = &validateOperations{
kubeCli: nil,
}
sc, err = ops.ValidateStorageClass(ctx, "sc")
c.Check(err, NotNil)
c.Check(sc, IsNil)
}
func (s *CSITestSuite) TestValidateVolumeSnapshotClass(c *C) {
ctx := context.Background()
ops := &validateOperations{
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
}
uVSC, err := ops.ValidateVolumeSnapshotClass(ctx, "vsc", &metav1.GroupVersionForDiscovery{GroupVersion: alphaVersion})
c.Check(err, NotNil)
c.Check(uVSC, IsNil)
ops = &validateOperations{
dynCli: fakedynamic.NewSimpleDynamicClient(
runtime.NewScheme(),
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version),
"kind": "VolumeSnapshotClass",
"metadata": map[string]interface{}{
"name": "vsc",
},
"snapshotter": "somesnapshotter",
"deletionPolicy": "Delete",
},
},
),
}
uVSC, err = ops.ValidateVolumeSnapshotClass(ctx, "vsc", &metav1.GroupVersionForDiscovery{Version: v1alpha1.Version})
c.Check(err, IsNil)
c.Check(uVSC, NotNil)
ops = &validateOperations{
dynCli: nil,
}
uVSC, err = ops.ValidateVolumeSnapshotClass(ctx, "vsc", &metav1.GroupVersionForDiscovery{Version: v1alpha1.Version})
c.Check(err, NotNil)
c.Check(uVSC, IsNil)
}
func (s *CSITestSuite) TestCreatePVC(c *C) {
ctx := context.Background()
resourceQuantity := resource.MustParse("1Gi")
for _, tc := range []struct {
cli kubernetes.Interface
args *types.CreatePVCArgs
failCreates bool
errChecker Checker
pvcChecker Checker
}{
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePVCArgs{
GenerateName: "genName",
StorageClass: "sc",
Namespace: "ns",
DataSource: &v1.TypedLocalObjectReference{
Name: "ds",
},
RestoreSize: &resourceQuantity,
},
errChecker: IsNil,
pvcChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePVCArgs{
GenerateName: "genName",
StorageClass: "sc",
Namespace: "ns",
DataSource: &v1.TypedLocalObjectReference{
Name: "ds",
},
},
errChecker: IsNil,
pvcChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePVCArgs{
GenerateName: "genName",
StorageClass: "sc",
Namespace: "ns",
},
errChecker: IsNil,
pvcChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePVCArgs{
GenerateName: "genName",
StorageClass: "sc",
Namespace: "ns",
},
failCreates: true,
errChecker: NotNil,
pvcChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePVCArgs{
GenerateName: "",
StorageClass: "sc",
Namespace: "ns",
},
errChecker: NotNil,
pvcChecker: IsNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePVCArgs{
GenerateName: "something",
StorageClass: "",
Namespace: "ns",
},
errChecker: NotNil,
pvcChecker: IsNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePVCArgs{
GenerateName: "Something",
StorageClass: "sc",
Namespace: "",
},
errChecker: NotNil,
pvcChecker: IsNil,
},
{
cli: nil,
args: &types.CreatePVCArgs{},
errChecker: NotNil,
pvcChecker: IsNil,
},
} {
creator := &applicationCreate{kubeCli: tc.cli}
if tc.failCreates {
creator.kubeCli.(*fake.Clientset).Fake.PrependReactor("create", "persistentvolumeclaims", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, errors.New("Error creating object")
})
}
pvc, err := creator.CreatePVC(ctx, tc.args)
c.Check(pvc, tc.pvcChecker)
c.Check(err, tc.errChecker)
if pvc != nil && err == nil {
_, ok := pvc.Labels[createdByLabel]
c.Assert(ok, Equals, true)
c.Assert(pvc.GenerateName, Equals, tc.args.GenerateName)
c.Assert(pvc.Namespace, Equals, tc.args.Namespace)
c.Assert(pvc.Spec.AccessModes, DeepEquals, []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce})
c.Assert(*pvc.Spec.StorageClassName, Equals, tc.args.StorageClass)
c.Assert(pvc.Spec.DataSource, DeepEquals, tc.args.DataSource)
if tc.args.RestoreSize != nil {
c.Assert(pvc.Spec.Resources, DeepEquals, v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: *tc.args.RestoreSize,
},
})
} else {
c.Assert(pvc.Spec.Resources, DeepEquals, v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("1Gi"),
},
})
}
}
}
}
func (s *CSITestSuite) TestCreatePod(c *C) {
ctx := context.Background()
for _, tc := range []struct {
cli kubernetes.Interface
args *types.CreatePodArgs
failCreates bool
errChecker Checker
podChecker Checker
}{
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePodArgs{
GenerateName: "name",
PVCName: "pvcname",
Namespace: "ns",
Cmd: "somecommand",
RunAsUser: 1000,
ContainerImage: "containerimage",
},
errChecker: IsNil,
podChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePodArgs{
GenerateName: "name",
PVCName: "pvcname",
Namespace: "ns",
Cmd: "somecommand",
},
errChecker: IsNil,
podChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePodArgs{
GenerateName: "name",
PVCName: "pvcname",
Namespace: "ns",
Cmd: "somecommand",
},
failCreates: true,
errChecker: NotNil,
podChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePodArgs{
GenerateName: "",
PVCName: "pvcname",
Namespace: "ns",
Cmd: "somecommand",
},
errChecker: NotNil,
podChecker: IsNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePodArgs{
GenerateName: "name",
PVCName: "",
Namespace: "ns",
Cmd: "somecommand",
},
errChecker: NotNil,
podChecker: IsNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePodArgs{
GenerateName: "name",
PVCName: "pvcname",
Namespace: "",
Cmd: "somecommand",
},
errChecker: NotNil,
podChecker: IsNil,
},
{
cli: fake.NewSimpleClientset(),
args: &types.CreatePodArgs{
GenerateName: "name",
PVCName: "pvcname",
Namespace: "ns",
Cmd: "",
},
errChecker: NotNil,
podChecker: IsNil,
},
{
cli: nil,
args: &types.CreatePodArgs{},
errChecker: NotNil,
podChecker: IsNil,
},
} {
creator := &applicationCreate{kubeCli: tc.cli}
if tc.failCreates {
creator.kubeCli.(*fake.Clientset).Fake.PrependReactor("create", "pods", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, errors.New("Error creating object")
})
}
pod, err := creator.CreatePod(ctx, tc.args)
c.Check(pod, tc.podChecker)
c.Check(err, tc.errChecker)
if pod != nil && err == nil {
_, ok := pod.Labels[createdByLabel]
c.Assert(ok, Equals, true)
c.Assert(pod.GenerateName, Equals, tc.args.GenerateName)
c.Assert(pod.Namespace, Equals, tc.args.Namespace)
c.Assert(len(pod.Spec.Containers), Equals, 1)
c.Assert(pod.Spec.Containers[0].Name, Equals, tc.args.GenerateName)
c.Assert(pod.Spec.Containers[0].Command, DeepEquals, []string{"/bin/sh"})
c.Assert(pod.Spec.Containers[0].Args, DeepEquals, []string{"-c", tc.args.Cmd})
c.Assert(pod.Spec.Containers[0].VolumeMounts, DeepEquals, []v1.VolumeMount{{
Name: "persistent-storage",
MountPath: "/data",
}})
c.Assert(pod.Spec.Volumes, DeepEquals, []v1.Volume{{
Name: "persistent-storage",
VolumeSource: v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: tc.args.PVCName,
},
}},
})
if tc.args.ContainerImage == "" {
c.Assert(pod.Spec.Containers[0].Image, Equals, DefaultPodImage)
} else {
c.Assert(pod.Spec.Containers[0].Image, Equals, tc.args.ContainerImage)
}
if tc.args.RunAsUser > 0 {
c.Assert(pod.Spec.SecurityContext, DeepEquals, &v1.PodSecurityContext{
RunAsUser: &tc.args.RunAsUser,
FSGroup: &tc.args.RunAsUser,
})
} else {
c.Check(pod.Spec.SecurityContext, IsNil)
}
}
}
}
func (s *CSITestSuite) TestCreateSnapshot(c *C) {
ctx := context.Background()
for _, tc := range []struct {
snapshotter kansnapshot.Snapshotter
args *types.CreateSnapshotArgs
snapChecker Checker
errChecker Checker
}{
{
snapshotter: &fakeSnapshotter{
getSnap: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "createdName",
},
},
},
args: &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
},
snapChecker: NotNil,
errChecker: IsNil,
},
{
snapshotter: &fakeSnapshotter{
getErr: fmt.Errorf("get Error"),
},
args: &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
},
snapChecker: IsNil,
errChecker: NotNil,
},
{
snapshotter: &fakeSnapshotter{
createErr: fmt.Errorf("create Error"),
},
args: &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
},
snapChecker: IsNil,
errChecker: NotNil,
},
{
snapshotter: &fakeSnapshotter{
createErr: fmt.Errorf("create Error"),
},
args: &types.CreateSnapshotArgs{
Namespace: "",
PVCName: "pvc",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
},
snapChecker: IsNil,
errChecker: NotNil,
},
{
snapshotter: &fakeSnapshotter{
createErr: fmt.Errorf("create Error"),
},
args: &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
},
snapChecker: IsNil,
errChecker: NotNil,
},
{
snapshotter: &fakeSnapshotter{
createErr: fmt.Errorf("create Error"),
},
args: &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc",
VolumeSnapshotClass: "",
SnapshotName: "snap1",
},
snapChecker: IsNil,
errChecker: NotNil,
},
{
snapshotter: &fakeSnapshotter{
createErr: fmt.Errorf("create Error"),
},
args: &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc",
VolumeSnapshotClass: "vsc",
SnapshotName: "",
},
snapChecker: IsNil,
errChecker: NotNil,
},
{
snapshotter: &fakeSnapshotter{},
snapChecker: IsNil,
errChecker: NotNil,
},
{
snapChecker: IsNil,
errChecker: NotNil,
},
} {
snapCreator := &snapshotCreate{}
snapshot, err := snapCreator.CreateSnapshot(ctx, tc.snapshotter, tc.args)
c.Check(snapshot, tc.snapChecker)
c.Check(err, tc.errChecker)
}
}
func (s *CSITestSuite) TestCreateFromSourceCheck(c *C) {
ctx := context.Background()
for _, tc := range []struct {
dyncli dynamic.Interface
snapshotter kansnapshot.Snapshotter
args *types.CreateFromSourceCheckArgs
errChecker Checker
}{
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{
gsSrc: &kansnapshot.Source{
Handle: "handle",
Driver: "driver",
},
},
args: &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "snapshot",
Namespace: "ns",
},
errChecker: IsNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{
gsSrc: &kansnapshot.Source{
Handle: "handle",
Driver: "driver",
},
cfsErr: fmt.Errorf("cfs error"),
},
args: &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "snapshot",
Namespace: "ns",
},
errChecker: NotNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{
gsErr: fmt.Errorf("gs error"),
},
args: &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "snapshot",
Namespace: "ns",
},
errChecker: NotNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{
cvsErr: fmt.Errorf("cvs error"),
},
args: &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "snapshot",
Namespace: "ns",
},
errChecker: NotNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{},
args: &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "",
SnapshotName: "snapshot",
Namespace: "ns",
},
errChecker: NotNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{},
args: &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "",
Namespace: "ns",
},
errChecker: NotNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{},
args: &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "snapshot",
Namespace: "",
},
errChecker: NotNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotter: &fakeSnapshotter{},
errChecker: NotNil,
},
{
dyncli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
errChecker: NotNil,
},
{
dyncli: nil,
errChecker: NotNil,
},
} {
snapCreator := &snapshotCreate{
dynCli: tc.dyncli,
}
err := snapCreator.CreateFromSourceCheck(ctx, tc.snapshotter, tc.args)
c.Check(err, tc.errChecker)
}
}
type fakeSnapshotter struct {
name string
createErr error
getSnap *v1alpha1.VolumeSnapshot
getErr error
cvsErr error
gsSrc *kansnapshot.Source
gsErr error
cfsErr error
}
func (f *fakeSnapshotter) GetVolumeSnapshotClass(annotationKey, annotationValue, storageClassName string) (string, error) {
return "", nil
}
func (f *fakeSnapshotter) CloneVolumeSnapshotClass(sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error {
return f.cvsErr
}
func (f *fakeSnapshotter) Create(ctx context.Context, name, namespace, pvcName string, snapshotClass *string, waitForReady bool) error {
return f.createErr
}
func (f *fakeSnapshotter) Get(ctx context.Context, name, namespace string) (*v1alpha1.VolumeSnapshot, error) {
return f.getSnap, f.getErr
}
func (f *fakeSnapshotter) Delete(ctx context.Context, name, namespace string) (*v1alpha1.VolumeSnapshot, error) {
return nil, nil
}
func (f *fakeSnapshotter) DeleteContent(ctx context.Context, name string) error { return nil }
func (f *fakeSnapshotter) Clone(ctx context.Context, name, namespace, cloneName, cloneNamespace string, waitForReady bool) error {
return nil
}
func (f *fakeSnapshotter) GetSource(ctx context.Context, snapshotName, namespace string) (*kansnapshot.Source, error) {
return f.gsSrc, f.gsErr
}
func (f *fakeSnapshotter) CreateFromSource(ctx context.Context, source *kansnapshot.Source, snapshotName, namespace string, waitForReady bool) error {
return f.cfsErr
}
func (f *fakeSnapshotter) CreateContentFromSource(ctx context.Context, source *kansnapshot.Source, contentName, snapshotName, namespace, deletionPolicy string) error {
return nil
}
func (f *fakeSnapshotter) WaitOnReadyToUse(ctx context.Context, snapshotName, namespace string) error {
return nil
}
func (s *CSITestSuite) TestDeletePVC(c *C) {
ctx := context.Background()
for _, tc := range []struct {
cli kubernetes.Interface
pvcName string
namespace string
errChecker Checker
}{
{
cli: fake.NewSimpleClientset(),
pvcName: "pvc",
namespace: "ns",
errChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc",
Namespace: "notns",
},
}),
pvcName: "pvc",
namespace: "ns",
errChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc",
Namespace: "ns",
},
}),
pvcName: "pvc",
namespace: "ns",
errChecker: IsNil,
},
{
cli: nil,
pvcName: "pvc",
namespace: "ns",
errChecker: NotNil,
},
} {
cleaner := &cleanse{
kubeCli: tc.cli,
}
err := cleaner.DeletePVC(ctx, tc.pvcName, tc.namespace)
c.Check(err, tc.errChecker)
}
}
func (s *CSITestSuite) TestDeletePod(c *C) {
ctx := context.Background()
for _, tc := range []struct {
cli kubernetes.Interface
podName string
namespace string
errChecker Checker
}{
{
cli: fake.NewSimpleClientset(),
podName: "pod",
namespace: "ns",
errChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Namespace: "notns",
},
}),
podName: "pod",
namespace: "ns",
errChecker: NotNil,
},
{
cli: fake.NewSimpleClientset(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Namespace: "ns",
},
}),
podName: "pod",
namespace: "ns",
errChecker: IsNil,
},
{
cli: nil,
podName: "pod",
namespace: "ns",
errChecker: NotNil,
},
} {
cleaner := &cleanse{
kubeCli: tc.cli,
}
err := cleaner.DeletePod(ctx, tc.podName, tc.namespace)
c.Check(err, tc.errChecker)
}
}
func (s *CSITestSuite) TestDeleteSnapshot(c *C) {
ctx := context.Background()
for _, tc := range []struct {
cli dynamic.Interface
snapshotName string
namespace string
errChecker Checker
}{
{
cli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
snapshotName: "snap1",
namespace: "ns",
errChecker: NotNil,
},
{
cli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme(),
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version),
"kind": "VolumeSnapshot",
"metadata": map[string]interface{}{
"name": "snap1",
"namespace": "notns",
},
},
}),
snapshotName: "pod",
namespace: "ns",
errChecker: NotNil,
},
{
cli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme(),
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version),
"kind": "VolumeSnapshot",
"metadata": map[string]interface{}{
"name": "snap1",
"namespace": "ns",
},
},
}),
snapshotName: "snap1",
namespace: "ns",
errChecker: IsNil,
},
{
cli: nil,
snapshotName: "pod",
namespace: "ns",
errChecker: NotNil,
},
} {
cleaner := &cleanse{
dynCli: tc.cli,
}
err := cleaner.DeleteSnapshot(ctx, tc.snapshotName, tc.namespace)
c.Check(err, tc.errChecker)
}
}

View file

@ -0,0 +1,850 @@
package csi
import (
"context"
"fmt"
"github.com/golang/mock/gomock"
"github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
"github.com/kastenhq/kubestr/pkg/csi/mocks"
"github.com/kastenhq/kubestr/pkg/csi/types"
. "gopkg.in/check.v1"
v1 "k8s.io/api/core/v1"
sv1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func (s *CSITestSuite) TestValidateArgs(c *C) {
ctx := context.Background()
type fields struct {
validateOps *mocks.MockArgumentValidator
versionOps *mocks.MockApiVersionFetcher
}
for _, tc := range []struct {
args *types.CSISnapshotRestoreArgs
prepare func(f *fields)
errChecker Checker
}{
{ // valid args
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "vsc",
Namespace: "ns",
},
prepare: func(f *fields) {
gomock.InOrder(
f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil),
f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), "sc").Return(
&sv1.StorageClass{
Provisioner: "p1",
}, nil),
f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(
&metav1.GroupVersionForDiscovery{
GroupVersion: alphaVersion,
}, nil),
f.validateOps.EXPECT().ValidateVolumeSnapshotClass(gomock.Any(), "vsc", &metav1.GroupVersionForDiscovery{
GroupVersion: alphaVersion,
}).Return(&unstructured.Unstructured{
Object: map[string]interface{}{
VolSnapClassAlphaDriverKey: "p1",
},
}, nil),
)
},
errChecker: IsNil,
},
{ // driver mismatch
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "vsc",
Namespace: "ns",
},
prepare: func(f *fields) {
gomock.InOrder(
f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil),
f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), "sc").Return(
&sv1.StorageClass{
Provisioner: "p1",
}, nil),
f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(
&metav1.GroupVersionForDiscovery{
GroupVersion: alphaVersion,
}, nil),
f.validateOps.EXPECT().ValidateVolumeSnapshotClass(gomock.Any(), "vsc", &metav1.GroupVersionForDiscovery{
GroupVersion: alphaVersion,
}).Return(&unstructured.Unstructured{
Object: map[string]interface{}{
VolSnapClassAlphaDriverKey: "p2",
},
}, nil),
)
},
errChecker: NotNil,
},
{ // vsc error
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "vsc",
Namespace: "ns",
},
prepare: func(f *fields) {
gomock.InOrder(
f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil),
f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), "sc").Return(
&sv1.StorageClass{
Provisioner: "p1",
}, nil),
f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(
&metav1.GroupVersionForDiscovery{
GroupVersion: alphaVersion,
}, nil),
f.validateOps.EXPECT().ValidateVolumeSnapshotClass(gomock.Any(), "vsc", &metav1.GroupVersionForDiscovery{
GroupVersion: alphaVersion,
}).Return(nil, fmt.Errorf("vsc error")),
)
},
errChecker: NotNil,
},
{ // groupversion error
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "vsc",
Namespace: "ns",
},
prepare: func(f *fields) {
gomock.InOrder(
f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil),
f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), "sc").Return(
&sv1.StorageClass{
Provisioner: "p1",
}, nil),
f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(
nil, fmt.Errorf("groupversion error")),
)
},
errChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "vsc",
Namespace: "ns",
},
prepare: func(f *fields) {
gomock.InOrder(
f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil),
f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), "sc").Return(
nil, fmt.Errorf("sc error")),
)
},
errChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "vsc",
Namespace: "ns",
},
prepare: func(f *fields) {
gomock.InOrder(
f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(fmt.Errorf("ns error")),
)
},
errChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "",
VolumeSnapshotClass: "vsc",
Namespace: "ns",
},
errChecker: NotNil,
}, {
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "",
Namespace: "ns",
},
errChecker: NotNil,
}, {
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
VolumeSnapshotClass: "vsc",
Namespace: "",
},
errChecker: NotNil,
},
} {
ctrl := gomock.NewController(c)
defer ctrl.Finish()
f := fields{
validateOps: mocks.NewMockArgumentValidator(ctrl),
versionOps: mocks.NewMockApiVersionFetcher(ctrl),
}
if tc.prepare != nil {
tc.prepare(&f)
}
stepper := &snapshotRestoreSteps{
validateOps: f.validateOps,
versionFetchOps: f.versionOps,
}
err := stepper.ValidateArgs(ctx, tc.args)
c.Check(err, tc.errChecker)
}
}
func (s *CSITestSuite) TestCreateApplication(c *C) {
ctx := context.Background()
type fields struct {
createAppOps *mocks.MockApplicationCreator
}
for _, tc := range []struct {
args *types.CSISnapshotRestoreArgs
genString string
prepare func(f *fields)
errChecker Checker
podChecker Checker
pvcChecker Checker
}{
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
genString: "some string",
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), &types.CreatePVCArgs{
GenerateName: originalPVCGenerateName,
StorageClass: "sc",
Namespace: "ns",
}).Return(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
}, nil),
f.createAppOps.EXPECT().CreatePod(gomock.Any(), &types.CreatePodArgs{
GenerateName: originalPodGenerateName,
PVCName: "pvc1",
Namespace: "ns",
Cmd: "echo 'some string' >> /data/out.txt; sync; tail -f /dev/null",
RunAsUser: 100,
ContainerImage: "image",
}).Return(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
},
}, nil),
f.createAppOps.EXPECT().WaitForPodReady(gomock.Any(), "ns", "pod1").Return(nil),
)
},
errChecker: IsNil,
podChecker: NotNil,
pvcChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
genString: "some string",
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), &types.CreatePVCArgs{
GenerateName: originalPVCGenerateName,
StorageClass: "sc",
Namespace: "ns",
}).Return(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
}, nil),
f.createAppOps.EXPECT().CreatePod(gomock.Any(), &types.CreatePodArgs{
GenerateName: originalPodGenerateName,
PVCName: "pvc1",
Namespace: "ns",
Cmd: "echo 'some string' >> /data/out.txt; sync; tail -f /dev/null",
RunAsUser: 100,
ContainerImage: "image",
}).Return(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
},
}, nil),
f.createAppOps.EXPECT().WaitForPodReady(gomock.Any(), "ns", "pod1").Return(fmt.Errorf("pod ready error")),
)
},
errChecker: NotNil,
podChecker: NotNil,
pvcChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
genString: "some string",
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), gomock.Any()).Return(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
}, nil),
f.createAppOps.EXPECT().CreatePod(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("create pod error")),
)
},
errChecker: NotNil,
podChecker: IsNil,
pvcChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
genString: "some string",
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("create pvc error")),
)
},
errChecker: NotNil,
podChecker: IsNil,
pvcChecker: IsNil,
},
} {
ctrl := gomock.NewController(c)
defer ctrl.Finish()
f := fields{
createAppOps: mocks.NewMockApplicationCreator(ctrl),
}
if tc.prepare != nil {
tc.prepare(&f)
}
stepper := &snapshotRestoreSteps{
createAppOps: f.createAppOps,
}
pod, pvc, err := stepper.CreateApplication(ctx, tc.args, tc.genString)
c.Check(err, tc.errChecker)
c.Check(pod, tc.podChecker)
c.Check(pvc, tc.pvcChecker)
}
}
func (s *CSITestSuite) TestSnapshotApplication(c *C) {
ctx := context.Background()
snapshotter := &fakeSnapshotter{name: "snapshotter"}
type fields struct {
snapshotOps *mocks.MockSnapshotCreator
}
for _, tc := range []struct {
args *types.CSISnapshotRestoreArgs
pvc *v1.PersistentVolumeClaim
snapshotName string
prepare func(f *fields)
errChecker Checker
snapChecker Checker
}{
{
args: &types.CSISnapshotRestoreArgs{
Namespace: "ns",
VolumeSnapshotClass: "vsc",
},
pvc: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
},
snapshotName: "snap1",
prepare: func(f *fields) {
gomock.InOrder(
f.snapshotOps.EXPECT().NewSnapshotter().Return(snapshotter, nil),
f.snapshotOps.EXPECT().CreateSnapshot(gomock.Any(), snapshotter, &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc1",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
}).Return(&v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "createdName",
},
}, nil),
f.snapshotOps.EXPECT().CreateFromSourceCheck(gomock.Any(), snapshotter, &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "createdName",
Namespace: "ns",
}).Return(nil),
)
},
errChecker: IsNil,
snapChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
Namespace: "ns",
VolumeSnapshotClass: "vsc",
SkipCFSCheck: true,
},
pvc: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
},
snapshotName: "snap1",
prepare: func(f *fields) {
gomock.InOrder(
f.snapshotOps.EXPECT().NewSnapshotter().Return(snapshotter, nil),
f.snapshotOps.EXPECT().CreateSnapshot(gomock.Any(), snapshotter, &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc1",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
}).Return(&v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "createdName",
},
}, nil),
)
},
errChecker: IsNil,
snapChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
Namespace: "ns",
VolumeSnapshotClass: "vsc",
},
pvc: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
},
snapshotName: "snap1",
prepare: func(f *fields) {
gomock.InOrder(
f.snapshotOps.EXPECT().NewSnapshotter().Return(snapshotter, nil),
f.snapshotOps.EXPECT().CreateSnapshot(gomock.Any(), snapshotter, &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc1",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
}).Return(&v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "createdName",
},
}, nil),
f.snapshotOps.EXPECT().CreateFromSourceCheck(gomock.Any(), snapshotter, &types.CreateFromSourceCheckArgs{
VolumeSnapshotClass: "vsc",
SnapshotName: "createdName",
Namespace: "ns",
}).Return(fmt.Errorf("cfs error")),
)
},
errChecker: NotNil,
snapChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
Namespace: "ns",
VolumeSnapshotClass: "vsc",
},
pvc: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
},
snapshotName: "snap1",
prepare: func(f *fields) {
gomock.InOrder(
f.snapshotOps.EXPECT().NewSnapshotter().Return(snapshotter, nil),
f.snapshotOps.EXPECT().CreateSnapshot(gomock.Any(), snapshotter, &types.CreateSnapshotArgs{
Namespace: "ns",
PVCName: "pvc1",
VolumeSnapshotClass: "vsc",
SnapshotName: "snap1",
}).Return(nil, fmt.Errorf("create snapshot error")),
)
},
errChecker: NotNil,
snapChecker: IsNil,
},
{
args: &types.CSISnapshotRestoreArgs{
Namespace: "ns",
VolumeSnapshotClass: "vsc",
},
pvc: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
},
snapshotName: "snap1",
prepare: func(f *fields) {
gomock.InOrder(
f.snapshotOps.EXPECT().NewSnapshotter().Return(nil, fmt.Errorf("snapshotter error")),
)
},
errChecker: NotNil,
snapChecker: IsNil,
},
} {
ctrl := gomock.NewController(c)
defer ctrl.Finish()
f := fields{
snapshotOps: mocks.NewMockSnapshotCreator(ctrl),
}
if tc.prepare != nil {
tc.prepare(&f)
}
stepper := &snapshotRestoreSteps{
snapshotCreateOps: f.snapshotOps,
}
snapshot, err := stepper.SnapshotApplication(ctx, tc.args, tc.pvc, tc.snapshotName)
c.Check(err, tc.errChecker)
c.Check(snapshot, tc.snapChecker)
}
}
func (s *CSITestSuite) TestRestoreApplication(c *C) {
ctx := context.Background()
resourceQuantity := resource.MustParse("1Gi")
snapshotAPIGroup := "snapshot.storage.k8s.io"
type fields struct {
createAppOps *mocks.MockApplicationCreator
}
for _, tc := range []struct {
args *types.CSISnapshotRestoreArgs
snapshot *v1alpha1.VolumeSnapshot
prepare func(f *fields)
errChecker Checker
podChecker Checker
pvcChecker Checker
}{
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
snapshot: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snap1",
},
Status: v1alpha1.VolumeSnapshotStatus{
RestoreSize: &resourceQuantity,
},
},
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), &types.CreatePVCArgs{
GenerateName: clonedPVCGenerateName,
StorageClass: "sc",
Namespace: "ns",
DataSource: &v1.TypedLocalObjectReference{
APIGroup: &snapshotAPIGroup,
Kind: "VolumeSnapshot",
Name: "snap1",
},
RestoreSize: &resourceQuantity,
}).Return(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
}, nil),
f.createAppOps.EXPECT().CreatePod(gomock.Any(), &types.CreatePodArgs{
GenerateName: clonedPodGenerateName,
PVCName: "pvc1",
Namespace: "ns",
Cmd: "tail -f /dev/null",
RunAsUser: 100,
ContainerImage: "image",
}).Return(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
},
}, nil),
f.createAppOps.EXPECT().WaitForPodReady(gomock.Any(), "ns", "pod1").Return(nil),
)
},
errChecker: IsNil,
podChecker: NotNil,
pvcChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
snapshot: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snap1",
},
Status: v1alpha1.VolumeSnapshotStatus{
RestoreSize: &resourceQuantity,
},
},
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), &types.CreatePVCArgs{
GenerateName: clonedPVCGenerateName,
StorageClass: "sc",
Namespace: "ns",
DataSource: &v1.TypedLocalObjectReference{
APIGroup: &snapshotAPIGroup,
Kind: "VolumeSnapshot",
Name: "snap1",
},
RestoreSize: &resourceQuantity,
}).Return(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
}, nil),
f.createAppOps.EXPECT().CreatePod(gomock.Any(), &types.CreatePodArgs{
GenerateName: clonedPodGenerateName,
PVCName: "pvc1",
Namespace: "ns",
Cmd: "tail -f /dev/null",
RunAsUser: 100,
ContainerImage: "image",
}).Return(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
},
}, nil),
f.createAppOps.EXPECT().WaitForPodReady(gomock.Any(), "ns", "pod1").Return(fmt.Errorf("pod ready error")),
)
},
errChecker: NotNil,
podChecker: NotNil,
pvcChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
snapshot: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snap1",
},
Status: v1alpha1.VolumeSnapshotStatus{
RestoreSize: &resourceQuantity,
},
},
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), gomock.Any()).Return(&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
},
}, nil),
f.createAppOps.EXPECT().CreatePod(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("create pod error")),
)
},
errChecker: NotNil,
podChecker: IsNil,
pvcChecker: NotNil,
},
{
args: &types.CSISnapshotRestoreArgs{
StorageClass: "sc",
Namespace: "ns",
RunAsUser: 100,
ContainerImage: "image",
},
snapshot: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snap1",
},
Status: v1alpha1.VolumeSnapshotStatus{
RestoreSize: &resourceQuantity,
},
},
prepare: func(f *fields) {
gomock.InOrder(
f.createAppOps.EXPECT().CreatePVC(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("create pvc error")),
)
},
errChecker: NotNil,
podChecker: IsNil,
pvcChecker: IsNil,
},
} {
ctrl := gomock.NewController(c)
defer ctrl.Finish()
f := fields{
createAppOps: mocks.NewMockApplicationCreator(ctrl),
}
if tc.prepare != nil {
tc.prepare(&f)
}
stepper := &snapshotRestoreSteps{
createAppOps: f.createAppOps,
}
pod, pvc, err := stepper.RestoreApplication(ctx, tc.args, tc.snapshot)
c.Check(err, tc.errChecker)
c.Check(pod, tc.podChecker)
c.Check(pvc, tc.pvcChecker)
}
}
func (s *CSITestSuite) TestCleanup(c *C) {
ctx := context.Background()
type fields struct {
cleanerOps *mocks.MockCleaner
}
for _, tc := range []struct {
results *types.CSISnapshotRestoreResults
prepare func(f *fields)
}{
{
results: nil,
},
{
results: &types.CSISnapshotRestoreResults{
OriginalPVC: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
Namespace: "ns",
},
},
OriginalPod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns",
},
},
ClonedPVC: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc2",
Namespace: "ns",
},
},
ClonedPod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns",
},
},
Snapshot: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snapshot",
Namespace: "ns",
},
},
},
prepare: func(f *fields) {
gomock.InOrder(
f.cleanerOps.EXPECT().DeletePVC(ctx, "pvc1", "ns").Return(nil),
f.cleanerOps.EXPECT().DeletePod(ctx, "pod1", "ns").Return(nil),
f.cleanerOps.EXPECT().DeletePVC(ctx, "pvc2", "ns").Return(nil),
f.cleanerOps.EXPECT().DeletePod(ctx, "pod2", "ns").Return(nil),
f.cleanerOps.EXPECT().DeleteSnapshot(ctx, "snapshot", "ns").Return(nil),
)
},
},
} {
ctrl := gomock.NewController(c)
defer ctrl.Finish()
f := fields{
cleanerOps: mocks.NewMockCleaner(ctrl),
}
if tc.prepare != nil {
tc.prepare(&f)
}
stepper := &snapshotRestoreSteps{
cleanerOps: f.cleanerOps,
}
stepper.Cleanup(ctx, tc.results)
}
}
func (s *CSITestSuite) TestValidateData(c *C) {
ctx := context.Background()
type fields struct {
validatorOps *mocks.MockDataValidator
}
for _, tc := range []struct {
prepare func(f *fields)
pod *v1.Pod
data string
errChecker Checker
}{
{
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Namespace: "ns",
},
},
data: "somedata",
prepare: func(f *fields) {
gomock.InOrder(
f.validatorOps.EXPECT().FetchPodData("pod", "ns").Return("somedata", nil),
)
},
errChecker: IsNil,
},
{
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Namespace: "ns",
},
},
data: "somedata",
prepare: func(f *fields) {
gomock.InOrder(
f.validatorOps.EXPECT().FetchPodData("pod", "ns").Return("someotherdata", nil),
)
},
errChecker: NotNil,
},
{
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Namespace: "ns",
},
},
data: "somedata",
prepare: func(f *fields) {
gomock.InOrder(
f.validatorOps.EXPECT().FetchPodData("pod", "ns").Return("", fmt.Errorf("error")),
)
},
errChecker: NotNil,
},
} {
ctrl := gomock.NewController(c)
defer ctrl.Finish()
f := fields{
validatorOps: mocks.NewMockDataValidator(ctrl),
}
if tc.prepare != nil {
tc.prepare(&f)
}
stepper := &snapshotRestoreSteps{
dataValidatorOps: f.validatorOps,
}
err := stepper.ValidateData(ctx, tc.pod, tc.data)
c.Check(err, tc.errChecker)
}
}

View file

@ -0,0 +1,421 @@
package csi
import (
"context"
"fmt"
"testing"
"github.com/golang/mock/gomock"
"github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
"github.com/kastenhq/kubestr/pkg/csi/mocks"
"github.com/kastenhq/kubestr/pkg/csi/types"
. "gopkg.in/check.v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
fakedynamic "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
)
func Test(t *testing.T) { TestingT(t) }
type CSITestSuite struct{}
var _ = Suite(&CSITestSuite{})
func (s *CSITestSuite) TestRunSnapshotRestoreHelper(c *C) {
ctx := context.Background()
type fields struct {
stepperOps *mocks.MockSnapshotRestoreStepper
}
for _, tc := range []struct {
kubeCli kubernetes.Interface
dynCli dynamic.Interface
args *types.CSISnapshotRestoreArgs
prepare func(f *fields)
result *types.CSISnapshotRestoreResults
errChecker Checker
}{
{ // success
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: true,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns",
},
},
&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
Namespace: "ns",
},
},
nil,
),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns",
},
}, gomock.Any()).Return(nil),
f.stepperOps.EXPECT().SnapshotApplication(gomock.Any(), gomock.Any(),
&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
Namespace: "ns",
},
}, gomock.Any(),
).Return(
&v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snapshot",
},
},
nil,
),
f.stepperOps.EXPECT().RestoreApplication(gomock.Any(), gomock.Any(),
&v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snapshot",
},
},
).Return(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns",
},
},
&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc2",
Namespace: "ns",
},
},
nil,
),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns",
},
}, gomock.Any()).Return(nil),
f.stepperOps.EXPECT().Cleanup(gomock.Any(), gomock.Any()).Return(),
)
},
result: &types.CSISnapshotRestoreResults{
OriginalPVC: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
Namespace: "ns",
},
},
OriginalPod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns",
},
},
ClonedPVC: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc2",
Namespace: "ns",
},
},
ClonedPod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns",
},
},
Snapshot: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snapshot",
},
},
},
errChecker: IsNil,
},
{ // no cleanup
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().SnapshotApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil),
f.stepperOps.EXPECT().RestoreApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
)
},
result: &types.CSISnapshotRestoreResults{},
errChecker: IsNil,
},
{ // restored data validation fails
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().SnapshotApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil),
f.stepperOps.EXPECT().RestoreApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf("validation error")),
)
},
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
{ // restore error, objects still returned
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().SnapshotApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil),
f.stepperOps.EXPECT().RestoreApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns",
},
},
&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc2",
Namespace: "ns",
},
},
fmt.Errorf("restore error"),
),
)
},
result: &types.CSISnapshotRestoreResults{
ClonedPVC: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc2",
Namespace: "ns",
},
},
ClonedPod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod2",
Namespace: "ns",
},
},
},
errChecker: NotNil,
},
{ // restore error, no objects returned
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().SnapshotApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil),
f.stepperOps.EXPECT().RestoreApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("restore error")),
)
},
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
{ // snapshot error, object still returned
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().SnapshotApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(
&v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snapshot",
},
},
fmt.Errorf("snapshot error"),
),
)
},
result: &types.CSISnapshotRestoreResults{
Snapshot: &v1alpha1.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "snapshot",
},
},
},
errChecker: NotNil,
},
{ // snapshot error, object not returned
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().SnapshotApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("snapshot error")),
)
},
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
{ // created data validation error
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil),
f.stepperOps.EXPECT().ValidateData(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf("validation error")),
)
},
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
{ // create error, objects still returned
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns",
},
},
&v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
Namespace: "ns",
},
},
fmt.Errorf("create error"),
),
)
},
result: &types.CSISnapshotRestoreResults{
OriginalPVC: &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc1",
Namespace: "ns",
},
},
OriginalPod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
Namespace: "ns",
},
},
},
errChecker: NotNil,
},
{ // create error, objects not returned
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil),
f.stepperOps.EXPECT().CreateApplication(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("create error")),
)
},
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
{ // args validate error
kubeCli: fake.NewSimpleClientset(),
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
args: &types.CSISnapshotRestoreArgs{
Cleanup: false,
},
prepare: func(f *fields) {
gomock.InOrder(
f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(fmt.Errorf("create error")),
)
},
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
{ // empty cli
kubeCli: nil,
dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()),
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
{ // empty dyncli
kubeCli: fake.NewSimpleClientset(),
dynCli: nil,
result: &types.CSISnapshotRestoreResults{},
errChecker: NotNil,
},
} {
ctrl := gomock.NewController(c)
defer ctrl.Finish()
f := fields{
stepperOps: mocks.NewMockSnapshotRestoreStepper(ctrl),
}
if tc.prepare != nil {
tc.prepare(&f)
}
runner := &SnapshotRestoreRunner{
KubeCli: tc.kubeCli,
DynCli: tc.dynCli,
srSteps: f.stepperOps,
}
result, err := runner.RunSnapshotRestoreHelper(ctx, tc.args)
c.Check(err, tc.errChecker)
c.Assert(result, DeepEquals, tc.result)
}
}
func (s *CSITestSuite) TestRunSnapshotRestoreRunner(c *C) {
ctx := context.Background()
r := &SnapshotRestoreRunner{}
_, err := r.RunSnapshotRestore(ctx, nil)
c.Check(err, NotNil)
}

View file

@ -0,0 +1,92 @@
package types
import (
"fmt"
"github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
type CSISnapshotRestoreArgs struct {
StorageClass string
VolumeSnapshotClass string
Namespace string
RunAsUser int64
ContainerImage string
Cleanup bool
SkipCFSCheck bool
}
func (a *CSISnapshotRestoreArgs) Validate() error {
if a.StorageClass == "" || a.VolumeSnapshotClass == "" || a.Namespace == "" {
return fmt.Errorf("Require fields are missing. (StorageClass, VolumeSnapshotClass, Namespace)")
}
return nil
}
type CSISnapshotRestoreResults struct {
OriginalPVC *v1.PersistentVolumeClaim
OriginalPod *v1.Pod
Snapshot *v1alpha1.VolumeSnapshot
ClonedPVC *v1.PersistentVolumeClaim
ClonedPod *v1.Pod
}
type CreatePVCArgs struct {
GenerateName string
StorageClass string
Namespace string
DataSource *v1.TypedLocalObjectReference
RestoreSize *resource.Quantity
}
func (c *CreatePVCArgs) Validate() error {
if c.GenerateName == "" || c.StorageClass == "" || c.Namespace == "" {
return fmt.Errorf("Invalid CreatePVCArgs (%v)", c)
}
return nil
}
type CreatePodArgs struct {
GenerateName string
PVCName string
Namespace string
Cmd string
RunAsUser int64
ContainerImage string
}
func (c *CreatePodArgs) Validate() error {
if c.GenerateName == "" || c.PVCName == "" || c.Namespace == "" || c.Cmd == "" {
return fmt.Errorf("Invalid CreatePodArgs (%v)", c)
}
return nil
}
type CreateSnapshotArgs struct {
Namespace string
PVCName string
VolumeSnapshotClass string
SnapshotName string
}
func (c *CreateSnapshotArgs) Validate() error {
if c.Namespace == "" || c.PVCName == "" || c.VolumeSnapshotClass == "" || c.SnapshotName == "" {
return fmt.Errorf("Invalid CreateSnapshotArgs (%v)", c)
}
return nil
}
type CreateFromSourceCheckArgs struct {
VolumeSnapshotClass string
SnapshotName string
Namespace string
}
func (c *CreateFromSourceCheckArgs) Validate() error {
if c.VolumeSnapshotClass == "" || c.SnapshotName == "" || c.Namespace == "" {
return fmt.Errorf("Invalid CreateFromSourceCheckArgs (%v)", c)
}
return nil
}

View file

@ -524,7 +524,7 @@ func (s *FIOTestSuite) TestDeletePVC(c *C) {
func (s *FIOTestSuite) TestCreatPod(c *C) {
ctx := context.Background()
for i, tc := range []struct {
for _, tc := range []struct {
pvcName string
configMapName string
testFileName string
@ -610,7 +610,6 @@ func (s *FIOTestSuite) TestCreatPod(c *C) {
errChecker: NotNil,
},
} {
fmt.Println(i)
stepper := &fioStepper{
cli: fake.NewSimpleClientset(),
podReady: &fakePodReadyChecker{prcErr: tc.podReadyErr},

View file

@ -24,12 +24,11 @@ var CSIDriverList = []*CSIDriver{
{NameUrl: "[Datatom-InfinityCSI](https://github.com/datatom-infinity/infinity-csi)", DriverName: "csi-infiblock-plugin", Versions: "v0.3, v1.0.0, v1.1.0", Description: "A Container Storage Interface (CSI) Driver for DATATOM Infinity storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Topology"},
{NameUrl: "[Datatom-InfinityCSI (filesystem)](https://github.com/datatom-infinity/infinity-csi)", DriverName: "csi-infifs-plugin", Versions: "v0.3, v1.0.0, v1.1.0", Description: "A Container Storage Interface (CSI) Driver for DATATOM Infinity filesystem storage", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Expansion"},
{NameUrl: "[Datera](https://github.com/Datera/datera-csi)", DriverName: "dsp.csi.daterainc.io", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Datera Data Services Platform (DSP)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Dell EMC Isilon](https://github.com/dell/csi-isilon)", DriverName: "csi-isilon.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC Isilon](https://www.delltechnologies.com/en-us/storage/isilon/index.htm)", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Dell EMC PowerMax](https://github.com/dell/csi-powermax)", DriverName: "csi-powermax.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC PowerMax](https://www.delltechnologies.com/en-us/storage/powermax.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Dell EMC PowerStore](https://github.com/dell/csi-powerstore)", DriverName: "csi-powerstore.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC PowerStore](https://www.delltechnologies.com/en-us/storage/powerstore-storage-appliance.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[Dell EMC Unity](https://github.com/dell/csi-unity)", DriverName: "csi-unity.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC Unity](https://www.delltechnologies.com/en-us/storage/unity.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Dell EMC VxFlexOS](https://github.com/dell/csi-vxflexos)", DriverName: "csi-vxflexos.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC VxFlexOS](https://www.delltechnologies.com/en-us/hyperconverged-infrastructure/vxflex.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Dell EMC XtremIO](https://github.com/dell/csi-xtremio-deploy)", DriverName: "csi-xtremio.dellemc.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC XtremIO](https://www.delltechnologies.com/en-us/storage/xtremio-all-flash.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Dell EMC PowerScale](https://github.com/dell/csi-powerscale)", DriverName: "csi-isilon.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC PowerScale](https://www.delltechnologies.com/en-us/storage/powerscale.htm)", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot, Expansion, Cloning"},
{NameUrl: "[Dell EMC PowerMax](https://github.com/dell/csi-powermax)", DriverName: "csi-powermax.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC PowerMax](https://www.delltechnologies.com/en-us/storage/powermax.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[Dell EMC PowerStore](https://github.com/dell/csi-powerstore)", DriverName: "csi-powerstore.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC PowerStore](https://www.delltechnologies.com/en-us/storage/powerstore-storage-appliance.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[Dell EMC Unity](https://github.com/dell/csi-unity)", DriverName: "csi-unity.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC Unity](https://www.delltechnologies.com/en-us/storage/unity.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[Dell EMC VxFlexOS](https://github.com/dell/csi-vxflexos)", DriverName: "csi-vxflexos.dellemc.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Dell EMC VxFlexOS](https://www.delltechnologies.com/en-us/hyperconverged-infrastructure/vxflex.htm)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Topology"},
{NameUrl: "[democratic-csi](https://github.com/democratic-csi/democratic-csi)", DriverName: "org.democratic-csi.[X]", Versions: "v1.0,v1.1,v1.2", Description: "Generic CSI plugin supporting zfs based solutions ([FreeNAS](https://www.freenas.org/) / [TrueNAS](https://www.truenas.com/) and [ZoL](https://zfsonlinux.org/) solutions such as [Ubuntu](https://ubuntu.com/))", Persistence: "Persistent and Ephemeral", AccessModes: "Read/Write Single Pod (Block Volume) <br/><br/> Read/Write Multiple Pods (File Volume)", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[Diamanti-CSI](https://diamanti.com/use-cases/io-acceleration/#csi)", DriverName: "dcx.csi.diamanti.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Diamanti DCX Platform", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion"},
{NameUrl: "[DigitalOcean Block Storage](https://github.com/digitalocean/csi-digitalocean)", DriverName: "dobs.csi.digitalocean.com", Versions: "v0.3, v1.0", Description: "A Container Storage Interface (CSI) Driver for DigitalOcean Block Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion"},
@ -42,7 +41,7 @@ var CSIDriverList = []*CSIDriver{
{NameUrl: "[GlusterFS](https://github.com/gluster/gluster-csi-driver)", DriverName: "org.gluster.glusterfs", Versions: "v0.3, v1.0", Description: "A Container Storage Interface (CSI) Driver for GlusterFS", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Gluster VirtBlock](https://github.com/gluster/gluster-csi-driver)", DriverName: "org.gluster.glustervirtblock", Versions: "v0.3, v1.0", Description: "A Container Storage Interface (CSI) Driver for Gluster Virtual Block volumes", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[Hammerspace CSI](https://github.com/hammer-space/csi-plugin)", DriverName: "com.hammerspace.csi", Versions: "v0.3, v1.0", Description: "A Container Storage Interface (CSI) Driver for Hammerspace Storage", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot"},
{NameUrl: "[Hedvig](https://documentation.commvault.com/commvault/hedvig/others/pdf/Hedvig_CSI_User_Guide.pdf)", DriverName: "io.hedvig.csi", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Hedvig", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Hedvig](https://documentation.commvault.com/commvault/hedvig/others/pdf/Hedvig_CSI_User_Guide.pdf)", DriverName: "io.hedvig.csi", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Hedvig", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion"},
{NameUrl: "[Hetzner Cloud Volumes CSI](https://github.com/hetznercloud/csi-driver)", DriverName: "csi.hetzner.cloud", Versions: "v0.3, v1.0", Description: "A Container Storage Interface (CSI) Driver for Hetzner Cloud Volumes", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Expansion"},
{NameUrl: "[Hitachi Vantara](https://knowledge.hitachivantara.com/Documents/Adapters_and_Drivers/Storage_Adapters_and_Drivers/Containers)", DriverName: "hspc.csi.hitachi.com", Versions: "v1.2", Description: "A Container Storage Interface (CSI) Driver for VSP series Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[HPE](https://github.com/hpe-storage/csi-driver)", DriverName: "csi.hpe.com", Versions: "v1.0, v1.1, v1.2", Description: "A [multi-platform](https://scod.hpedev.io/csi_driver) Container Storage Interface (CSI) driver. Supports [HPE Nimble Storage](https://hpe.com/storage/nimble), [HPE Primera](https://hpe.com/storage/primera) and [HPE 3PAR](https://hpe.com/storage/3par)", Persistence: "Persistent and Ephemeral", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
@ -56,6 +55,7 @@ var CSIDriverList = []*CSIDriver{
{NameUrl: "[Intel PMEM-CSI](https://github.com/intel/pmem-csi)", DriverName: "pmem-csi.intel.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) driver for [PMEM](https://pmem.io/) from Intel", Persistence: "Persistent and Ephemeral", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block"},
{NameUrl: "[JuiceFS](https://github.com/juicedata/juicefs-csi-driver)", DriverName: "csi.juicefs.com", Versions: "v0.3, v1.0", Description: "A Container Storage Interface (CSI) Driver for JuiceFS File System", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pod", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[kaDalu](https://github.com/kadalu/kadalu)", DriverName: "org.kadalu.gluster", Versions: "v0.3", Description: "A CSI Driver (and operator) for GlusterFS", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[KumoScale Block Storage](https://github.com/KioxiaAmerica/kumoscale-csi)", DriverName: "kumoscale.kioxia.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for KumoScale Block Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Topology"},
{NameUrl: "[Linode Block Storage](https://github.com/linode/linode-blockstorage-csi-driver)", DriverName: "linodebs.csi.linode.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Linode Block Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[LINSTOR](https://github.com/LINBIT/linstor-csi)", DriverName: "io.drbd.linstor-csi", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [LINSTOR](https://www.linbit.com/en/linstor/) volumes", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Longhorn](https://github.com/longhorn/longhorn)", DriverName: "driver.longhorn.io", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for [Longhorn](https://longhorn.io/) volumes", Persistence: "Persistent", AccessModes: "Read/Write Single Node", DynamicProvisioning: "Yes", Features: "Raw Block"},
@ -71,13 +71,14 @@ var CSIDriverList = []*CSIDriver{
{NameUrl: "[OpenSDS](https://github.com/opensds/nbp/tree/master/csi)", DriverName: "csi-opensdsplugin", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for [OpenSDS]((https://www.opensds.io/))", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot"},
{NameUrl: "[Open-E](https://github.com/open-e/JovianDSS-KubernetesCSI)", DriverName: "com.open-e.joviandss.csi", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Open-E JovianDSS Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot, Cloning"},
{NameUrl: "[Portworx](https://github.com/libopenstorage/openstorage/tree/master/csi)", DriverName: "pxd.openstorage.org", Versions: "v0.3, v1.1", Description: "A Container Storage Interface (CSI) Driver for [Portworx](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/csi/)", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot, Expansion"},
{NameUrl: "[Pure Storage CSI](https://github.com/purestorage/pso-csi)", DriverName: "pure-csi", Versions: "v1.0, v1.1, v1.2", Description: "A Container Storage Interface (CSI) Driver for Pure Storage's [Pure Service Orchestrator](https://purestorage.com/containers)", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot, Cloning, Raw Block, Topology, Expansion"},
{NameUrl: "[QingCloud CSI](https://github.com/yunify/qingcloud-csi)", DriverName: "disk.csi.qingcloud.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for QingCloud Block Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot, Expansion, Cloning"},
{NameUrl: "[QingStor CSI](https://github.com/yunify/qingstor-csi)", DriverName: "csi-neonsan", Versions: "v0.3", Description: "A Container Storage Interface (CSI) Driver for NeonSAN storage system", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Snapshot"},
{NameUrl: "[Pure Storage CSI](https://github.com/purestorage/pso-csi)", DriverName: "pure-csi", Versions: "v1.0, v1.1, v1.2, v1.3", Description: "A Container Storage Interface (CSI) Driver for Pure Storage's [Pure Service Orchestrator](https://purestorage.com/containers)", Persistence: "Persistent and Ephemeral", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot, Cloning, Raw Block, Topology, Expansion"},
{NameUrl: "[QingCloud CSI](https://github.com/yunify/qingcloud-csi)", DriverName: "disk.csi.qingcloud.com", Versions: "v1.1", Description: "A Container Storage Interface (CSI) Driver for QingCloud Block Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[QingStor CSI](https://github.com/yunify/qingstor-csi)", DriverName: "neonsan.csi.qingstor.com", Versions: "v0.3, v1.1", Description: "A Container Storage Interface (CSI) Driver for NeonSAN storage system", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[Quobyte](https://github.com/quobyte/quobyte-csi)", DriverName: "quobyte-csi", Versions: "v0.2", Description: "A Container Storage Interface (CSI) Driver for Quobyte", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[ROBIN](https://get.robin.io/)", DriverName: "robin", Versions: "v0.3, v1.0", Description: "A Container Storage Interface (CSI) Driver for [ROBIN](https://docs.robin.io)", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Cloning"},
{NameUrl: "[SandStone](https://github.com/sandstone-storage/sandstone-csi-driver)", DriverName: "csi-sandstone-plugin", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for SandStone USP", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[Sangfor-EDS](https://github.com/evan37717/sangfor-eds-csi)", DriverName: "eds.csi.sangfor.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Sangfor Distributed File Storage(EDS)", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[Sangfor-EDS-Block-Storage](https://github.com/eds-wzc/sangfor-eds-csi)", DriverName: "eds.csi.block.sangfor.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for Sangfor Block Storage(EDS)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[SeaweedFS](https://github.com/seaweedfs/seaweedfs-csi-driver)", DriverName: "seaweedfs-csi-driver", Versions: "v1.0", Description: "A Container Storage Interface (CSI Driver for [SeaweedFS](https://github.com/chrislusf/seaweedfs))", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[Secrets Store CSI Driver](https://github.com/kubernetes-sigs/secrets-store-csi-driver)", DriverName: "secrets-store.csi.k8s.io", Versions: "v0.0.10", Description: "A Container Storage Interface (CSI) Driver for mounting secrets, keys, and certs stored in enterprise-grade external secrets stores as volumes.", Persistence: "Ephemeral", AccessModes: "N/A", DynamicProvisioning: "N/A", Features: ""},
{NameUrl: "[SmartX](http://www.smartx.com/?locale=en)", DriverName: "csi-smtx-plugin", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for SmartX ZBS Storage", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot, Expansion"},
@ -93,7 +94,9 @@ var CSIDriverList = []*CSIDriver{
{NameUrl: "[XSKY-EBS](https://xsky-storage.github.io/xsky-csi-driver/csi-block.html)", DriverName: "csi.block.xsky.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for XSKY Distributed Block Storage (X-EBS)", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: "Raw Block, Snapshot, Expansion, Cloning"},
{NameUrl: "[XSKY-EUS](https://xsky-storage.github.io/xsky-csi-driver/csi-fs.html)", DriverName: "csi.fs.xsky.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for XSKY Distributed File Storage (X-EUS)", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[Vault](https://github.com/kubevault/csi-driver)", DriverName: "secrets.csi.kubevault.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for mounting HashiCorp Vault secrets as volumes.", Persistence: "Ephemeral", AccessModes: "N/A", DynamicProvisioning: "N/A", Features: ""},
{NameUrl: "[vSphere](https://github.com/kubernetes-sigs/vsphere-csi-driver)", DriverName: "csi.vsphere.vmware.com", Versions: "v2.0.0", Description: "A Container Storage Interface (CSI) Driver for VMware vSphere", Persistence: "Persistent", AccessModes: "Read/Write Single Pod (Block Volume) <br/><br/> Read/Write Multiple Pods (File Volume)", DynamicProvisioning: "Yes", Features: "Raw Block,<br/><br/>Expansion (Block Volume),<br/><br/>Topology Aware (Block Volume)"},
{NameUrl: "[Veritas InfoScale Volumes](https://www.veritas.com/solution/virtualization/containers.html)", DriverName: "org.veritas.infoscale", Versions: "v1.2", Description: "A Container Storage Interface (CSI) Driver for Veritas InfoScale volumes", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: "Snapshot, Expansion, Cloning"},
{NameUrl: "[vSphere](https://github.com/kubernetes-sigs/vsphere-csi-driver)", DriverName: "csi.vsphere.vmware.com", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for VMware vSphere", Persistence: "Persistent", AccessModes: "Read/Write Single Pod (Block Volume) <br/><br/> Read/Write Multiple Pods (File Volume)", DynamicProvisioning: "Yes", Features: "Raw Block,<br/><br/>Expansion (Block Volume),<br/><br/>Topology Aware (Block Volume)"},
{NameUrl: "[Vultr Block Storage](https://github.com/vultr/vultr-csi)", DriverName: "block.csi.vultr.com", Versions: "v1.2", Description: "A Container Storage Interface (CSI) Driver for Vultr Block Storage", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[WekaIO](https://github.com/weka/csi-wekafs)", DriverName: "csi.weka.io", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for mounting WekaIO WekaFS filesystem as volumes", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[Yandex.Cloud](https://github.com/flant/yandex-csi-driver)", DriverName: "yandex.csi.flant.com", Versions: "v1.2", Description: "A Container Storage Interface (CSI) plugin for Yandex.Cloud Compute Disks", Persistence: "Persistent", AccessModes: "Read/Write Single Pod", DynamicProvisioning: "Yes", Features: ""},
{NameUrl: "[YanRongYun](http://www.yanrongyun.com/)", DriverName: "?", Versions: "v1.0", Description: "A Container Storage Interface (CSI) Driver for YanRong YRCloudFile Storage", Persistence: "Persistent", AccessModes: "Read/Write Multiple Pods", DynamicProvisioning: "Yes", Features: ""},

View file

@ -38,11 +38,11 @@ var (
// NewKubestr initializes a new kubestr object to run preflight tests
func NewKubestr() (*Kubestr, error) {
cli, err := getKubeCli()
cli, err := LoadKubeCli()
if err != nil {
return nil, err
}
dynCli, err := getDynCli()
dynCli, err := LoadDynCli()
if err != nil {
return nil, err
}
@ -59,8 +59,8 @@ func NewKubestr() (*Kubestr, error) {
}, nil
}
// getDynCli loads the config and returns a dynamic CLI
func getDynCli() (dynamic.Interface, error) {
// LoadDynCli loads the config and returns a dynamic CLI
func LoadDynCli() (dynamic.Interface, error) {
cfg, err := kube.LoadConfig()
if err != nil {
return nil, errors.Wrap(err, "Failed to load config for Dynamic client")
@ -72,9 +72,9 @@ func getDynCli() (dynamic.Interface, error) {
return clientset, nil
}
// getKubeCli load the config and returns a kubernetes client
// LoadKubeCli load the config and returns a kubernetes client
// NewClient returns a k8 client configured by the kanister environment.
func getKubeCli() (kubernetes.Interface, error) {
func LoadKubeCli() (kubernetes.Interface, error) {
config, err := kube.LoadConfig()
if err != nil {
return nil, err

View file

@ -152,7 +152,7 @@ func (v *Provisioner) Print() {
if len(v.StorageClasses) > 0 {
fmt.Println()
fmt.Println(" To perform a FIO test, run-")
fmt.Println(" ./kubestr fio -c <storage class>")
fmt.Println(" ./kubestr fio -s <storage class>")
fmt.Println(" Validate the results against our benchmarks at <website>. For more options try '-h'.")
switch {
case len(v.VolumeSnapshotClasses) == 0 && v.CSIDriver != nil && v.CSIDriver.SupportsSnapshots():
@ -160,7 +160,8 @@ func (v *Provisioner) Print() {
fmt.Println(" This provisioner supports snapshots, however no Volume Snaphsot Classes were found.")
case len(v.VolumeSnapshotClasses) > 0:
fmt.Println()
fmt.Println(" (Coming soon) Test snapshot/restore functionality.")
fmt.Println(" To test CSI snapshot/restore functionality, run-")
fmt.Println(" ./kubestr csicheck -s <storage class> -v <volume snapshot class>")
}
}
}