forked from rook/rook
1050 lines
34 KiB
Go
1050 lines
34 KiB
Go
/*
|
|
Copyright 2016 The Rook Authors. All rights reserved.
|
|
|
|
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 utils
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coreos/pkg/capnslog"
|
|
rookalpha "github.com/rook/rook/pkg/apis/rook.io/v1alpha1"
|
|
rookclient "github.com/rook/rook/pkg/client/clientset/versioned"
|
|
"github.com/rook/rook/pkg/daemon/agent/flexvolume/attachment"
|
|
"github.com/rook/rook/pkg/util/exec"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/kubernetes/pkg/util/version"
|
|
)
|
|
|
|
//K8sHelper is a helper for common kubectl commads
|
|
type K8sHelper struct {
|
|
executor *exec.CommandExecutor
|
|
Clientset *kubernetes.Clientset
|
|
RookClientset *rookclient.Clientset
|
|
RunningInCluster bool
|
|
T func() *testing.T
|
|
}
|
|
|
|
const (
|
|
//RetryLoop params for tests.
|
|
RetryLoop = 30
|
|
//RetryInterval param for test - wait time while in RetryLoop
|
|
RetryInterval = 5
|
|
)
|
|
|
|
//CreateK8sHelper creates a instance of k8sHelper
|
|
func CreateK8sHelper(t func() *testing.T) (*K8sHelper, error) {
|
|
executor := &exec.CommandExecutor{}
|
|
config, err := getKubeConfig(executor)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get kube client. %+v", err)
|
|
}
|
|
clientset, err := kubernetes.NewForConfig(config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get clientset. %+v", err)
|
|
}
|
|
rookClientset, err := rookclient.NewForConfig(config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get rook clientset. %+v", err)
|
|
}
|
|
|
|
h := &K8sHelper{executor: executor, Clientset: clientset, RookClientset: rookClientset, T: t}
|
|
if strings.Index(config.Host, "//10.") != -1 {
|
|
h.RunningInCluster = true
|
|
}
|
|
return h, err
|
|
}
|
|
|
|
var k8slogger = capnslog.NewPackageLogger("github.com/rook/rook", "utils")
|
|
|
|
//GetK8sServerVersion returns k8s server version under test
|
|
func (k8sh *K8sHelper) GetK8sServerVersion() string {
|
|
versionInfo, err := k8sh.Clientset.ServerVersion()
|
|
require.Nil(k8sh.T(), err)
|
|
return versionInfo.GitVersion
|
|
}
|
|
|
|
func (k8sh *K8sHelper) VersionAtLeast(minVersion string) bool {
|
|
v := version.MustParseSemantic(k8sh.GetK8sServerVersion())
|
|
return v.AtLeast(version.MustParseSemantic(minVersion))
|
|
}
|
|
|
|
//Kubectl is wrapper for executing kubectl commands
|
|
func (k8sh *K8sHelper) Kubectl(args ...string) (string, error) {
|
|
result, err := k8sh.executor.ExecuteCommandWithOutput(false, "", "kubectl", args...)
|
|
if err != nil {
|
|
k8slogger.Errorf("Errors Encountered while executing kubectl command : %v", err)
|
|
return "", fmt.Errorf("Failed to run kubectl commands on args %v : %v", args, err)
|
|
|
|
}
|
|
return result, nil
|
|
|
|
}
|
|
|
|
//KubectlWithStdin is wrapper for executing kubectl commands in stdin
|
|
func (k8sh *K8sHelper) KubectlWithStdin(stdin string, args ...string) (string, error) {
|
|
|
|
cmdStruct := CommandArgs{Command: "kubectl", PipeToStdIn: stdin, CmdArgs: args}
|
|
|
|
cmdOut := ExecuteCommand(cmdStruct)
|
|
|
|
if cmdOut.ExitCode != 0 {
|
|
k8slogger.Errorf("Errors Encountered while executing kubectl command : %v", cmdOut.Err.Error())
|
|
if strings.Index(cmdOut.Err.Error(), "(NotFound)") != -1 {
|
|
return cmdOut.StdErr, errors.NewNotFound(schema.GroupResource{}, "")
|
|
}
|
|
return cmdOut.StdErr, fmt.Errorf("Failed to run kubectl commands on args %v : %v", args, cmdOut.StdErr)
|
|
}
|
|
if cmdOut.StdOut == "" {
|
|
return cmdOut.StdErr, nil
|
|
}
|
|
|
|
return cmdOut.StdOut, nil
|
|
|
|
}
|
|
|
|
func getKubeConfig(executor exec.Executor) (*rest.Config, error) {
|
|
context, err := executor.ExecuteCommandWithOutput(false, "", "kubectl", "config", "view", "-o", "json")
|
|
if err != nil {
|
|
k8slogger.Errorf("Errors Encountered while executing kubectl command : %v", err)
|
|
}
|
|
|
|
// Parse the kubectl context to get the settings for client connections
|
|
var kc kubectlContext
|
|
if err := json.Unmarshal([]byte(context), &kc); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal kubectl config: %+v", err)
|
|
}
|
|
|
|
// find the current context
|
|
var currentContext kContext
|
|
found := false
|
|
for _, c := range kc.Contexts {
|
|
if kc.Current == c.Name {
|
|
currentContext = c
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("failed to find current context %s in %+v", kc.Current, kc.Contexts)
|
|
}
|
|
|
|
// find the current cluster
|
|
var currentCluster kclusterContext
|
|
found = false
|
|
for _, c := range kc.Clusters {
|
|
if currentContext.Cluster.Cluster == c.Name {
|
|
currentCluster = c
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("failed to find cluster %s in %+v", kc.Current, kc.Clusters)
|
|
}
|
|
config := &rest.Config{Host: currentCluster.Cluster.Server}
|
|
|
|
if currentContext.Cluster.User == "" {
|
|
config.Insecure = true
|
|
} else {
|
|
config.Insecure = false
|
|
|
|
// find the current user
|
|
var currentUser kuserContext
|
|
found = false
|
|
for _, u := range kc.Users {
|
|
if currentContext.Cluster.User == u.Name {
|
|
currentUser = u
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("failed to find kube user %s in %+v", kc.Current, kc.Users)
|
|
}
|
|
|
|
config.TLSClientConfig = rest.TLSClientConfig{
|
|
CAFile: currentCluster.Cluster.CertAuthority,
|
|
KeyFile: currentUser.Cluster.ClientKey,
|
|
CertFile: currentUser.Cluster.ClientCert,
|
|
}
|
|
//set Insecure to true if cert information is missing
|
|
if currentUser.Cluster.ClientCert == "" {
|
|
config.Insecure = true
|
|
}
|
|
}
|
|
|
|
logger.Infof("Loaded kubectl context %s at %s. secure=%t",
|
|
currentCluster.Name, config.Host, !config.Insecure)
|
|
return config, nil
|
|
}
|
|
|
|
type kubectlContext struct {
|
|
Contexts []kContext `json:"contexts"`
|
|
Users []kuserContext `json:"users"`
|
|
Clusters []kclusterContext `json:"clusters"`
|
|
Current string `json:"current-context"`
|
|
}
|
|
type kContext struct {
|
|
Name string `json:"name"`
|
|
Cluster struct {
|
|
Cluster string `json:"cluster"`
|
|
User string `json:"user"`
|
|
} `json:"context"`
|
|
}
|
|
type kclusterContext struct {
|
|
Name string `json:"name"`
|
|
Cluster struct {
|
|
Server string `json:"server"`
|
|
Insecure bool `json:"insecure-skip-tls-verify"`
|
|
CertAuthority string `json:"certificate-authority"`
|
|
} `json:"cluster"`
|
|
}
|
|
type kuserContext struct {
|
|
Name string `json:"name"`
|
|
Cluster struct {
|
|
ClientCert string `json:"client-certificate"`
|
|
ClientKey string `json:"client-key"`
|
|
} `json:"user"`
|
|
}
|
|
|
|
//ResourceOperationFromTemplate performs a kubectl action from a template file after replacing its context
|
|
func (k8sh *K8sHelper) ResourceOperationFromTemplate(action string, podDefinition string, config map[string]string) (string, error) {
|
|
|
|
t := template.New("testTemplate")
|
|
t, err := t.Parse(podDefinition)
|
|
if err != nil {
|
|
return err.Error(), err
|
|
}
|
|
var tpl bytes.Buffer
|
|
|
|
if err := t.Execute(&tpl, config); err != nil {
|
|
return err.Error(), err
|
|
}
|
|
|
|
podDef := tpl.String()
|
|
|
|
args := []string{action, "-f", "-"}
|
|
result, err := k8sh.KubectlWithStdin(podDef, args...)
|
|
if err == nil {
|
|
return result, nil
|
|
}
|
|
logger.Errorf("Failed to execute kubectl %v %v -- %v", args, podDef, err)
|
|
return "", fmt.Errorf("Could Not create resource in args : %v %v-- %v", args, podDef, err)
|
|
|
|
}
|
|
|
|
//ResourceOperation performs a kubectl action on a pod definition
|
|
func (k8sh *K8sHelper) ResourceOperation(action string, podDefiniton string) (string, error) {
|
|
|
|
args := []string{action, "-f", "-"}
|
|
result, err := k8sh.KubectlWithStdin(podDefiniton, args...)
|
|
if err == nil {
|
|
return result, nil
|
|
}
|
|
logger.Errorf("Failed to execute kubectl %v -- %v", args, err)
|
|
return "", fmt.Errorf("Could Not create resource in args : %v -- %v", args, err)
|
|
|
|
}
|
|
|
|
//DeleteResource performs a kubectl delete on give args
|
|
func (k8sh *K8sHelper) DeleteResource(args []string) (string, error) {
|
|
args = append([]string{"delete"}, args...)
|
|
result, err := k8sh.Kubectl(args...)
|
|
if err == nil {
|
|
return result, nil
|
|
}
|
|
return "", fmt.Errorf("Could Not delete resource in k8s -- %v", err)
|
|
|
|
}
|
|
|
|
//GetResource performs a kubectl get on give args
|
|
func (k8sh *K8sHelper) GetResource(args ...string) (string, error) {
|
|
args = append([]string{"get"}, args...)
|
|
result, err := k8sh.Kubectl(args...)
|
|
if err == nil {
|
|
return result, nil
|
|
}
|
|
return "", fmt.Errorf("Could Not get resource in k8s -- %v", err)
|
|
|
|
}
|
|
|
|
//GetMonitorServices returns all ceph mon pod names
|
|
func (k8sh *K8sHelper) GetMonitorServices(namespace string) (map[string]string, error) {
|
|
listOpts := metav1.ListOptions{LabelSelector: "app=rook-ceph-mon"}
|
|
|
|
podList, err := k8sh.Clientset.CoreV1().Services(namespace).List(listOpts)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get rook monitor pods in namespace %s, err: %v", namespace, err)
|
|
return nil, fmt.Errorf("Cannot get rook monitor pods in namespace %s, err: %v", namespace, err)
|
|
}
|
|
mons := []string{}
|
|
for _, svc := range podList.Items {
|
|
mons = append(mons, svc.Spec.ClusterIP)
|
|
|
|
}
|
|
if len(mons) != 3 {
|
|
return nil, fmt.Errorf("Unexpected monitors: %+v", mons)
|
|
}
|
|
|
|
return map[string]string{
|
|
"mon0": fmt.Sprintf("%s:6790", mons[0]),
|
|
"mon1": fmt.Sprintf("%s:6790", mons[1]),
|
|
"mon2": fmt.Sprintf("%s:6790", mons[2]),
|
|
}, nil
|
|
}
|
|
|
|
//IsPodWithLabelRunning returns true if a Pod is running status or goes to Running status within 90s else returns false
|
|
func (k8sh *K8sHelper) IsPodWithLabelRunning(label string, namespace string) bool {
|
|
options := metav1.ListOptions{LabelSelector: label}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(options)
|
|
if err != nil {
|
|
logger.Errorf("failed to find pod with label %s. %+v", label, err)
|
|
return false
|
|
}
|
|
|
|
if len(pods.Items) > 0 {
|
|
for _, pod := range pods.Items {
|
|
if pod.Status.Phase == "Running" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
logger.Infof("waiting for pod with label %s in namespace %s to be running", label, namespace)
|
|
|
|
}
|
|
logger.Infof("Giving up waiting for pod with label %s in namespace %s to be running", label, namespace)
|
|
return false
|
|
}
|
|
|
|
//WaitUntilPodWithLabelDeleted returns true if a Pod is deleted within 90s else returns false
|
|
func (k8sh *K8sHelper) WaitUntilPodWithLabelDeleted(label string, namespace string) bool {
|
|
options := metav1.ListOptions{LabelSelector: label}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(options)
|
|
if errors.IsNotFound(err) {
|
|
logger.Infof("error Found err %v", err)
|
|
return true
|
|
}
|
|
if len(pods.Items) == 0 {
|
|
return true
|
|
}
|
|
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
logger.Infof("waiting for pod with label %s in namespace %s to be deleted", label, namespace)
|
|
|
|
}
|
|
logger.Infof("Giving up waiting for pod with label %s in namespace %s to be deleted", label, namespace)
|
|
return false
|
|
}
|
|
|
|
//IsPodRunning returns true if a Pod is running status or goes to Running status within 90s else returns false
|
|
func (k8sh *K8sHelper) IsPodRunning(name string, namespace string) bool {
|
|
getOpts := metav1.GetOptions{}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
pod, err := k8sh.Clientset.CoreV1().Pods(namespace).Get(name, getOpts)
|
|
if err == nil {
|
|
if pod.Status.Phase == "Running" {
|
|
return true
|
|
}
|
|
}
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
logger.Infof("waiting for pod %s in namespace %s to be running", name, namespace)
|
|
|
|
}
|
|
logger.Infof("Giving up waiting for pod %s in namespace %s to be running", name, namespace)
|
|
return false
|
|
}
|
|
|
|
//IsPodTerminated returns true if a Pod is terminated status or goes to Terminated status
|
|
// within 90s else returns false\
|
|
func (k8sh *K8sHelper) IsPodTerminated(name string, namespace string) bool {
|
|
getOpts := metav1.GetOptions{}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
pod, err := k8sh.Clientset.CoreV1().Pods(namespace).Get(name, getOpts)
|
|
if err != nil {
|
|
k8slogger.Infof("Pod %s in namespace %s terminated ", name, namespace)
|
|
return true
|
|
}
|
|
k8slogger.Infof("waiting for Pod %s in namespace %s to terminated, status : %v", name, namespace, pod.Status.Phase)
|
|
time.Sleep(RetryInterval * time.Second)
|
|
inc++
|
|
|
|
}
|
|
k8slogger.Infof("Pod %s in namespace %s did not terminated", name, namespace)
|
|
return false
|
|
}
|
|
|
|
//IsServiceUp returns true if a service is up or comes up within 150s, else returns false
|
|
func (k8sh *K8sHelper) IsServiceUp(name string, namespace string) bool {
|
|
getOpts := metav1.GetOptions{}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
_, err := k8sh.Clientset.CoreV1().Services(namespace).Get(name, getOpts)
|
|
if err == nil {
|
|
k8slogger.Infof("Service: %s in namespace: %s is up", name, namespace)
|
|
return true
|
|
}
|
|
k8slogger.Infof("waiting for Service %s in namespace %s ", name, namespace)
|
|
time.Sleep(RetryInterval * time.Second)
|
|
inc++
|
|
|
|
}
|
|
k8slogger.Infof("Giving up waiting for service: %s in namespace %s ", name, namespace)
|
|
return false
|
|
}
|
|
|
|
//GetService returns output from "kubectl get svc $NAME" command
|
|
func (k8sh *K8sHelper) GetService(servicename string, namespace string) (*v1.Service, error) {
|
|
getOpts := metav1.GetOptions{}
|
|
result, err := k8sh.Clientset.CoreV1().Services(namespace).Get(servicename, getOpts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Cannot find service %s in namespace %s, err-- %v", servicename, namespace, err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
//IsThirdPartyResourcePresent returns true if Third party resource is present
|
|
func (k8sh *K8sHelper) IsThirdPartyResourcePresent(tprname string) bool {
|
|
args := []string{"get", "thirdpartyresources", tprname}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
_, err := k8sh.Kubectl(args...)
|
|
if err == nil {
|
|
k8slogger.Infof("Found the thirdparty resource: " + tprname)
|
|
return true
|
|
}
|
|
time.Sleep(RetryInterval * time.Second)
|
|
inc++
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
//IsCRDPresent returns true if custom resource definition is present
|
|
func (k8sh *K8sHelper) IsCRDPresent(crdName string) bool {
|
|
|
|
cmdArgs := []string{"get", "crd", crdName}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
_, err := k8sh.Kubectl(cmdArgs...)
|
|
if err == nil {
|
|
k8slogger.Infof("Found the CRD resource: " + crdName)
|
|
return true
|
|
}
|
|
time.Sleep(RetryInterval * time.Second)
|
|
inc++
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetVolumeAttachmentResourceName gets the VolumeAttachment object name from the PVC
|
|
func (k8sh *K8sHelper) GetVolumeAttachmentResourceName(namespace, pvcName string) (string, error) {
|
|
|
|
getOpts := metav1.GetOptions{}
|
|
pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(pvcName, getOpts)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return pvc.Spec.VolumeName, nil
|
|
}
|
|
|
|
//IsVolumeAttachmentResourcePresent returns true if VolumeAttachment resource is present
|
|
func (k8sh *K8sHelper) IsVolumeAttachmentResourcePresent(namespace, volumeAttachmentName string) bool {
|
|
err := k8sh.waitForVolumeAttachment(namespace, volumeAttachmentName, true)
|
|
if err != nil {
|
|
k8slogger.Error(err.Error())
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
//IsVolumeAttachmentResourceAbsent returns true if the VolumeAttachment resource is deleted/absent within 90s else returns false
|
|
func (k8sh *K8sHelper) IsVolumeAttachmentResourceAbsent(namespace, volumeAttachmentName string) bool {
|
|
|
|
err := k8sh.waitForVolumeAttachment(namespace, volumeAttachmentName, false)
|
|
if err != nil {
|
|
k8slogger.Error(err.Error())
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (k8sh *K8sHelper) waitForVolumeAttachment(namespace, volumeAttachmentName string, exist bool) error {
|
|
|
|
action := "exist"
|
|
if !exist {
|
|
action = "not " + action
|
|
}
|
|
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
isExist, err := k8sh.isVolumeAttachmentExist(namespace, volumeAttachmentName)
|
|
if err != nil {
|
|
return fmt.Errorf("Errors encountered while getting VolumeAttachment %s/%s: %v", namespace, volumeAttachmentName, err)
|
|
}
|
|
if isExist == exist {
|
|
return nil
|
|
}
|
|
|
|
k8slogger.Infof("waiting for VolumeAttachment %s in namespace %s to %s", volumeAttachmentName, namespace, action)
|
|
time.Sleep(RetryInterval * time.Second)
|
|
inc++
|
|
|
|
}
|
|
return fmt.Errorf("timeout for VolumeAttachment %s in namespace %s wait to %s", volumeAttachmentName, namespace, action)
|
|
}
|
|
|
|
func (k8sh *K8sHelper) isVolumeAttachmentExist(namespace, name string) (bool, error) {
|
|
var result rookalpha.VolumeAttachment
|
|
uri := fmt.Sprintf("apis/%s/%s/namespaces/%s/%s", rookalpha.CustomResourceGroup, rookalpha.Version, namespace, attachment.CustomResourceNamePlural)
|
|
err := k8sh.Clientset.Core().RESTClient().Get().
|
|
RequestURI(uri).
|
|
Name(name).
|
|
Do().
|
|
Into(&result)
|
|
if err != nil {
|
|
if errors.IsNotFound(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
//GetPodDetails returns details about a pod
|
|
func (k8sh *K8sHelper) GetPodDetails(podNamePattern string, namespace string) (string, error) {
|
|
args := []string{"get", "pods", "-l", "app=" + podNamePattern, "-o", "wide", "--no-headers=true", "-o", "name"}
|
|
if namespace != "" {
|
|
args = append(args, []string{"-n", namespace}...)
|
|
}
|
|
result, err := k8sh.Kubectl(args...)
|
|
if err != nil || strings.Contains(result, "No resources found") {
|
|
return "", fmt.Errorf("Cannot find pod in with name like %s in namespace : %s -- %v", podNamePattern, namespace, err)
|
|
}
|
|
return strings.TrimSpace(result), nil
|
|
}
|
|
|
|
//GetPodEvents returns events about a pod
|
|
func (k8sh *K8sHelper) GetPodEvents(podNamePattern string, namespace string) (*v1.EventList, error) {
|
|
uri := fmt.Sprintf("api/v1/namespaces/%s/events?fieldSelector=involvedObject.name=%s,involvedObject.namespace=%s", namespace, podNamePattern, namespace)
|
|
result, err := k8sh.Clientset.CoreV1().RESTClient().Get().RequestURI(uri).DoRaw()
|
|
if err != nil {
|
|
logger.Errorf("Cannot get events for pod %v in namespace %v, err: %v", podNamePattern, namespace, err)
|
|
return nil, fmt.Errorf("Cannot get events for pod %s in namespace %s, err: %v", podNamePattern, namespace, err)
|
|
}
|
|
|
|
events := v1.EventList{}
|
|
err = json.Unmarshal(result, &events)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal eventlist response: %v", err)
|
|
}
|
|
|
|
return &events, nil
|
|
}
|
|
|
|
//IsPodInError returns true if a Pod is in error status with the given reason and contains the given message
|
|
func (k8sh *K8sHelper) IsPodInError(podNamePattern, namespace, reason, containingMessage string) bool {
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
events, err := k8sh.GetPodEvents(podNamePattern, namespace)
|
|
if err != nil {
|
|
k8slogger.Errorf("Cannot get Pod events for %s in namespace %s: %+v ", podNamePattern, namespace, err)
|
|
return false
|
|
}
|
|
|
|
for _, e := range events.Items {
|
|
if e.Reason == reason && strings.Contains(e.Message, containingMessage) {
|
|
return true
|
|
}
|
|
}
|
|
k8slogger.Infof("waiting for Pod %s in namespace %s to error with reason %s and containing the message: %s", podNamePattern, namespace, reason, containingMessage)
|
|
time.Sleep(RetryInterval * time.Second)
|
|
inc++
|
|
|
|
}
|
|
k8slogger.Infof("Pod %s in namespace %s did not error with reason %s", podNamePattern, namespace, reason)
|
|
return false
|
|
}
|
|
|
|
//GetPodHostID returns HostIP address of a pod
|
|
func (k8sh *K8sHelper) GetPodHostID(podNamePattern string, namespace string) (string, error) {
|
|
listOpts := metav1.ListOptions{LabelSelector: "app=" + podNamePattern}
|
|
podList, err := k8sh.Clientset.CoreV1().Pods(namespace).List(listOpts)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
|
|
return "", fmt.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
|
|
}
|
|
|
|
if len(podList.Items) < 1 {
|
|
logger.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
|
|
return "", fmt.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
|
|
}
|
|
return podList.Items[0].Status.HostIP, nil
|
|
}
|
|
|
|
//GetServiceNodePort returns nodeProt of service
|
|
func (k8sh *K8sHelper) GetServiceNodePort(serviceName string, namespace string) (string, error) {
|
|
getOpts := metav1.GetOptions{}
|
|
svc, err := k8sh.Clientset.CoreV1().Services(namespace).Get(serviceName, getOpts)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get service : %v in namespace %v, err: %v", serviceName, namespace, err)
|
|
return "", fmt.Errorf("Cannot get service : %v in namespace %v, err: %v", serviceName, namespace, err)
|
|
}
|
|
np := svc.Spec.Ports[0].NodePort
|
|
return strconv.FormatInt(int64(np), 10), nil
|
|
}
|
|
|
|
//IsStorageClassPresent returns true if storageClass is present, if not false
|
|
func (k8sh *K8sHelper) IsStorageClassPresent(name string) (bool, error) {
|
|
args := []string{"get", "storageclass", "-o", "jsonpath='{.items[*].metadata.name}'"}
|
|
result, err := k8sh.Kubectl(args...)
|
|
if strings.Contains(result, name) {
|
|
return true, nil
|
|
}
|
|
return false, fmt.Errorf("Storageclass %s not found, err ->%v", name, err)
|
|
|
|
}
|
|
|
|
//CheckPvcCount returns True if expected number pvs for a app are found
|
|
func (k8sh *K8sHelper) CheckPvcCountAndStatus(podName string, namespace string, expectedPvcCount int, expectedStatus string) bool {
|
|
logger.Infof("wait until %d pvc for app=%s are present", expectedPvcCount, podName)
|
|
listOpts := metav1.ListOptions{LabelSelector: "app=" + podName}
|
|
pvcCountCheck := false
|
|
|
|
actualPvcCount := 0
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
pvcList, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(listOpts)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get pvc for app : %v in namespace %v, err: %v", podName, namespace, err)
|
|
return false
|
|
}
|
|
actualPvcCount = len(pvcList.Items)
|
|
if actualPvcCount == expectedPvcCount {
|
|
pvcCountCheck = true
|
|
break
|
|
}
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
|
|
if !pvcCountCheck {
|
|
logger.Errorf("Expecting %d number of PVCs for %s app, found %d ", expectedPvcCount, podName, actualPvcCount)
|
|
return false
|
|
}
|
|
|
|
inc = 0
|
|
for inc < RetryLoop {
|
|
checkAllPVCsStatus := true
|
|
pl, _ := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(listOpts)
|
|
for _, pvc := range pl.Items {
|
|
if !(pvc.Status.Phase == v1.PersistentVolumeClaimPhase(expectedStatus)) {
|
|
checkAllPVCsStatus = false
|
|
logger.Infof("waiting for pvc %v to be in %s Phase, currently in %v Phase", pvc.Name, expectedStatus, pvc.Status.Phase)
|
|
}
|
|
}
|
|
if checkAllPVCsStatus {
|
|
return true
|
|
}
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
|
|
}
|
|
logger.Errorf("Giving up waiting for %d PVCs for %s app to be in %s phase", expectedPvcCount, podName, expectedStatus)
|
|
return false
|
|
}
|
|
|
|
//GetPVCStatus returns status of PVC
|
|
func (k8sh *K8sHelper) GetPVCStatus(namespace string, name string) (v1.PersistentVolumeClaimPhase, error) {
|
|
getOpts := metav1.GetOptions{}
|
|
|
|
pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(name, getOpts)
|
|
if err != nil {
|
|
return v1.ClaimLost, fmt.Errorf("PVC %s not found,err->%v", name, err)
|
|
}
|
|
|
|
return pvc.Status.Phase, nil
|
|
|
|
}
|
|
|
|
//GetPVCAccessModes returns AccessModes on PVC
|
|
func (k8sh *K8sHelper) GetPVCAccessModes(namespace string, name string) ([]v1.PersistentVolumeAccessMode, error) {
|
|
getOpts := metav1.GetOptions{}
|
|
|
|
pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(name, getOpts)
|
|
if err != nil {
|
|
return []v1.PersistentVolumeAccessMode{}, fmt.Errorf("PVC %s not found,err->%v", name, err)
|
|
}
|
|
|
|
return pvc.Status.AccessModes, nil
|
|
|
|
}
|
|
|
|
//IsPodInExpectedState waits for 90s for a pod to be an expected state
|
|
//If the pod is in expected state within 90s true is returned, if not false
|
|
func (k8sh *K8sHelper) IsPodInExpectedState(podNamePattern string, namespace string, state string) bool {
|
|
listOpts := metav1.ListOptions{LabelSelector: "app=" + podNamePattern}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
podList, err := k8sh.Clientset.CoreV1().Pods(namespace).List(listOpts)
|
|
if err == nil {
|
|
if len(podList.Items) >= 1 {
|
|
if podList.Items[0].Status.Phase == v1.PodPhase(state) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
//CheckPodCountAndState returns true if expected number of pods with matching name are found and are in expected state
|
|
func (k8sh *K8sHelper) CheckPodCountAndState(podName string, namespace string, minExpected int, expectedPhase string) bool {
|
|
listOpts := metav1.ListOptions{LabelSelector: "app=" + podName}
|
|
podCountCheck := false
|
|
actualPodCount := 0
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
podList, err := k8sh.Clientset.CoreV1().Pods(namespace).List(listOpts)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get logs for app : %v in namespace %v, err: %v", podName, namespace, err)
|
|
return false
|
|
}
|
|
actualPodCount = len(podList.Items)
|
|
if actualPodCount >= minExpected {
|
|
podCountCheck = true
|
|
break
|
|
}
|
|
|
|
inc++
|
|
logger.Infof("waiting for %d pods with label app=%s,found %d", minExpected, podName, actualPodCount)
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
if !podCountCheck {
|
|
logger.Errorf("Expecting %d number of pods for %s app, found %d ", minExpected, podName, actualPodCount)
|
|
return false
|
|
}
|
|
|
|
inc = 0
|
|
for inc < RetryLoop {
|
|
checkAllPodsStatus := true
|
|
pl, _ := k8sh.Clientset.CoreV1().Pods(namespace).List(listOpts)
|
|
for _, pod := range pl.Items {
|
|
if !(pod.Status.Phase == v1.PodPhase(expectedPhase)) {
|
|
checkAllPodsStatus = false
|
|
logger.Infof("waiting for pod %v to be in %s Phase, currently in %v Phase", pod.Name, expectedPhase, pod.Status.Phase)
|
|
}
|
|
}
|
|
if checkAllPodsStatus {
|
|
return true
|
|
}
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
|
|
}
|
|
logger.Errorf("All pods with app Name %v not in %v phase ", podName, expectedPhase)
|
|
return false
|
|
|
|
}
|
|
|
|
//WaitUntilPodInNamespaceIsDeleted waits for 90s for a pod in a namespace to be terminated
|
|
//If the pod disappears within 90s true is returned, if not false
|
|
func (k8sh *K8sHelper) WaitUntilPodInNamespaceIsDeleted(podNamePattern string, namespace string) bool {
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
out, _ := k8sh.GetResource("-n", namespace, "pods", "-l", "app="+podNamePattern)
|
|
if !strings.Contains(out, podNamePattern) {
|
|
return true
|
|
}
|
|
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
logger.Infof("Pod %s in namespace %s not deleted", podNamePattern, namespace)
|
|
return false
|
|
}
|
|
|
|
//WaitUntilPodIsDeleted waits for 90s for a pod to be terminated
|
|
//If the pod disappears within 90s true is returned, if not false
|
|
func (k8sh *K8sHelper) WaitUntilPodIsDeleted(podNamePattern string) bool {
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
out, _ := k8sh.GetResource("pods", "-l", "app="+podNamePattern)
|
|
if !strings.Contains(out, podNamePattern) {
|
|
return true
|
|
}
|
|
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
return false
|
|
}
|
|
|
|
//WaitUntilPVCIsBound waits for a PVC to be in bound state for 90 seconds
|
|
//if PVC goes to Bound state within 90s True is returned, if not false
|
|
func (k8sh *K8sHelper) WaitUntilPVCIsBound(namespace string, pvcname string) bool {
|
|
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
out, err := k8sh.GetPVCStatus(namespace, pvcname)
|
|
if err == nil {
|
|
if out == v1.PersistentVolumeClaimPhase(v1.ClaimBound) {
|
|
return true
|
|
}
|
|
}
|
|
logger.Infof("waiting for PVC %s to be bound. current=%s. err=%+v", pvcname, out, err)
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (k8sh *K8sHelper) WaitUntilPVCIsDeleted(namespace string, pvcname string) bool {
|
|
getOpts := metav1.GetOptions{}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
_, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(pvcname, getOpts)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
logger.Infof("waiting for PVC %s to be deleted.", pvcname)
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (k8sh *K8sHelper) DeletePvcWithLabel(namespace string, podName string) bool {
|
|
delOpts := metav1.DeleteOptions{}
|
|
listOpts := metav1.ListOptions{LabelSelector: "app=" + podName}
|
|
|
|
err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).DeleteCollection(&delOpts, listOpts)
|
|
if err != nil {
|
|
logger.Errorf("cannot deleted PVCs for pods with label app=%s", podName)
|
|
return false
|
|
}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
pvcs, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(listOpts)
|
|
if err == nil {
|
|
if len(pvcs.Items) == 0 {
|
|
return true
|
|
}
|
|
}
|
|
logger.Infof("waiting for PVCs for pods with label=%s to be deleted.", podName)
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
return false
|
|
}
|
|
|
|
//WaitUntilNameSpaceIsDeleted waits for namespace to be deleted for 180s.
|
|
//If namespace is deleted True is returned, if not false.
|
|
func (k8sh *K8sHelper) WaitUntilNameSpaceIsDeleted(namespace string) bool {
|
|
getOpts := metav1.GetOptions{}
|
|
inc := 0
|
|
for inc < RetryLoop {
|
|
ns, err := k8sh.Clientset.CoreV1().Namespaces().Get(namespace, getOpts)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
logger.Infof("Namespace %s %v", namespace, ns.Status.Phase)
|
|
inc++
|
|
time.Sleep(RetryInterval * time.Second)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
//CreateExternalRGWService creates a service for rgw access external to the cluster on a node port
|
|
func (k8sh *K8sHelper) CreateExternalRGWService(namespace, storeName string) error {
|
|
svcName := "rgw-external-" + storeName
|
|
externalSvc := `apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: ` + svcName + `
|
|
namespace: ` + namespace + `
|
|
labels:
|
|
app: rook-ceph-rgw
|
|
rook_cluster: ` + namespace + `
|
|
spec:
|
|
ports:
|
|
- name: rook-ceph-rgw
|
|
port: 53390
|
|
protocol: TCP
|
|
selector:
|
|
app: rook-ceph-rgw
|
|
rook_cluster: ` + namespace + `
|
|
sessionAffinity: None
|
|
type: NodePort
|
|
`
|
|
_, err := k8sh.KubectlWithStdin(externalSvc, []string{"create", "-f", "-"}...)
|
|
if err != nil && !errors.IsAlreadyExists(err) {
|
|
return fmt.Errorf("failed to create external service. %+v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (k8sh *K8sHelper) GetRGWServiceURL(storeName string, namespace string) (string, error) {
|
|
if k8sh.RunningInCluster {
|
|
return k8sh.GetInternalRGWServiceURL(storeName, namespace)
|
|
}
|
|
return k8sh.GetExternalRGWServiceURL(storeName, namespace)
|
|
}
|
|
|
|
//GetRGWServiceURL returns URL of ceph RGW service in the cluster
|
|
func (k8sh *K8sHelper) GetInternalRGWServiceURL(storeName string, namespace string) (string, error) {
|
|
name := "rook-ceph-rgw-" + storeName
|
|
svc, err := k8sh.GetService(name, namespace)
|
|
if err != nil {
|
|
return "", fmt.Errorf("RGW service not found/object. %+v", err)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s:%d", svc.Spec.ClusterIP, svc.Spec.Ports[0].Port)
|
|
logger.Infof("internal rgw endpoint: %s", endpoint)
|
|
return endpoint, nil
|
|
}
|
|
|
|
//GetRGWServiceURL returns URL of ceph RGW service in the cluster
|
|
func (k8sh *K8sHelper) GetExternalRGWServiceURL(storeName string, namespace string) (string, error) {
|
|
hostip, err := k8sh.GetPodHostID("rook-ceph-rgw", namespace)
|
|
if err != nil {
|
|
return "", fmt.Errorf("RGW pods not found. %+v", err)
|
|
}
|
|
|
|
serviceName := "rgw-external-" + storeName
|
|
nodePort, err := k8sh.GetServiceNodePort(serviceName, namespace)
|
|
if err != nil {
|
|
return "", fmt.Errorf("RGW service not found. %+v", err)
|
|
}
|
|
endpoint := hostip + ":" + nodePort
|
|
logger.Infof("external rgw endpoint: %s", endpoint)
|
|
return endpoint, err
|
|
}
|
|
|
|
//IsRookInstalled returns true is rook-api service is running(indicating rook is installed)
|
|
func (k8sh *K8sHelper) IsRookInstalled(namespace string) bool {
|
|
opts := metav1.GetOptions{}
|
|
_, err := k8sh.Clientset.CoreV1().Services(namespace).Get("rook-api", opts)
|
|
if err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
//GetRookLogs captures logs from specified rook pod and writes it to specified file
|
|
func (k8sh *K8sHelper) GetRookLogs(podAppName string, hostType string, namespace string, testName string) {
|
|
logOpts := &v1.PodLogOptions{}
|
|
listOpts := metav1.ListOptions{LabelSelector: "app=" + podAppName}
|
|
|
|
podList, err := k8sh.Clientset.CoreV1().Pods(namespace).List(listOpts)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get logs for app : %v in namespace %v, err: %v", podAppName, namespace, err)
|
|
return
|
|
}
|
|
|
|
for _, pod := range podList.Items {
|
|
podName := pod.Name
|
|
logger.Infof("getting logs for pod : %v", podName)
|
|
res := k8sh.Clientset.CoreV1().Pods(namespace).GetLogs(podName, logOpts).Do()
|
|
rawData, err := res.Raw()
|
|
if err != nil {
|
|
logger.Errorf("Cannot get logs for app : %v in namespace %v, err: %v", podName, namespace, err)
|
|
continue
|
|
}
|
|
dir, _ := os.Getwd()
|
|
fpath := path.Join(dir, "_output/tests/")
|
|
if _, err := os.Stat(fpath); os.IsNotExist(err) {
|
|
err := os.MkdirAll(fpath, 0777)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get logs files dir for app : %v in namespace %v, err: %v", podName, namespace, err)
|
|
continue
|
|
}
|
|
}
|
|
fileName := fmt.Sprintf("%s_%s_%s_%s_%d.log", testName, hostType, podName, namespace, time.Now().Unix())
|
|
fpath = path.Join(fpath, fileName)
|
|
file, err := os.Create(fpath)
|
|
if err != nil {
|
|
logger.Errorf("Cannot get logs files for app : %v in namespace %v, err: %v", podName, namespace, err)
|
|
continue
|
|
}
|
|
|
|
defer file.Close()
|
|
_, err = file.Write(rawData)
|
|
if err != nil {
|
|
logger.Errorf("Errors while writing logs for : %v to file, err : %v", podName, err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
//CreateAnonSystemClusterBinding Creates anon-user-access clusterrolebinding for cluster-admin role - used by kubeadm env.
|
|
func (k8sh *K8sHelper) CreateAnonSystemClusterBinding() {
|
|
args := []string{"create", "clusterrolebinding", "anon-user-access", "--clusterrole", "cluster-admin", "--user", "system:anonymous"}
|
|
_, err := k8sh.Kubectl(args...)
|
|
if err != nil {
|
|
logger.Warningf("anon-user-access not created")
|
|
return
|
|
}
|
|
logger.Infof("anon-user-access created")
|
|
}
|
|
|
|
func (k8sh *K8sHelper) DeleteRoleAndBindings(name, namespace string) error {
|
|
_, err := k8sh.DeleteResource([]string{"role", name, "-n", namespace})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = k8sh.DeleteResource([]string{"rolebinding", name, "-n", namespace})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (k8sh *K8sHelper) ScaleStatefulSet(statefulSetName, namespace string, replicationSize int) error {
|
|
args := []string{"-n", namespace, "scale", "statefulsets", statefulSetName, fmt.Sprintf("--replicas=%d", replicationSize)}
|
|
_, err := k8sh.Kubectl(args...)
|
|
return err
|
|
}
|