mirror of
https://github.com/kastenhq/kubestr.git
synced 2024-12-14 11:57:56 +00:00
parent
137eac22a9
commit
0b3650274e
6 changed files with 592 additions and 66 deletions
1
go.mod
1
go.mod
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/kanisterio/kanister v0.0.0-00010101000000-000000000000
|
github.com/kanisterio/kanister v0.0.0-00010101000000-000000000000
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
|
||||||
k8s.io/api v0.19.0
|
k8s.io/api v0.19.0
|
||||||
k8s.io/apimachinery v0.19.0
|
k8s.io/apimachinery v0.19.0
|
||||||
k8s.io/client-go v0.19.0
|
k8s.io/client-go v0.19.0
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
version "k8s.io/apimachinery/pkg/version"
|
version "k8s.io/apimachinery/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,15 +24,15 @@ const (
|
||||||
func (p *Kubestr) KubernetesChecks() []*TestOutput {
|
func (p *Kubestr) KubernetesChecks() []*TestOutput {
|
||||||
var result []*TestOutput
|
var result []*TestOutput
|
||||||
result = append(result, p.validateK8sVersion())
|
result = append(result, p.validateK8sVersion())
|
||||||
result = append(result, p.getRBAC())
|
result = append(result, p.validateRBAC())
|
||||||
result = append(result, p.getAggregatedLayer())
|
result = append(result, p.validateAggregatedLayer())
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateK8sVersion validates the clusters K8s version
|
// validateK8sVersion validates the clusters K8s version
|
||||||
func (p *Kubestr) validateK8sVersion() *TestOutput {
|
func (p *Kubestr) validateK8sVersion() *TestOutput {
|
||||||
testName := "Kubernetes Version Check"
|
testName := "Kubernetes Version Check"
|
||||||
version, err := p.getK8sVersion()
|
version, err := p.validateK8sVersionHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return makeTestOutput(testName, StatusError, err.Error(), nil)
|
return makeTestOutput(testName, StatusError, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
@ -39,14 +40,14 @@ func (p *Kubestr) validateK8sVersion() *TestOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getK8sVersion fetches the k8s vesion
|
// getK8sVersion fetches the k8s vesion
|
||||||
func (p *Kubestr) getK8sVersion() (*version.Info, error) {
|
func (p *Kubestr) validateK8sVersionHelper() (*version.Info, error) {
|
||||||
version, err := p.cli.Discovery().ServerVersion()
|
version, err := p.cli.Discovery().ServerVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
majorStr := version.Major
|
majorStr := version.Major
|
||||||
if string(majorStr[len(majorStr)-1]) == "+" {
|
if len(majorStr) > 1 && string(majorStr[len(majorStr)-1]) == "+" {
|
||||||
majorStr = majorStr[:len(majorStr)-1]
|
majorStr = majorStr[:len(majorStr)-1]
|
||||||
}
|
}
|
||||||
major, err := strconv.Atoi(majorStr)
|
major, err := strconv.Atoi(majorStr)
|
||||||
|
@ -55,7 +56,7 @@ func (p *Kubestr) getK8sVersion() (*version.Info, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
minorStr := version.Minor
|
minorStr := version.Minor
|
||||||
if string(minorStr[len(minorStr)-1]) == "+" {
|
if len(minorStr) > 1 && string(minorStr[len(minorStr)-1]) == "+" {
|
||||||
minorStr = minorStr[:len(minorStr)-1]
|
minorStr = minorStr[:len(minorStr)-1]
|
||||||
}
|
}
|
||||||
minor, err := strconv.Atoi(minorStr)
|
minor, err := strconv.Atoi(minorStr)
|
||||||
|
@ -69,33 +70,49 @@ func (p *Kubestr) getK8sVersion() (*version.Info, error) {
|
||||||
return version, nil
|
return version, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRBAC runs the Rbac test
|
func (p *Kubestr) validateRBAC() *TestOutput {
|
||||||
func (p *Kubestr) getRBAC() *TestOutput {
|
|
||||||
testName := "RBAC Check"
|
testName := "RBAC Check"
|
||||||
//fmt.Println(" Checking if Kubernetes RBAC is enabled:")
|
//fmt.Println(" Checking if Kubernetes RBAC is enabled:")
|
||||||
serverGroups, err := p.cli.Discovery().ServerGroups()
|
group, err := p.validateRBACHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return makeTestOutput(testName, StatusError, err.Error(), nil)
|
return makeTestOutput(testName, StatusError, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
return makeTestOutput(testName, StatusOK, "Kubernetes RBAC is enabled", *group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRBAC runs the Rbac test
|
||||||
|
func (p *Kubestr) validateRBACHelper() (*v1.APIGroup, error) {
|
||||||
|
serverGroups, err := p.cli.Discovery().ServerGroups()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for _, group := range serverGroups.Groups {
|
for _, group := range serverGroups.Groups {
|
||||||
if group.Name == RbacGroupName {
|
if group.Name == RbacGroupName {
|
||||||
return makeTestOutput(testName, StatusOK, "Kubernetes RBAC is enabled", group)
|
return &group, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return makeTestOutput(testName, StatusError, "Kubernetes RBAC is not enabled", nil)
|
return nil, fmt.Errorf("Kubernetes RBAC is not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Kubestr) validateAggregatedLayer() *TestOutput {
|
||||||
|
testName := "Aggregated Layer Check"
|
||||||
|
resourceList, err := p.validateAggregatedLayerHelper()
|
||||||
|
if err != nil {
|
||||||
|
makeTestOutput(testName, StatusError, err.Error(), nil)
|
||||||
|
}
|
||||||
|
return makeTestOutput(testName, StatusOK, "The Kubernetes Aggregated Layer is enabled", resourceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAggregatedLayer checks the aggregated API layer
|
// getAggregatedLayer checks the aggregated API layer
|
||||||
func (p *Kubestr) getAggregatedLayer() *TestOutput {
|
func (p *Kubestr) validateAggregatedLayerHelper() (*v1.APIResourceList, error) {
|
||||||
testName := "Aggregated Layer Check"
|
|
||||||
_, serverResources, err := p.cli.Discovery().ServerGroupsAndResources()
|
_, serverResources, err := p.cli.Discovery().ServerGroupsAndResources()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return makeTestOutput(testName, StatusError, err.Error(), nil)
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, resourceList := range serverResources {
|
for _, resourceList := range serverResources {
|
||||||
if resourceList.GroupVersion == "apiregistration.k8s.io/v1" || resourceList.GroupVersion == "apiregistration.k8s.io/v1beta1" {
|
if resourceList.GroupVersion == "apiregistration.k8s.io/v1" || resourceList.GroupVersion == "apiregistration.k8s.io/v1beta1" {
|
||||||
return makeTestOutput(testName, StatusOK, "The Kubernetes Aggregated Layer is enabled", resourceList)
|
return resourceList, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return makeTestOutput(testName, StatusError, "Can not detect the Aggregated Layer. Is it enabled?", nil)
|
return nil, fmt.Errorf("Can not detect the Aggregated Layer. Is it enabled?")
|
||||||
}
|
}
|
||||||
|
|
160
pkg/kubestr/kubernetes_checks_test.go
Normal file
160
pkg/kubestr/kubernetes_checks_test.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package kubestr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
version "k8s.io/apimachinery/pkg/version"
|
||||||
|
discoveryfake "k8s.io/client-go/discovery/fake"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) { TestingT(t) }
|
||||||
|
|
||||||
|
type K8sChecksTestSuite struct{}
|
||||||
|
|
||||||
|
var _ = Suite(&K8sChecksTestSuite{})
|
||||||
|
|
||||||
|
func (s *K8sChecksTestSuite) TestGetK8sVersion(c *C) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
ver *version.Info
|
||||||
|
checker Checker
|
||||||
|
out *version.Info
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "1", Minor: "17", GitVersion: "v1.17"},
|
||||||
|
checker: IsNil,
|
||||||
|
out: &version.Info{Major: "1", Minor: "17", GitVersion: "v1.17"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "1", Minor: "11", GitVersion: "v1.11"},
|
||||||
|
checker: NotNil,
|
||||||
|
out: &version.Info{Major: "1", Minor: "11", GitVersion: "v1.11"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "1", Minor: "", GitVersion: "v1."},
|
||||||
|
checker: NotNil,
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "", Minor: "11", GitVersion: "v."},
|
||||||
|
checker: NotNil,
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
cli := fake.NewSimpleClientset()
|
||||||
|
cli.Discovery().(*discoveryfake.FakeDiscovery).FakedServerVersion = tc.ver
|
||||||
|
p := &Kubestr{cli: cli}
|
||||||
|
out, err := p.validateK8sVersionHelper()
|
||||||
|
c.Assert(out, DeepEquals, tc.out)
|
||||||
|
c.Check(err, tc.checker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *K8sChecksTestSuite) TestValidateRBAC(c *C) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
resources []*metav1.APIResourceList
|
||||||
|
checker Checker
|
||||||
|
out *v1.APIGroup
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "/////",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checker: NotNil,
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "rbac.authorization.k8s.io/v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checker: IsNil,
|
||||||
|
out: &v1.APIGroup{
|
||||||
|
Name: "rbac.authorization.k8s.io",
|
||||||
|
Versions: []v1.GroupVersionForDiscovery{
|
||||||
|
{GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1"},
|
||||||
|
},
|
||||||
|
PreferredVersion: v1.GroupVersionForDiscovery{GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "notrbac.authorization.k8s.io/v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checker: NotNil,
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
cli := fake.NewSimpleClientset()
|
||||||
|
cli.Discovery().(*discoveryfake.FakeDiscovery).Resources = tc.resources
|
||||||
|
p := &Kubestr{cli: cli}
|
||||||
|
out, err := p.validateRBACHelper()
|
||||||
|
c.Assert(out, DeepEquals, tc.out)
|
||||||
|
c.Check(err, tc.checker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *K8sChecksTestSuite) TestValidateAggregatedLayer(c *C) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
resources []*metav1.APIResourceList
|
||||||
|
checker Checker
|
||||||
|
out *metav1.APIResourceList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "/////",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checker: NotNil,
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "apiregistration.k8s.io/v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checker: IsNil,
|
||||||
|
out: &metav1.APIResourceList{
|
||||||
|
GroupVersion: "apiregistration.k8s.io/v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "apiregistration.k8s.io/v1beta1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checker: IsNil,
|
||||||
|
out: &metav1.APIResourceList{
|
||||||
|
GroupVersion: "apiregistration.k8s.io/v1beta1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "notapiregistration.k8s.io/v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checker: NotNil,
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
cli := fake.NewSimpleClientset()
|
||||||
|
cli.Discovery().(*discoveryfake.FakeDiscovery).Resources = tc.resources
|
||||||
|
p := &Kubestr{cli: cli}
|
||||||
|
out, err := p.validateAggregatedLayerHelper()
|
||||||
|
c.Assert(out, DeepEquals, tc.out)
|
||||||
|
c.Check(err, tc.checker)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import (
|
||||||
type Kubestr struct {
|
type Kubestr struct {
|
||||||
cli kubernetes.Interface
|
cli kubernetes.Interface
|
||||||
dynCli dynamic.Interface
|
dynCli dynamic.Interface
|
||||||
|
sdsfgValidator snapshotDataSourceFG
|
||||||
storageClassList *sv1.StorageClassList
|
storageClassList *sv1.StorageClassList
|
||||||
volumeSnapshotClassList *unstructured.UnstructuredList
|
volumeSnapshotClassList *unstructured.UnstructuredList
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,14 @@ func NewKubestr() (*Kubestr, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Kubestr{cli: cli, dynCli: dynCli}, nil
|
return &Kubestr{
|
||||||
|
cli: cli,
|
||||||
|
dynCli: dynCli,
|
||||||
|
sdsfgValidator: &snapshotDataSourceFGValidator{
|
||||||
|
cli: cli,
|
||||||
|
dynCli: dynCli,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDynCli loads the config and returns a dynamic CLI
|
// getDynCli loads the config and returns a dynamic CLI
|
||||||
|
|
|
@ -15,6 +15,8 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -122,6 +124,8 @@ func (v *Provisioner) Print() {
|
||||||
v.CSIDriver.Print(" ")
|
v.CSIDriver.Print(" ")
|
||||||
case strings.HasPrefix(v.ProvisionerName, "kubernetes.io"):
|
case strings.HasPrefix(v.ProvisionerName, "kubernetes.io"):
|
||||||
fmt.Println(" This is an in tree provisioner.")
|
fmt.Println(" This is an in tree provisioner.")
|
||||||
|
case strings.Contains(v.ProvisionerName, "csi"):
|
||||||
|
fmt.Println(" This might be a CSI Driver. But it is not publicly listed.")
|
||||||
default:
|
default:
|
||||||
fmt.Println(" Unknown driver type.")
|
fmt.Println(" Unknown driver type.")
|
||||||
}
|
}
|
||||||
|
@ -225,7 +229,7 @@ func (p *Kubestr) processProvisioner(ctx context.Context, provisioner string) (*
|
||||||
for _, vsc := range vscs.Items {
|
for _, vsc := range vscs.Items {
|
||||||
if p.getDriverNameFromUVSC(vsc, csiSnapshotGroupVersion.GroupVersion) == provisioner {
|
if p.getDriverNameFromUVSC(vsc, csiSnapshotGroupVersion.GroupVersion) == provisioner {
|
||||||
retProvisioner.VolumeSnapshotClasses = append(retProvisioner.VolumeSnapshotClasses,
|
retProvisioner.VolumeSnapshotClasses = append(retProvisioner.VolumeSnapshotClasses,
|
||||||
p.validateVolumeSnapshotClass(vsc, provisioner, csiSnapshotGroupVersion.GroupVersion))
|
p.validateVolumeSnapshotClass(vsc, csiSnapshotGroupVersion.GroupVersion))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,7 +251,7 @@ func (p *Kubestr) hasCSIDriverObject(ctx context.Context, provisioner string) bo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Kubestr) isK8sVersionCSISnapshotCapable(ctx context.Context) (bool, error) {
|
func (p *Kubestr) isK8sVersionCSISnapshotCapable(ctx context.Context) (bool, error) {
|
||||||
k8sVersion, err := p.getK8sVersion()
|
k8sVersion, err := p.validateK8sVersionHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -260,52 +264,7 @@ func (p *Kubestr) isK8sVersionCSISnapshotCapable(ctx context.Context) (bool, err
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if minor < 17 && k8sVersion.Major == "1" {
|
if minor < 17 && k8sVersion.Major == "1" {
|
||||||
return p.validateVolumeSnapshotDataSourceFeatureGate(ctx)
|
return p.sdsfgValidator.validate(ctx)
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Kubestr) validateVolumeSnapshotDataSourceFeatureGate(ctx context.Context) (bool, error) {
|
|
||||||
ns := getPodNamespace()
|
|
||||||
|
|
||||||
// deletes if exists. If it doesn't exist, this is a noop
|
|
||||||
err := kanvolume.DeletePVC(p.cli, ns, FeatureGateTestPVCName)
|
|
||||||
if err != nil {
|
|
||||||
return false, errors.Wrap(err, "Error deleting VolumeSnapshotDataSource feature-gate validation pvc")
|
|
||||||
}
|
|
||||||
// defer delete
|
|
||||||
defer func() {
|
|
||||||
_ = kanvolume.DeletePVC(p.cli, ns, FeatureGateTestPVCName)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// create PVC
|
|
||||||
snapshotKind := "VolumeSnapshot"
|
|
||||||
snapshotAPIGroup := "snapshot.storage.k8s.io"
|
|
||||||
pvc := &v1.PersistentVolumeClaim{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: FeatureGateTestPVCName,
|
|
||||||
},
|
|
||||||
Spec: v1.PersistentVolumeClaimSpec{
|
|
||||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
|
||||||
DataSource: &v1.TypedLocalObjectReference{
|
|
||||||
APIGroup: &snapshotAPIGroup,
|
|
||||||
Kind: snapshotKind,
|
|
||||||
Name: "fakeSnap",
|
|
||||||
},
|
|
||||||
Resources: v1.ResourceRequirements{
|
|
||||||
Requests: v1.ResourceList{
|
|
||||||
v1.ResourceStorage: resource.MustParse("1Gi"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pvcRes, err := p.cli.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return false, errors.Wrap(err, "Error creating VolumeSnapshotDataSource feature-gate validation pvc")
|
|
||||||
}
|
|
||||||
if pvcRes.Spec.DataSource == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
@ -320,7 +279,7 @@ func (p *Kubestr) validateStorageClass(provisioner string, storageClass sv1.Stor
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateVolumeSnapshotClass validates the VolumeSnapshotClass
|
// validateVolumeSnapshotClass validates the VolumeSnapshotClass
|
||||||
func (p *Kubestr) validateVolumeSnapshotClass(vsc unstructured.Unstructured, provisionerName string, groupVersion string) *VSCInfo {
|
func (p *Kubestr) validateVolumeSnapshotClass(vsc unstructured.Unstructured, groupVersion string) *VSCInfo {
|
||||||
retVSC := &VSCInfo{
|
retVSC := &VSCInfo{
|
||||||
Name: vsc.GetName(),
|
Name: vsc.GetName(),
|
||||||
Raw: vsc,
|
Raw: vsc,
|
||||||
|
@ -414,3 +373,57 @@ func (p *Kubestr) getCSIGroupVersion() *metav1.GroupVersionForDiscovery {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type snapshotDataSourceFG interface {
|
||||||
|
validate(ctx context.Context) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type snapshotDataSourceFGValidator struct {
|
||||||
|
cli kubernetes.Interface
|
||||||
|
dynCli dynamic.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshotDataSourceFGValidator) validate(ctx context.Context) (bool, error) {
|
||||||
|
ns := getPodNamespace()
|
||||||
|
|
||||||
|
// deletes if exists. If it doesn't exist, this is a noop
|
||||||
|
err := kanvolume.DeletePVC(s.cli, ns, FeatureGateTestPVCName)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, "Error deleting VolumeSnapshotDataSource feature-gate validation pvc")
|
||||||
|
}
|
||||||
|
// defer delete
|
||||||
|
defer func() {
|
||||||
|
_ = kanvolume.DeletePVC(s.cli, ns, FeatureGateTestPVCName)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create PVC
|
||||||
|
snapshotKind := "VolumeSnapshot"
|
||||||
|
snapshotAPIGroup := "snapshot.storage.k8s.io"
|
||||||
|
pvc := &v1.PersistentVolumeClaim{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: FeatureGateTestPVCName,
|
||||||
|
},
|
||||||
|
Spec: v1.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||||
|
DataSource: &v1.TypedLocalObjectReference{
|
||||||
|
APIGroup: &snapshotAPIGroup,
|
||||||
|
Kind: snapshotKind,
|
||||||
|
Name: "fakeSnap",
|
||||||
|
},
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: v1.ResourceList{
|
||||||
|
v1.ResourceStorage: resource.MustParse("1Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pvcRes, err := s.cli.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, "Error creating VolumeSnapshotDataSource feature-gate validation pvc")
|
||||||
|
}
|
||||||
|
if pvcRes.Spec.DataSource == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
327
pkg/kubestr/storage_provisioners_test.go
Normal file
327
pkg/kubestr/storage_provisioners_test.go
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
package kubestr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
scv1 "k8s.io/api/storage/v1"
|
||||||
|
"k8s.io/api/storage/v1beta1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
version "k8s.io/apimachinery/pkg/version"
|
||||||
|
discoveryfake "k8s.io/client-go/discovery/fake"
|
||||||
|
fakedynamic "k8s.io/client-go/dynamic/fake"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProvisionerTestSuite struct{}
|
||||||
|
|
||||||
|
var _ = Suite(&ProvisionerTestSuite{})
|
||||||
|
|
||||||
|
func (s *ProvisionerTestSuite) TestHasCSIDriverObject(c *C) {
|
||||||
|
ctx := context.Background()
|
||||||
|
for _, tc := range []struct {
|
||||||
|
cli kubernetes.Interface
|
||||||
|
provisionerName string
|
||||||
|
hasDriver bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cli: fake.NewSimpleClientset(),
|
||||||
|
provisionerName: "provisioner",
|
||||||
|
hasDriver: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cli: fake.NewSimpleClientset(&v1beta1.CSIDriverList{
|
||||||
|
Items: []v1beta1.CSIDriver{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "drivername",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}),
|
||||||
|
provisionerName: "drivername",
|
||||||
|
hasDriver: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
p := &Kubestr{cli: tc.cli}
|
||||||
|
hasDriver := p.hasCSIDriverObject(ctx, tc.provisionerName)
|
||||||
|
c.Assert(hasDriver, Equals, tc.hasDriver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProvisionerTestSuite) TestIsK8sVersionCSISnapshotCapable(c *C) {
|
||||||
|
ctx := context.Background()
|
||||||
|
for _, tc := range []struct {
|
||||||
|
ver *version.Info
|
||||||
|
checker Checker
|
||||||
|
capable bool
|
||||||
|
sdsfg snapshotDataSourceFG
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "1", Minor: "", GitVersion: "v1.17"},
|
||||||
|
checker: NotNil,
|
||||||
|
capable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "1", Minor: "15+", GitVersion: "v1.15+"},
|
||||||
|
checker: NotNil,
|
||||||
|
capable: false,
|
||||||
|
sdsfg: &fakeSDSFGValidator{err: fmt.Errorf("someerror"), cap: false},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "1", Minor: "15+", GitVersion: "v1.15+"},
|
||||||
|
checker: IsNil,
|
||||||
|
capable: true,
|
||||||
|
sdsfg: &fakeSDSFGValidator{err: nil, cap: true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ver: &version.Info{Major: "1", Minor: "17", GitVersion: "v1.17"},
|
||||||
|
checker: IsNil,
|
||||||
|
capable: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
cli := fake.NewSimpleClientset()
|
||||||
|
cli.Discovery().(*discoveryfake.FakeDiscovery).FakedServerVersion = tc.ver
|
||||||
|
p := &Kubestr{cli: cli, sdsfgValidator: tc.sdsfg}
|
||||||
|
cap, err := p.isK8sVersionCSISnapshotCapable(ctx)
|
||||||
|
c.Check(err, tc.checker)
|
||||||
|
c.Assert(cap, Equals, tc.capable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeSDSFGValidator struct {
|
||||||
|
err error
|
||||||
|
cap bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeSDSFGValidator) validate(ctx context.Context) (bool, error) {
|
||||||
|
return f.cap, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProvisionerTestSuite) TestValidateVolumeSnapshotClass(c *C) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
vsc unstructured.Unstructured
|
||||||
|
groupVersion string
|
||||||
|
out *VSCInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "vsc1",
|
||||||
|
},
|
||||||
|
"snapshotter": "something",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupVersion: "snapshot.storage.k8s.io/v1alpha1",
|
||||||
|
out: &VSCInfo{
|
||||||
|
Name: "vsc1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // failure
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "vsc1",
|
||||||
|
},
|
||||||
|
"notsnapshotter": "something",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupVersion: "snapshot.storage.k8s.io/v1alpha1",
|
||||||
|
out: &VSCInfo{
|
||||||
|
Name: "vsc1",
|
||||||
|
StatusList: []Status{
|
||||||
|
makeStatus(StatusError, fmt.Sprintf("VolumeSnapshotClass (%s) missing 'snapshotter' field", "vsc1"), nil),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "vsc1",
|
||||||
|
},
|
||||||
|
"driver": "something",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupVersion: "snapshot.storage.k8s.io/v1beta1",
|
||||||
|
out: &VSCInfo{
|
||||||
|
Name: "vsc1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // failure
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "vsc1",
|
||||||
|
},
|
||||||
|
"notdriver": "something",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupVersion: "snapshot.storage.k8s.io/v1beta1",
|
||||||
|
out: &VSCInfo{
|
||||||
|
Name: "vsc1",
|
||||||
|
StatusList: []Status{
|
||||||
|
makeStatus(StatusError, fmt.Sprintf("VolumeSnapshotClass (%s) missing 'driver' field", "vsc1"), nil),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
p := &Kubestr{}
|
||||||
|
out := p.validateVolumeSnapshotClass(tc.vsc, tc.groupVersion)
|
||||||
|
c.Assert(out.Name, Equals, tc.out.Name)
|
||||||
|
c.Assert(len(out.StatusList), Equals, len(tc.out.StatusList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProvisionerTestSuite) TestLoadStorageClassesAndProvisioners(c *C) {
|
||||||
|
ctx := context.Background()
|
||||||
|
p := &Kubestr{cli: fake.NewSimpleClientset(
|
||||||
|
&scv1.StorageClass{ObjectMeta: metav1.ObjectMeta{Name: "sc1"}, Provisioner: "provisioner1"},
|
||||||
|
&scv1.StorageClass{ObjectMeta: metav1.ObjectMeta{Name: "sc2"}, Provisioner: "provisioner2"},
|
||||||
|
)}
|
||||||
|
scs, err := p.loadStorageClasses(ctx)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(len(scs.Items), Equals, 2)
|
||||||
|
c.Assert(scs, Equals, p.storageClassList)
|
||||||
|
|
||||||
|
// reload has the same
|
||||||
|
p.cli = fake.NewSimpleClientset()
|
||||||
|
scs, err = p.loadStorageClasses(ctx)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(len(scs.Items), Equals, 2)
|
||||||
|
c.Assert(scs, Equals, p.storageClassList)
|
||||||
|
|
||||||
|
// proviosners uses loaded list
|
||||||
|
provisioners, err := p.provisionerList(ctx)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(len(provisioners), Equals, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProvisionerTestSuite) TestLoadVolumeSnaphsotClasses(c *C) {
|
||||||
|
ctx := context.Background()
|
||||||
|
p := &Kubestr{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": "theVSC",
|
||||||
|
},
|
||||||
|
"snapshotter": "somesnapshotter",
|
||||||
|
"deletionPolicy": "Delete",
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
vsc, err := p.loadVolumeSnapshotClasses(ctx, v1alpha1.Version)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(len(vsc.Items), Equals, 1)
|
||||||
|
c.Assert(vsc, Equals, p.volumeSnapshotClassList)
|
||||||
|
|
||||||
|
// reload has the same
|
||||||
|
p.dynCli = fakedynamic.NewSimpleDynamicClient(runtime.NewScheme())
|
||||||
|
vsc, err = p.loadVolumeSnapshotClasses(ctx, v1alpha1.Version)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(len(vsc.Items), Equals, 1)
|
||||||
|
c.Assert(vsc, Equals, p.volumeSnapshotClassList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProvisionerTestSuite) TestGetCSIGroupVersion(c *C) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
resources []*metav1.APIResourceList
|
||||||
|
out *metav1.GroupVersionForDiscovery
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "/////",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "snapshot.storage.k8s.io/v1beta1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupVersion: "snapshot.storage.k8s.io/v1apha1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: &metav1.GroupVersionForDiscovery{
|
||||||
|
GroupVersion: "snapshot.storage.k8s.io/v1beta1",
|
||||||
|
Version: "v1beta1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resources: []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "NOTsnapshot.storage.k8s.io/v1beta1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
cli := fake.NewSimpleClientset()
|
||||||
|
cli.Discovery().(*discoveryfake.FakeDiscovery).Resources = tc.resources
|
||||||
|
p := &Kubestr{cli: cli}
|
||||||
|
out := p.getCSIGroupVersion()
|
||||||
|
c.Assert(out, DeepEquals, tc.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProvisionerTestSuite) TestGetDriverNameFromUVSC(c *C) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
vsc unstructured.Unstructured
|
||||||
|
version string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{ // alpha success
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"snapshotter": "drivername",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
version: "snapshot.storage.k8s.io/v1alpha1",
|
||||||
|
out: "drivername",
|
||||||
|
},
|
||||||
|
{ // key missing
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
version: "snapshot.storage.k8s.io/v1alpha1",
|
||||||
|
out: "",
|
||||||
|
},
|
||||||
|
{ // beta success
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"driver": "drivername",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
version: "snapshot.storage.k8s.io/v1beta1",
|
||||||
|
out: "drivername",
|
||||||
|
},
|
||||||
|
{ // key missing
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
version: "snapshot.storage.k8s.io/v1beta1",
|
||||||
|
out: "",
|
||||||
|
},
|
||||||
|
{ // type conversion
|
||||||
|
vsc: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"driver": int64(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
version: "snapshot.storage.k8s.io/v1beta1",
|
||||||
|
out: "",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
p := &Kubestr{}
|
||||||
|
out := p.getDriverNameFromUVSC(tc.vsc, tc.version)
|
||||||
|
c.Assert(out, Equals, tc.out)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue