2020-10-13 02:06:26 +00:00
// Copyright 2020 Kubestr Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"encoding/json"
"fmt"
2021-10-07 17:55:17 +00:00
"os"
2020-10-13 02:06:26 +00:00
"time"
2023-07-24 21:23:35 +00:00
"github.com/kastenhq/kubestr/pkg/block"
2020-12-07 21:16:53 +00:00
"github.com/kastenhq/kubestr/pkg/csi"
csitypes "github.com/kastenhq/kubestr/pkg/csi/types"
2020-12-01 01:37:56 +00:00
"github.com/kastenhq/kubestr/pkg/fio"
2020-10-13 02:06:26 +00:00
"github.com/kastenhq/kubestr/pkg/kubestr"
"github.com/spf13/cobra"
)
var (
output string
2021-10-07 17:55:17 +00:00
outfile string
2020-10-13 02:06:26 +00:00
rootCmd = & cobra . Command {
Use : "kubestr" ,
Short : "A tool to validate kubernetes storage" ,
Long : ` kubestr is a tool that will scan your k8s cluster
and validate that the storage systems in place as well as run
performance tests . ` ,
2021-10-07 17:55:17 +00:00
SilenceUsage : true ,
2022-08-16 20:11:31 +00:00
Args : cobra . ExactArgs ( 0 ) ,
2021-10-07 17:55:17 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2020-10-13 02:06:26 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Minute )
defer cancel ( )
2021-10-07 17:55:17 +00:00
return Baseline ( ctx , output )
2020-10-13 02:06:26 +00:00
} ,
}
2020-12-15 00:14:53 +00:00
storageClass string
namespace string
containerImage string
2020-12-07 21:16:53 +00:00
fioCheckerSize string
2022-08-16 19:51:19 +00:00
fioNodeSelector map [ string ] string
2020-12-07 21:16:53 +00:00
fioCheckerFilePath string
fioCheckerTestName string
fioCmd = & cobra . Command {
2020-10-29 02:44:35 +00:00
Use : "fio" ,
Short : "Runs an fio test" ,
Long : ` Run an fio test ` ,
2022-08-16 20:11:31 +00:00
Args : cobra . ExactArgs ( 0 ) ,
2021-10-07 17:55:17 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2020-10-29 02:44:35 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Minute )
defer cancel ( )
2022-08-16 19:51:19 +00:00
return Fio ( ctx , output , outfile , storageClass , fioCheckerSize , namespace , fioNodeSelector , fioCheckerTestName , fioCheckerFilePath , containerImage )
2020-12-07 21:16:53 +00:00
} ,
}
csiCheckVolumeSnapshotClass string
csiCheckRunAsUser int64
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" ,
2022-08-16 20:11:31 +00:00
Args : cobra . ExactArgs ( 0 ) ,
2021-10-07 17:55:17 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2020-12-07 21:16:53 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Minute )
defer cancel ( )
2021-10-07 17:55:17 +00:00
return CSICheck ( ctx , output , outfile , namespace , storageClass , csiCheckVolumeSnapshotClass , csiCheckRunAsUser , containerImage , csiCheckCleanup , csiCheckSkipCFSCheck )
2020-10-29 02:44:35 +00:00
} ,
}
2021-10-08 19:53:57 +00:00
pvcBrowseLocalPort int
pvcBrowseCmd = & cobra . Command {
Use : "browse [PVC name]" ,
Short : "Browse the contents of a CSI PVC via file browser" ,
Long : "Browse the contents of a CSI provisioned PVC by cloning the volume and mounting it with a file browser." ,
2022-08-16 20:11:31 +00:00
Args : cobra . ExactArgs ( 1 ) ,
2021-10-08 19:53:57 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return CsiPvcBrowse ( context . Background ( ) , args [ 0 ] ,
namespace ,
csiCheckVolumeSnapshotClass ,
csiCheckRunAsUser ,
pvcBrowseLocalPort ,
)
} ,
}
2023-07-24 21:23:35 +00:00
blockMountRunAsUser int64
blockMountCleanup bool
blockMountCleanupOnly bool
blockMountWaitTimeoutSeconds uint32
blockMountPVCSize string
blockMountCmd = & cobra . Command {
Use : "blockmount" ,
Short : "Checks if a storage class supports block volumes" ,
Long : ` Checks if volumes provisioned by a storage class can be mounted in block mode .
The checker works as follows :
- It dynamically provisions a volume of the given storage class .
- It then launches a pod with the volume mounted as a block device .
- If the pod is successfully created then the test passes .
- If the pod fails or times out then the test fails .
In case of failure , re - run the checker with the "-c=false" flag and examine the
failed PVC and Pod : it may be necessary to adjust the default values used for
the PVC size , the pod wait timeout , etc . Clean up the failed resources by
running the checker with the "--cleanup-only" flag .
` ,
Args : cobra . ExactArgs ( 0 ) ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Minute )
defer cancel ( )
checkerArgs := block . BlockMountCheckerArgs {
StorageClass : storageClass ,
Namespace : namespace ,
Cleanup : blockMountCleanup ,
RunAsUser : blockMountRunAsUser ,
ContainerImage : containerImage ,
K8sObjectReadyTimeout : ( time . Second * time . Duration ( blockMountWaitTimeoutSeconds ) ) ,
PVCSize : blockMountPVCSize ,
}
return BlockMountCheck ( ctx , output , outfile , blockMountCleanupOnly , checkerArgs )
} ,
}
2020-10-13 02:06:26 +00:00
)
func init ( ) {
rootCmd . PersistentFlags ( ) . StringVarP ( & output , "output" , "o" , "" , "Options(json)" )
2021-10-07 17:55:17 +00:00
rootCmd . PersistentFlags ( ) . StringVarP ( & outfile , "outfile" , "e" , "" , "The file where test results will be written" )
2020-10-13 02:06:26 +00:00
2020-10-29 02:44:35 +00:00
rootCmd . AddCommand ( fioCmd )
2020-12-07 21:16:53 +00:00
fioCmd . Flags ( ) . StringVarP ( & storageClass , "storageclass" , "s" , "" , "The name of a Storageclass. (Required)" )
2020-12-01 01:37:56 +00:00
_ = fioCmd . MarkFlagRequired ( "storageclass" )
2021-10-07 17:55:17 +00:00
fioCmd . Flags ( ) . StringVarP ( & fioCheckerSize , "size" , "z" , fio . DefaultPVCSize , "The size of the volume used to run FIO. Note that the FIO job definition is not scaled accordingly." )
2020-12-07 21:16:53 +00:00
fioCmd . Flags ( ) . StringVarP ( & namespace , "namespace" , "n" , fio . DefaultNS , "The namespace used to run FIO." )
2022-08-16 19:51:19 +00:00
fioCmd . Flags ( ) . StringToStringVarP ( & fioNodeSelector , "nodeselector" , "N" , map [ string ] string { } , "Node selector applied to pod." )
2020-12-01 01:37:56 +00:00
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)" )
2020-12-15 00:14:53 +00:00
fioCmd . Flags ( ) . StringVarP ( & containerImage , "image" , "i" , "" , "The container image used to create a pod." )
2020-12-07 21:16:53 +00:00
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." )
2020-12-15 00:14:53 +00:00
csiCheckCmd . Flags ( ) . StringVarP ( & containerImage , "image" , "i" , "" , "The container image used to create a pod." )
2020-12-07 21:16:53 +00:00
csiCheckCmd . Flags ( ) . BoolVarP ( & csiCheckCleanup , "cleanup" , "c" , true , "Clean up the objects created by tool" )
2023-07-24 21:23:35 +00:00
csiCheckCmd . Flags ( ) . Int64VarP ( & csiCheckRunAsUser , "runAsUser" , "u" , 0 , "Runs the CSI check pod with the specified user ID (int)" )
2020-12-07 21:16:53 +00:00
csiCheckCmd . Flags ( ) . BoolVarP ( & csiCheckSkipCFSCheck , "skipCFScheck" , "k" , false , "Use this flag to skip validating the ability to clone a snapshot." )
2021-10-08 19:53:57 +00:00
rootCmd . AddCommand ( pvcBrowseCmd )
pvcBrowseCmd . Flags ( ) . StringVarP ( & csiCheckVolumeSnapshotClass , "volumesnapshotclass" , "v" , "" , "The name of a VolumeSnapshotClass. (Required)" )
_ = pvcBrowseCmd . MarkFlagRequired ( "volumesnapshotclass" )
pvcBrowseCmd . Flags ( ) . StringVarP ( & namespace , "namespace" , "n" , fio . DefaultNS , "The namespace of the PersistentVolumeClaim." )
pvcBrowseCmd . Flags ( ) . Int64VarP ( & csiCheckRunAsUser , "runAsUser" , "u" , 0 , "Runs the inspector pod as a user (int)" )
pvcBrowseCmd . Flags ( ) . IntVarP ( & pvcBrowseLocalPort , "localport" , "l" , 8080 , "The local port to expose the inspector" )
2023-07-24 21:23:35 +00:00
rootCmd . AddCommand ( blockMountCmd )
blockMountCmd . Flags ( ) . StringVarP ( & storageClass , "storageclass" , "s" , "" , "The name of a Storageclass. (Required)" )
_ = blockMountCmd . MarkFlagRequired ( "storageclass" )
blockMountCmd . Flags ( ) . StringVarP ( & namespace , "namespace" , "n" , fio . DefaultNS , "The namespace used to run the check." )
blockMountCmd . Flags ( ) . StringVarP ( & containerImage , "image" , "i" , "" , "The container image used to create a pod." )
blockMountCmd . Flags ( ) . BoolVarP ( & blockMountCleanup , "cleanup" , "c" , true , "Clean up the objects created by the check." )
blockMountCmd . Flags ( ) . BoolVarP ( & blockMountCleanupOnly , "cleanup-only" , "" , false , "Do not run the checker, but just clean up resources left from a previous invocation." )
blockMountCmd . Flags ( ) . Int64VarP ( & blockMountRunAsUser , "runAsUser" , "u" , 0 , "Runs the block mount check pod with the specified user ID (int)" )
blockMountCmd . Flags ( ) . Uint32VarP ( & blockMountWaitTimeoutSeconds , "wait-timeout" , "w" , 60 , "Max time in seconds to wait for the check pod to become ready" )
blockMountCmd . Flags ( ) . StringVarP ( & blockMountPVCSize , "pvc-size" , "" , "1Gi" , "The size of the provisioned PVC." )
2020-10-13 02:06:26 +00:00
}
// Execute executes the main command
func Execute ( ) error {
return rootCmd . Execute ( )
}
// Baseline executes the baseline check
2021-10-07 17:55:17 +00:00
func Baseline ( ctx context . Context , output string ) error {
2020-10-13 02:06:26 +00:00
p , err := kubestr . NewKubestr ( )
if err != nil {
fmt . Println ( err . Error ( ) )
2021-10-07 17:55:17 +00:00
return err
2020-10-13 02:06:26 +00:00
}
2020-10-16 01:09:13 +00:00
fmt . Print ( kubestr . Logo )
2020-10-13 02:06:26 +00:00
result := p . KubernetesChecks ( )
2021-10-07 17:55:17 +00:00
if PrintAndJsonOutput ( result , output , outfile ) {
return err
2020-10-13 02:06:26 +00:00
}
2021-10-07 17:55:17 +00:00
2020-10-13 02:06:26 +00:00
for _ , retval := range result {
retval . Print ( )
fmt . Println ( )
2020-10-16 01:09:13 +00:00
time . Sleep ( 500 * time . Millisecond )
2020-10-13 02:06:26 +00:00
}
provisionerList , err := p . ValidateProvisioners ( ctx )
if err != nil {
fmt . Println ( err . Error ( ) )
2021-10-07 17:55:17 +00:00
return err
2020-10-13 02:06:26 +00:00
}
2021-10-07 17:55:17 +00:00
2020-10-14 19:09:00 +00:00
fmt . Println ( "Available Storage Provisioners:" )
fmt . Println ( )
2020-10-29 02:44:35 +00:00
time . Sleep ( 500 * time . Millisecond ) // Added to introduce lag.
2020-10-13 02:06:26 +00:00
for _ , provisioner := range provisionerList {
provisioner . Print ( )
fmt . Println ( )
2020-10-16 01:09:13 +00:00
time . Sleep ( 500 * time . Millisecond )
2020-10-13 02:06:26 +00:00
}
2021-10-07 17:55:17 +00:00
return err
}
// PrintAndJsonOutput Print JSON output to stdout and to file if arguments say so
// Returns whether we have generated output or JSON
func PrintAndJsonOutput ( result [ ] * kubestr . TestOutput , output string , outfile string ) bool {
if output == "json" {
jsonRes , _ := json . MarshalIndent ( result , "" , " " )
if len ( outfile ) > 0 {
err := os . WriteFile ( outfile , jsonRes , 0666 )
if err != nil {
fmt . Println ( "Error writing output:" , err . Error ( ) )
os . Exit ( 2 )
}
} else {
fmt . Println ( string ( jsonRes ) )
}
return true
}
return false
2020-10-13 02:06:26 +00:00
}
2020-10-29 02:44:35 +00:00
// Fio executes the FIO test.
2022-08-16 19:51:19 +00:00
func Fio ( ctx context . Context , output , outfile , storageclass , size , namespace string , nodeSelector map [ string ] string , jobName , fioFilePath string , containerImage string ) error {
2020-12-07 21:16:53 +00:00
cli , err := kubestr . LoadKubeCli ( )
2020-10-29 02:44:35 +00:00
if err != nil {
fmt . Println ( err . Error ( ) )
2021-10-07 17:55:17 +00:00
return err
2020-10-29 02:44:35 +00:00
}
2020-12-07 21:16:53 +00:00
fioRunner := & fio . FIOrunner {
Cli : cli ,
}
2020-12-01 01:37:56 +00:00
testName := "FIO test results"
var result * kubestr . TestOutput
2021-10-07 17:55:17 +00:00
fioResult , err := fioRunner . RunFio ( ctx , & fio . RunFIOArgs {
2020-12-01 01:37:56 +00:00
StorageClass : storageclass ,
Size : size ,
Namespace : namespace ,
2022-08-16 19:51:19 +00:00
NodeSelector : nodeSelector ,
2020-12-01 01:37:56 +00:00
FIOJobName : jobName ,
FIOJobFilepath : fioFilePath ,
2020-12-15 00:14:53 +00:00
Image : containerImage ,
2021-10-07 17:55:17 +00:00
} )
if err != nil {
2020-12-01 01:37:56 +00:00
result = kubestr . MakeTestOutput ( testName , kubestr . StatusError , err . Error ( ) , fioResult )
} else {
2021-02-01 20:45:57 +00:00
result = kubestr . MakeTestOutput ( testName , kubestr . StatusOK , fmt . Sprintf ( "\n%s" , fioResult . Result . Print ( ) ) , fioResult )
2020-12-01 01:37:56 +00:00
}
2021-10-07 17:55:17 +00:00
var wrappedResult = [ ] * kubestr . TestOutput { result }
if ! PrintAndJsonOutput ( wrappedResult , output , outfile ) {
result . Print ( )
2020-10-29 02:44:35 +00:00
}
2021-10-07 17:55:17 +00:00
return err
2020-10-29 02:44:35 +00:00
}
2020-12-07 21:16:53 +00:00
2021-10-07 17:55:17 +00:00
func CSICheck ( ctx context . Context , output , outfile ,
2020-12-07 21:16:53 +00:00
namespace string ,
storageclass string ,
volumesnapshotclass string ,
runAsUser int64 ,
containerImage string ,
cleanup bool ,
skipCFScheck bool ,
2021-10-07 17:55:17 +00:00
) error {
2020-12-07 21:16:53 +00:00
testName := "CSI checker test"
kubecli , err := kubestr . LoadKubeCli ( )
if err != nil {
2021-10-08 19:53:57 +00:00
fmt . Printf ( "Failed to load kubeCli (%s)" , err . Error ( ) )
2021-10-07 17:55:17 +00:00
return err
2020-12-07 21:16:53 +00:00
}
dyncli , err := kubestr . LoadDynCli ( )
if err != nil {
2021-10-08 19:53:57 +00:00
fmt . Printf ( "Failed to load dynCli (%s)" , err . Error ( ) )
2021-10-07 17:55:17 +00:00
return err
2020-12-07 21:16:53 +00:00
}
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 )
}
2021-10-07 17:55:17 +00:00
var wrappedResult = [ ] * kubestr . TestOutput { result }
if ! PrintAndJsonOutput ( wrappedResult , output , outfile ) {
result . Print ( )
2020-12-07 21:16:53 +00:00
}
2021-10-07 17:55:17 +00:00
return err
2020-12-07 21:16:53 +00:00
}
2021-10-08 19:53:57 +00:00
func CsiPvcBrowse ( ctx context . Context ,
pvcName string ,
namespace string ,
volumeSnapshotClass string ,
runAsUser int64 ,
localPort int ,
) error {
kubecli , err := kubestr . LoadKubeCli ( )
if err != nil {
fmt . Printf ( "Failed to load kubeCli (%s)" , err . Error ( ) )
return err
}
dyncli , err := kubestr . LoadDynCli ( )
if err != nil {
fmt . Printf ( "Failed to load dynCli (%s)" , err . Error ( ) )
return err
}
browseRunner := & csi . PVCBrowseRunner {
KubeCli : kubecli ,
DynCli : dyncli ,
}
err = browseRunner . RunPVCBrowse ( ctx , & csitypes . PVCBrowseArgs {
PVCName : pvcName ,
Namespace : namespace ,
VolumeSnapshotClass : volumeSnapshotClass ,
RunAsUser : runAsUser ,
LocalPort : localPort ,
} )
if err != nil {
fmt . Printf ( "Failed to run PVC browser (%s)\n" , err . Error ( ) )
}
return err
}
2023-07-24 21:23:35 +00:00
func BlockMountCheck ( ctx context . Context , output , outfile string , cleanupOnly bool , checkerArgs block . BlockMountCheckerArgs ) error {
kubecli , err := kubestr . LoadKubeCli ( )
if err != nil {
fmt . Printf ( "Failed to load kubeCli (%s)" , err . Error ( ) )
return err
}
checkerArgs . KubeCli = kubecli
dyncli , err := kubestr . LoadDynCli ( )
if err != nil {
fmt . Printf ( "Failed to load dynCli (%s)" , err . Error ( ) )
return err
}
checkerArgs . DynCli = dyncli
blockMountTester , err := block . NewBlockMountChecker ( checkerArgs )
if err != nil {
fmt . Printf ( "Failed to initialize BlockMounter (%s)" , err . Error ( ) )
return err
}
if cleanupOnly {
blockMountTester . Cleanup ( )
return nil
}
var (
testName = "Block VolumeMode test"
result * kubestr . TestOutput
)
mountResult , err := blockMountTester . Mount ( ctx )
if err != nil {
if ! checkerArgs . Cleanup {
fmt . Printf ( "Warning: Resources may not have been released. Rerun with the additional --cleanup-only flag.\n" )
}
result = kubestr . MakeTestOutput ( testName , kubestr . StatusError , fmt . Sprintf ( "StorageClass (%s) does not appear to support Block VolumeMode" , checkerArgs . StorageClass ) , mountResult )
} else {
result = kubestr . MakeTestOutput ( testName , kubestr . StatusOK , fmt . Sprintf ( "StorageClass (%s) supports Block VolumeMode" , checkerArgs . StorageClass ) , mountResult )
}
var wrappedResult = [ ] * kubestr . TestOutput { result }
if ! PrintAndJsonOutput ( wrappedResult , output , outfile ) {
result . Print ( )
}
return err
}