Files
my-rook-config/tests/framework/utils/k8s_helper.go
T
2019-05-03 12:46:18 +05:30

1542 lines
50 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"
rookclient "github.com/rook/rook/pkg/client/clientset/versioned"
"github.com/rook/rook/pkg/clusterd"
"github.com/rook/rook/pkg/util/exec"
"github.com/stretchr/testify/require"
v1 "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/apimachinery/pkg/util/version"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
storagev1util "k8s.io/kubernetes/pkg/apis/storage/v1/util"
)
// 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 = 40
// RetryInterval param for test - wait time while in RetryLoop
RetryInterval = 5
// TestMountPath is the path inside a test pod where storage is mounted
TestMountPath = "/tmp/testrook"
//hostnameTestPrefix is a prefix added to the node hostname
hostnameTestPrefix = "test-prefix-this-is-a-very-long-hostname-"
)
// 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))
}
func (k8sh *K8sHelper) MakeContext() *clusterd.Context {
return &clusterd.Context{Clientset: k8sh.Clientset, RookClientset: k8sh.RookClientset, Executor: k8sh.executor}
}
func (k8sh *K8sHelper) GetDockerImage(image string) error {
return k8sh.executor.ExecuteCommand(false, "", "docker", "pull", image)
}
// SetDeploymentVersion sets the container version on the deployment. It is assumed to be the rook/ceph image.
func (k8sh *K8sHelper) SetDeploymentVersion(namespace, deploymentName, containerName, version string) error {
_, err := k8sh.Kubectl("-n", namespace, "set", "image", "deploy/"+deploymentName, containerName+"=rook/ceph:"+version)
return err
}
// Kubectl is wrapper for executing kubectl commands
func (k8sh *K8sHelper) Kubectl(args ...string) (string, error) {
result, err := k8sh.executor.ExecuteCommandWithTimeout(false, 15*time.Second, "kubectl", "kubectl", args...)
if err != nil {
k8slogger.Errorf("Failed to execute: kubectl %+v : %+v. %s", args, err, result)
if args[0] == "delete" {
// allow the tests to continue if we were deleting a resource that timed out
return result, nil
}
return result, fmt.Errorf("Failed to run: kubectl %v : %v", args, err)
}
return result, nil
}
func (k8sh *K8sHelper) PurgeClusters() error {
// get all namespaces
namespaces, err := k8sh.Clientset.CoreV1().Namespaces().List(metav1.ListOptions{})
if err != nil {
return fmt.Errorf("failed to get namespaces to remove finalizers. %+v", err)
}
// look for the clusters in all namespaces
for _, n := range namespaces.Items {
namespace := n.Name
logger.Infof("looking in namespace %s for clusters to purge", namespace)
clusters, err := k8sh.RookClientset.CephV1().CephClusters(namespace).List(metav1.ListOptions{})
if err != nil {
logger.Warningf("failed to get clusters in namespace %s. %+v", namespace, err)
continue
}
for _, cluster := range clusters.Items {
logger.Infof("Ensuring rook cluster crd %s in namespace %s is deleted", cluster.Name, namespace)
if _, err := k8sh.Kubectl("patch", "cephclusters.ceph.rook.io", cluster.Name, "-n", namespace, "-p", `{"metadata":{"finalizers": []}}`, "--type=merge"); err != nil {
logger.Warningf("failed to remove finalizer from cluster %s. %+v", cluster.Name, err)
}
if err := k8sh.RookClientset.CephV1().CephClusters(namespace).Delete(cluster.Name, &metav1.DeleteOptions{}); err != nil {
logger.Warningf("failed to delete cluster %s", cluster.Name)
}
}
}
return 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("Failed to execute stdin: kubectl %v : %v", args, cmdOut.Err.Error())
if strings.Index(cmdOut.Err.Error(), "(NotFound)") != -1 || strings.Index(cmdOut.StdErr, "(NotFound)") != -1 {
return cmdOut.StdErr, errors.NewNotFound(schema.GroupResource{}, "")
}
return cmdOut.StdErr, fmt.Errorf("Failed to run stdin: kubectl %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"`
}
func (k8sh *K8sHelper) Exec(namespace, podName, command string, commandArgs []string) (string, error) {
return k8sh.ExecWithRetry(1, namespace, podName, command, commandArgs)
}
// ExecWithRetry will attempt to run a command "retries" times, waiting 3s between each call. Upon success, returns the output.
func (k8sh *K8sHelper) ExecWithRetry(retries int, namespace, podName, command string, commandArgs []string) (string, error) {
var err error
for i := 0; i < retries; i++ {
args := []string{"exec", "-n", namespace, podName, "--", command}
args = append(args, commandArgs...)
var result string
result, err = k8sh.Kubectl(args...)
if err == nil {
return result, nil
}
if i < retries-1 {
time.Sleep(3 * time.Second)
}
}
return "", fmt.Errorf("kubectl exec command %s failed on pod %s in namespace %s. %+v", command, podName, namespace, err)
}
// 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 %s resource in args : %v %v-- %v", action, args, podDef, err)
}
// ResourceOperation performs a kubectl action on a pod definition
func (k8sh *K8sHelper) ResourceOperation(action string, manifest string) error {
args := []string{action, "-f", "-"}
logger.Infof("kubectl create manifest:\n%s", manifest)
_, err := k8sh.KubectlWithStdin(manifest, args...)
if err == nil {
return nil
}
logger.Errorf("Failed to execute kubectl %v -- %v", args, err)
return fmt.Errorf("Could Not create resource in args : %v -- %v", args, err)
}
// DeletePod performs a kubectl delete pod on the given pod
func (k8sh *K8sHelper) DeletePod(namespace, name string) error {
args := append([]string{"--grace-period=0", "pod"}, name)
if namespace != "" {
args = append(args, []string{"-n", namespace}...)
}
return k8sh.DeleteResourceAndWait(true, args...)
}
// DeletePods performs a kubectl delete pod on the given pods
func (k8sh *K8sHelper) DeletePods(pods ...string) (msg string, err error) {
for _, pod := range pods {
if perr := k8sh.DeletePod("", pod); perr != nil {
err = perr
}
}
return
}
// DeleteResource performs a kubectl delete on the given args
func (k8sh *K8sHelper) DeleteResource(args ...string) error {
return k8sh.DeleteResourceAndWait(true, args...)
}
// WaitForCustomResourceDeletion waits for the CRD deletion
func (k8sh *K8sHelper) WaitForCustomResourceDeletion(namespace string, checkerFunc func() error) error {
// wait for the operator to finalize and delete the CRD
for i := 0; i < 10; i++ {
err := checkerFunc()
if err == nil {
logger.Infof("custom resource %s still exists", namespace)
time.Sleep(2 * time.Second)
continue
}
if errors.IsNotFound(err) {
logger.Infof("custom resource %s deleted", namespace)
return nil
}
return err
}
logger.Errorf("gave up deleting custom resource %s", namespace)
return nil
}
// DeleteResource performs a kubectl delete on give args.
// If wait is false, a flag will be passed to indicate the delete should return immediately
func (k8sh *K8sHelper) DeleteResourceAndWait(wait bool, args ...string) error {
if !wait {
// new flag in k8s 1.11
v := version.MustParseSemantic(k8sh.GetK8sServerVersion())
if v.AtLeast(version.MustParseSemantic("1.11.0")) {
args = append(args, "--wait=false")
}
}
args = append([]string{"delete"}, args...)
_, err := k8sh.Kubectl(args...)
if err == nil {
return 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)
}
func (k8sh *K8sHelper) CreateNamespace(namespace string) error {
ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
_, err := k8sh.Clientset.CoreV1().Namespaces().Create(ns)
if err != nil && !errors.IsAlreadyExists(err) {
return fmt.Errorf("failed to create namespace %s. %+v", namespace, err)
}
return nil
}
func (k8sh *K8sHelper) CountPodsWithLabel(label string, namespace string) (int, error) {
options := metav1.ListOptions{LabelSelector: label}
pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(options)
if err != nil {
if errors.IsNotFound(err) {
return 0, nil
}
return 0, err
}
return len(pods.Items), nil
}
// WaitForPodCount waits until the desired number of pods with the label are started
func (k8sh *K8sHelper) WaitForPodCount(label, namespace string, count int) error {
options := metav1.ListOptions{LabelSelector: label}
inc := 0
for inc < RetryLoop {
pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(options)
if err != nil {
return fmt.Errorf("failed to find pod with label %s. %+v", label, err)
}
if len(pods.Items) >= count {
logger.Infof("found %d pods with label %s", count, label)
return nil
}
inc++
time.Sleep(RetryInterval * time.Second)
logger.Infof("waiting for %d pods (found %d) with label %s in namespace %s", count, len(pods.Items), label, namespace)
}
return fmt.Errorf("Giving up waiting for pods with label %s in namespace %s", label, namespace)
}
// IsPodWithLabelPresent return true if there is at least one Pod with the label is present.
func (k8sh *K8sHelper) IsPodWithLabelPresent(label string, namespace string) bool {
count, err := k8sh.CountPodsWithLabel(label, namespace)
if err != nil {
return false
}
return count > 0
}
// WaitForLabeledPodsToRun calls WaitForLabeledPodsToRunWithRetries with the default number of retries
func (k8sh *K8sHelper) WaitForLabeledPodsToRun(label, namespace string) error {
return k8sh.WaitForLabeledPodsToRunWithRetries(label, namespace, RetryLoop)
}
// WaitForLabeledPodsToRunWithRetries returns true if a Pod is running status or goes to Running status within 90s else returns false
func (k8sh *K8sHelper) WaitForLabeledPodsToRunWithRetries(label string, namespace string, retries int) error {
options := metav1.ListOptions{LabelSelector: label}
var lastPod v1.Pod
for i := 0; i < retries; i++ {
pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(options)
lastStatus := ""
running := 0
if err == nil && len(pods.Items) > 0 {
for _, pod := range pods.Items {
if pod.Status.Phase == "Running" {
running++
}
lastPod = pod
lastStatus = string(pod.Status.Phase)
}
if running == len(pods.Items) {
logger.Infof("All %d pod(s) with label %s are running", len(pods.Items), label)
return nil
}
}
logger.Infof("waiting for pod(s) with label %s in namespace %s to be running. status=%s, running=%d/%d, err=%+v",
label, namespace, lastStatus, running, len(pods.Items), err)
time.Sleep(RetryInterval * time.Second)
}
if len(lastPod.Name) == 0 {
logger.Infof("no pod was found with label %s", label)
} else {
k8sh.PrintPodDescribe(namespace, lastPod.Name)
}
return fmt.Errorf("Giving up waiting for pod with label %s in namespace %s to be running", label, namespace)
}
// 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}
for i := 0; i < RetryLoop; i++ {
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 {
logger.Infof("no (more) pods with label %s in namespace %s to be deleted", label, namespace)
return true
}
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
}
// PrintPodStatus log out the status phase of a pod
func (k8sh *K8sHelper) PrintPodStatus(namespace string) {
pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{})
if err != nil {
logger.Errorf("failed to get pod status in namespace %s. %+v", namespace, err)
return
}
for _, pod := range pods.Items {
logger.Infof("%s (%s) pod status: %+v", pod.Name, namespace, pod.Status)
}
}
func (k8sh *K8sHelper) PrintPodDescribeForNamespace(namespace string) {
logger.Infof("printing pod describe for all pods in namespace %s", namespace)
pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{})
if err != nil {
logger.Errorf("failed to list pods in namespace %s. %+v", namespace, err)
return
}
for _, p := range pods.Items {
logger.Infof("pod %s in namespace %s: %+v", p.Name, namespace, p)
}
k8sh.PrintEventsForNamespace(namespace)
}
func (k8sh *K8sHelper) PrintPodDescribe(namespace string, args ...string) {
args = append([]string{"describe", "pod", "-n", namespace}, args...)
description, err := k8sh.Kubectl(args...)
if err != nil {
logger.Errorf("failed to describe pod. %v %+v", args, err)
} else {
logger.Infof("pod description:\n%s\n", description)
}
}
func (k8sh *K8sHelper) PrintEventsForNamespace(namespace string) {
events, err := k8sh.Clientset.CoreV1().Events(namespace).List(metav1.ListOptions{})
if err != nil {
logger.Warningf("failed to get events in namespace %s. %+v", namespace, err)
return
}
logger.Infof("DUMPING events in namespace %s", namespace)
for _, event := range events.Items {
logger.Infof("%+v", event)
}
logger.Infof("DONE DUMPING events in namespace %s", namespace)
}
// 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 wrapper around IsPodTerminatedWithOpts()
func (k8sh *K8sHelper) IsPodTerminated(name string, namespace string) bool {
return k8sh.IsPodTerminatedWithOpts(name, namespace, metav1.GetOptions{})
}
// IsPodTerminatedWithOpts returns true if a Pod is terminated status or goes to Terminated status
// within 90s else returns false\
func (k8sh *K8sHelper) IsPodTerminatedWithOpts(name string, namespace string, getOpts metav1.GetOptions) bool {
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 terminate, status : %v", name, namespace, pod.Status.Phase)
time.Sleep(RetryInterval * time.Second)
inc++
}
k8slogger.Infof("Pod %s in namespace %s did not terminate", 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
}
// 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
}
// WriteToPod write file in Pod
func (k8sh *K8sHelper) WriteToPod(namespace, podName, filename, message string) error {
return k8sh.WriteToPodRetry(namespace, podName, filename, message, 1)
}
// WriteToPodRetry WriteToPod in a retry loop
func (k8sh *K8sHelper) WriteToPodRetry(namespace, podName, filename, message string, retries int) error {
logger.Infof("Writing file %s to pod %s", filename, podName)
var err error
for i := 0; i < retries; i++ {
if i > 0 {
logger.Infof("retrying write in 5s...")
time.Sleep(5 * time.Second)
}
err = k8sh.writeToPod(namespace, podName, filename, message)
if err == nil {
logger.Infof("write file %s in pod %s was successful", filename, podName)
return nil
}
}
return fmt.Errorf("failed to write file %s to pod %s. %+v", filename, podName, err)
}
func (k8sh *K8sHelper) ReadFromPod(namespace, podName, filename, expectedMessage string) error {
return k8sh.ReadFromPodRetry(namespace, podName, filename, expectedMessage, 1)
}
func (k8sh *K8sHelper) ReadFromPodRetry(namespace, podName, filename, expectedMessage string, retries int) error {
logger.Infof("Reading file %s from pod %s", filename, podName)
var err error
for i := 0; i < retries; i++ {
if i > 0 {
logger.Infof("retrying read in 5s...")
time.Sleep(5 * time.Second)
}
var data string
data, err = k8sh.readFromPod(namespace, podName, filename)
if err == nil {
logger.Infof("read file %s from pod %s was successful after %d attempt(s)", filename, podName, (i + 1))
if !strings.Contains(data, expectedMessage) {
return fmt.Errorf(`file %s in pod %s returned message "%s" instead of "%s"`, filename, podName, data, expectedMessage)
}
return nil
}
}
return fmt.Errorf("failed to read file %s from pod %s. %+v", filename, podName, err)
}
func (k8sh *K8sHelper) writeToPod(namespace, name, filename, message string) error {
wt := "echo \"" + message + "\">" + path.Join(TestMountPath, filename)
args := []string{"exec", name}
if namespace != "" {
args = append(args, "-n", namespace)
}
args = append(args, "--", "sh", "-c", wt)
_, err := k8sh.Kubectl(args...)
if err != nil {
return fmt.Errorf("failed to write file %s to pod %s. %+v", filename, name, err)
}
return nil
}
func (k8sh *K8sHelper) readFromPod(namespace, name, filename string) (string, error) {
rd := path.Join(TestMountPath, filename)
args := []string{"exec", name}
if namespace != "" {
args = append(args, "-n", namespace)
}
args = append(args, "--", "cat", rd)
result, err := k8sh.Kubectl(args...)
if err != nil {
return "", fmt.Errorf("failed to read file %s from pod %s. %+v", filename, name, err)
}
return result, nil
}
// GetVolumeResourceName gets the Volume object name from the PVC
func (k8sh *K8sHelper) GetVolumeResourceName(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
}
// IsVolumeResourcePresent returns true if Volume resource is present
func (k8sh *K8sHelper) IsVolumeResourcePresent(namespace, volumeName string) bool {
err := k8sh.waitForVolume(namespace, volumeName, true)
if err != nil {
k8slogger.Error(err.Error())
return false
}
return true
}
// IsVolumeResourceAbsent returns true if the Volume resource is deleted/absent within 90s else returns false
func (k8sh *K8sHelper) IsVolumeResourceAbsent(namespace, volumeName string) bool {
err := k8sh.waitForVolume(namespace, volumeName, false)
if err != nil {
k8slogger.Error(err.Error())
return false
}
return true
}
func (k8sh *K8sHelper) waitForVolume(namespace, volumeName string, exist bool) error {
action := "exist"
if !exist {
action = "not " + action
}
inc := 0
for inc < RetryLoop {
isExist, err := k8sh.isVolumeExist(namespace, volumeName)
if err != nil {
return fmt.Errorf("Errors encountered while getting Volume %s/%s: %v", namespace, volumeName, err)
}
if isExist == exist {
return nil
}
k8slogger.Infof("waiting for Volume %s in namespace %s to %s", volumeName, namespace, action)
time.Sleep(RetryInterval * time.Second)
inc++
}
k8sh.printVolumes(namespace, volumeName)
k8sh.PrintPVs(false /*detailed*/)
k8sh.PrintPVCs(namespace, false /*detailed*/)
return fmt.Errorf("timeout for Volume %s in namespace %s wait to %s", volumeName, namespace, action)
}
func (k8sh *K8sHelper) PrintPVs(detailed bool) {
pvs, err := k8sh.Clientset.CoreV1().PersistentVolumes().List(metav1.ListOptions{})
if err != nil {
logger.Errorf("failed to list pvs. %+v", err)
return
}
if detailed {
logger.Infof("Found %d PVs", len(pvs.Items))
for _, pv := range pvs.Items {
logger.Infof("PV %s: %+v", pv.Name, pv)
}
} else {
var names []string
for _, pv := range pvs.Items {
names = append(names, pv.Name)
}
logger.Infof("Found PVs: %v", names)
}
}
func (k8sh *K8sHelper) PrintPVCs(namespace string, detailed bool) {
pvcs, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(metav1.ListOptions{})
if err != nil {
logger.Errorf("failed to list pvcs. %+v", err)
return
}
if detailed {
logger.Infof("Found %d PVCs", len(pvcs.Items))
for _, pvc := range pvcs.Items {
logger.Infof("PVC %s: %+v", pvc.Name, pvc)
}
} else {
var names []string
for _, pvc := range pvcs.Items {
names = append(names, pvc.Name)
}
logger.Infof("Found PVCs: %v", names)
}
}
func (k8sh *K8sHelper) PrintStorageClasses(detailed bool) {
scs, err := k8sh.Clientset.StorageV1().StorageClasses().List(metav1.ListOptions{})
if err != nil {
logger.Errorf("failed to list StorageClasses: %+v", err)
return
}
if detailed {
logger.Infof("Found %d StorageClasses", len(scs.Items))
for _, sc := range scs.Items {
logger.Infof("StorageClass %s: %+v", sc.Name, sc)
}
} else {
var names []string
for _, sc := range scs.Items {
names = append(names, sc.Name)
}
logger.Infof("Found StorageClasses: %v", names)
}
}
func (k8sh *K8sHelper) printVolumes(namespace, desiredVolume string) {
volumes, err := k8sh.RookClientset.RookV1alpha2().Volumes(namespace).List(metav1.ListOptions{})
if err != nil {
logger.Infof("failed to list volumes in ns %s. %+v", namespace, err)
}
var names []string
for _, volume := range volumes.Items {
names = append(names, volume.Name)
}
logger.Infof("looking for volume %s in namespace %s. Found volumes: %v", desiredVolume, namespace, names)
}
func (k8sh *K8sHelper) isVolumeExist(namespace, name string) (bool, error) {
_, err := k8sh.RookClientset.RookV1alpha2().Volumes(namespace).Get(name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}
func (k8sh *K8sHelper) GetPodNamesForApp(appName, namespace string) ([]string, error) {
args := []string{"get", "pod", "-n", namespace, "-l", fmt.Sprintf("app=%s", appName),
"-o", "jsonpath={.items[*].metadata.name}"}
result, err := k8sh.Kubectl(args...)
if err != nil {
return nil, fmt.Errorf("failed to get pod names for app %s: %+v. output: %s", appName, err, result)
}
podNames := strings.Split(result, " ")
return podNames, 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) error {
args := []string{"get", "storageclass", "-o", "jsonpath='{.items[*].metadata.name}'"}
result, err := k8sh.Kubectl(args...)
if strings.Contains(result, name) {
return nil
}
return fmt.Errorf("Storageclass %s not found, err ->%v", name, err)
}
func (k8sh *K8sHelper) IsDefaultStorageClassPresent() (bool, error) {
scs, err := k8sh.Clientset.StorageV1().StorageClasses().List(metav1.ListOptions{})
if err != nil {
return false, fmt.Errorf("failed to list StorageClasses: %+v", err)
}
for _, sc := range scs.Items {
if storagev1util.IsDefaultAnnotation(sc.ObjectMeta) {
return true, nil
}
}
return false, nil
}
// 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
}
// GetPVCVolumeName returns volume name of PVC
func (k8sh *K8sHelper) GetPVCVolumeName(namespace string, name string) (string, error) {
getOpts := metav1.GetOptions{}
pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(name, getOpts)
if err != nil {
return "", fmt.Errorf("PVC %s not found,err->%v", name, err)
}
return pvc.Spec.VolumeName, 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
}
// GetPV returns PV by name
func (k8sh *K8sHelper) GetPV(name string) (*v1.PersistentVolume, error) {
getOpts := metav1.GetOptions{}
pv, err := k8sh.Clientset.CoreV1().PersistentVolumes().Get(name, getOpts)
if err != nil {
return nil, fmt.Errorf("PV %s not found,err->%v", name, err)
}
return pv, 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 list pods for app=%s in namespace %s, err: %+v", podName, namespace, err)
return false
}
actualPodCount = len(podList.Items)
if actualPodCount >= minExpected {
logger.Infof("%d of %d pods with label app=%s were found", actualPodCount, minExpected, podName)
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
}
for i := 0; i < RetryLoop; i++ {
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
}
time.Sleep(RetryInterval * time.Second)
}
logger.Errorf("All pods with app Name %v not in %v phase ", podName, expectedPhase)
k8sh.PrintPodDescribe(namespace, "-l", listOpts.LabelSelector)
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(name, namespace string) bool {
inc := 0
for inc < RetryLoop {
_, err := k8sh.Clientset.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
if err != nil && errors.IsNotFound(err) {
return true
}
inc++
logger.Infof("pod %s in namespace %s is not deleted yet", name, namespace)
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) {
logger.Infof("PVC %s is bound", pvcname)
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
}
// ChangeHostnames modifies the node hostname label to run tests in an environment where the node name is different from the hostname label
func (k8sh *K8sHelper) ChangeHostnames() error {
nodes, err := k8sh.Clientset.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
return err
}
for _, node := range nodes.Items {
hostname := node.Labels[v1.LabelHostname]
if !strings.HasPrefix(hostname, hostnameTestPrefix) {
node.Labels[v1.LabelHostname] = hostnameTestPrefix + hostname
logger.Infof("changed hostname of node %s to %s", node.Name, node.Labels[v1.LabelHostname])
_, err := k8sh.Clientset.CoreV1().Nodes().Update(&node)
if err != nil {
return err
}
}
}
return nil
}
// RestoreHostnames removes the test suffix from the node hostname labels
func (k8sh *K8sHelper) RestoreHostnames() ([]string, error) {
nodes, err := k8sh.Clientset.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, node := range nodes.Items {
hostname := node.Labels[v1.LabelHostname]
if strings.HasPrefix(hostname, hostnameTestPrefix) {
node.Labels[v1.LabelHostname] = hostname[len(hostnameTestPrefix):]
logger.Infof("restoring hostname of node %s to %s", node.Name, node.Labels[v1.LabelHostname])
_, err := k8sh.Clientset.CoreV1().Nodes().Update(&node)
if err != nil {
return nil, err
}
}
}
return nil, nil
}
// IsRookInstalled returns true is rook-ceph-mgr 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-ceph-mgr", opts)
if err == nil {
return true
}
return false
}
// GetPreviousLogs captures logs from the previous run of the container if it exists
func (k8sh *K8sHelper) GetPreviousLogs(podAppName string, hostType string, namespace string, testName string) {
k8sh.getContainerLogs(podAppName, hostType, namespace, testName, "", true)
}
// GetLogs captures logs from specified rook pod and writes it to specified file
func (k8sh *K8sHelper) GetLogs(podAppName string, hostType string, namespace string, testName string) {
k8sh.getContainerLogs(podAppName, hostType, namespace, testName, "", false)
}
// GetLogs captures logs from a specific container in a pod
func (k8sh *K8sHelper) GetContainerLogs(podAppName, hostType, namespace, testName, containerName string) {
k8sh.getContainerLogs(podAppName, hostType, namespace, testName, containerName, false)
}
func (k8sh *K8sHelper) getContainerLogs(podAppName, hostType, namespace, testName, containerName string, previousLog bool) {
logOpts := &v1.PodLogOptions{Previous: previousLog}
if containerName != "" {
logOpts.Container = containerName
}
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
}
if len(podList.Items) == 0 {
logger.Infof("no logs found for pod %s in namespace %s", podAppName, namespace)
}
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
}
}
logSuffix := ""
if containerName != "" {
logSuffix = "_" + containerName
}
fileName := fmt.Sprintf("%s_%s_%s_%s%s_%d.log", testName, hostType, podName, namespace, logSuffix, 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 creation completed, waiting for it to exist in API")
inc := 0
for inc < RetryLoop {
var err error
if _, err = k8sh.Clientset.RbacV1beta1().ClusterRoleBindings().Get("anon-user-access", metav1.GetOptions{}); err == nil {
break
}
logger.Warningf("failed to get anon-user-access clusterrolebinding, will try again: %+v", err)
inc++
time.Sleep(RetryInterval * time.Second)
}
}
func (k8sh *K8sHelper) DeleteRoleAndBindings(name, namespace string) error {
err := k8sh.DeleteResource("role", name, "-n", namespace)
if err != nil {
return err
}
err = k8sh.DeleteResource("rolebinding", name, "-n", namespace)
if err != nil {
return err
}
return nil
}
func (k8sh *K8sHelper) DeleteRoleBinding(name, namespace string) error {
err := k8sh.DeleteResource("rolebinding", name, "-n", namespace)
return err
}
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
}
func IsKubectlErrorNotFound(output string, err error) bool {
return err != nil && strings.Contains(output, "Error from server (NotFound)")
}