k8s_helper.go 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644
  1. /*
  2. Copyright 2016 The Rook Authors. All rights reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package utils
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "io"
  19. "net/http"
  20. "os"
  21. "path"
  22. "strconv"
  23. "strings"
  24. "testing"
  25. "time"
  26. "github.com/coreos/pkg/capnslog"
  27. bktclient "github.com/kube-object-storage/lib-bucket-provisioner/pkg/client/clientset/versioned"
  28. "github.com/pkg/errors"
  29. rookclient "github.com/rook/rook/pkg/client/clientset/versioned"
  30. "github.com/rook/rook/pkg/clusterd"
  31. "github.com/rook/rook/pkg/operator/ceph/cluster/nodedaemon"
  32. "github.com/rook/rook/pkg/operator/k8sutil"
  33. "github.com/rook/rook/pkg/util/exec"
  34. "github.com/stretchr/testify/assert"
  35. "github.com/stretchr/testify/require"
  36. apps "k8s.io/api/apps/v1"
  37. v1 "k8s.io/api/core/v1"
  38. kerrors "k8s.io/apimachinery/pkg/api/errors"
  39. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  40. "k8s.io/apimachinery/pkg/runtime/schema"
  41. "k8s.io/apimachinery/pkg/util/version"
  42. "k8s.io/apimachinery/pkg/util/wait"
  43. "k8s.io/client-go/kubernetes"
  44. "sigs.k8s.io/controller-runtime/pkg/client/config"
  45. )
  46. // K8sHelper is a helper for common kubectl commands
  47. type K8sHelper struct {
  48. executor *exec.CommandExecutor
  49. remoteExecutor *exec.RemotePodCommandExecutor
  50. Clientset *kubernetes.Clientset
  51. RookClientset *rookclient.Clientset
  52. BucketClientset *bktclient.Clientset
  53. RunningInCluster bool
  54. T func() *testing.T
  55. }
  56. const (
  57. // RetryInterval param for test - wait time while in RetryLoop
  58. RetryInterval = 5
  59. // TestMountPath is the path inside a test pod where storage is mounted
  60. TestMountPath = "/tmp/testrook"
  61. //hostnameTestPrefix is a prefix added to the node hostname
  62. hostnameTestPrefix = "test-prefix-this-is-a-very-long-hostname-"
  63. )
  64. // getCmd returns kubectl or oc if env var rook_test_openshift is
  65. // set to true
  66. func getCmd() string {
  67. cmd := "kubectl"
  68. if IsPlatformOpenShift() {
  69. cmd = "oc"
  70. }
  71. return cmd
  72. }
  73. // CreateK8sHelper creates a instance of k8sHelper
  74. func CreateK8sHelper(t func() *testing.T) (*K8sHelper, error) {
  75. executor := &exec.CommandExecutor{}
  76. config, err := config.GetConfig()
  77. if err != nil {
  78. return nil, fmt.Errorf("failed to get kube client. %+v", err)
  79. }
  80. clientset, err := kubernetes.NewForConfig(config)
  81. if err != nil {
  82. return nil, fmt.Errorf("failed to get clientset. %+v", err)
  83. }
  84. rookClientset, err := rookclient.NewForConfig(config)
  85. if err != nil {
  86. return nil, fmt.Errorf("failed to get rook clientset. %+v", err)
  87. }
  88. bucketClientset, err := bktclient.NewForConfig(config)
  89. if err != nil {
  90. return nil, fmt.Errorf("failed to get lib-bucket-provisioner clientset. %+v", err)
  91. }
  92. remoteExecutor := &exec.RemotePodCommandExecutor{
  93. ClientSet: clientset,
  94. RestClient: config,
  95. }
  96. h := &K8sHelper{executor: executor, Clientset: clientset, RookClientset: rookClientset, BucketClientset: bucketClientset, T: t, remoteExecutor: remoteExecutor}
  97. if strings.Contains(config.Host, "//10.") {
  98. h.RunningInCluster = true
  99. }
  100. return h, err
  101. }
  102. var (
  103. k8slogger = capnslog.NewPackageLogger("github.com/rook/rook", "utils")
  104. cmd = getCmd()
  105. // RetryLoop params for tests.
  106. RetryLoop = TestRetryNumber()
  107. )
  108. // GetK8sServerVersion returns k8s server version under test
  109. func (k8sh *K8sHelper) GetK8sServerVersion() string {
  110. versionInfo, err := k8sh.Clientset.ServerVersion()
  111. require.Nil(k8sh.T(), err)
  112. return versionInfo.GitVersion
  113. }
  114. func VersionAtLeast(actualVersion, minVersion string) bool {
  115. v := version.MustParseSemantic(actualVersion)
  116. return v.AtLeast(version.MustParseSemantic(minVersion))
  117. }
  118. func (k8sh *K8sHelper) VersionAtLeast(minVersion string) bool {
  119. v := version.MustParseSemantic(k8sh.GetK8sServerVersion())
  120. return v.AtLeast(version.MustParseSemantic(minVersion))
  121. }
  122. func (k8sh *K8sHelper) MakeContext() *clusterd.Context {
  123. return &clusterd.Context{Clientset: k8sh.Clientset, RookClientset: k8sh.RookClientset, Executor: k8sh.executor}
  124. }
  125. func (k8sh *K8sHelper) GetDockerImage(image string) error {
  126. dockercmd := os.Getenv("DOCKERCMD")
  127. if dockercmd == "" {
  128. dockercmd = "docker"
  129. }
  130. return k8sh.executor.ExecuteCommand(dockercmd, "pull", image)
  131. }
  132. // SetDeploymentVersion sets the container version on the deployment. It is assumed to be the rook/ceph image.
  133. func (k8sh *K8sHelper) SetDeploymentVersion(namespace, deploymentName, containerName, version string) error {
  134. _, err := k8sh.Kubectl("-n", namespace, "set", "image", "deploy/"+deploymentName, containerName+"=rook/ceph:"+version)
  135. return err
  136. }
  137. // Kubectl is wrapper for executing kubectl commands
  138. func (k8sh *K8sHelper) Kubectl(args ...string) (string, error) {
  139. result, err := k8sh.executor.ExecuteCommandWithTimeout(15*time.Second, "kubectl", args...)
  140. if err != nil {
  141. k8slogger.Errorf("Failed to execute: %s %+v : %+v. %s", cmd, args, err, result)
  142. if args[0] == "delete" {
  143. // allow the tests to continue if we were deleting a resource that timed out
  144. return result, nil
  145. }
  146. return result, fmt.Errorf("Failed to run: %s %v : %v", cmd, args, err)
  147. }
  148. return result, nil
  149. }
  150. // KubectlWithStdin is wrapper for executing kubectl commands in stdin
  151. func (k8sh *K8sHelper) KubectlWithStdin(stdin string, args ...string) (string, error) {
  152. cmdStruct := CommandArgs{Command: cmd, PipeToStdIn: stdin, CmdArgs: args}
  153. cmdOut := ExecuteCommand(cmdStruct)
  154. if cmdOut.ExitCode != 0 {
  155. k8slogger.Errorf("Failed to execute stdin: %s %v : %v", cmd, args, cmdOut.Err.Error())
  156. if strings.Contains(cmdOut.Err.Error(), "(NotFound)") || strings.Contains(cmdOut.StdErr, "(NotFound)") {
  157. return cmdOut.StdErr, kerrors.NewNotFound(schema.GroupResource{}, "")
  158. }
  159. return cmdOut.StdErr, fmt.Errorf("Failed to run stdin: %s %v : %v", cmd, args, cmdOut.StdErr)
  160. }
  161. if cmdOut.StdOut == "" {
  162. return cmdOut.StdErr, nil
  163. }
  164. return cmdOut.StdOut, nil
  165. }
  166. func getManifestFromURL(url string) (string, error) {
  167. req, err := http.NewRequest("GET", url, nil)
  168. if err != nil {
  169. return "", err
  170. }
  171. res, err := http.DefaultClient.Do(req)
  172. if err != nil {
  173. return "", errors.Wrapf(err, "failed to get manifest from url %s", url)
  174. }
  175. defer res.Body.Close()
  176. body, err := io.ReadAll(res.Body)
  177. if err != nil {
  178. return "", errors.Wrapf(err, "failed to read manifest from url %s", url)
  179. }
  180. return string(body), nil
  181. }
  182. // ExecToolboxWithRetry will attempt to run a toolbox command "retries" times, waiting 3s between each call. Upon success, returns the output.
  183. func (k8sh *K8sHelper) ExecToolboxWithRetry(retries int, namespace, command string, commandArgs []string) (string, error) {
  184. var err error
  185. var output, stderr string
  186. cliFinal := append([]string{command}, commandArgs...)
  187. for i := 0; i < retries; i++ {
  188. output, stderr, err = k8sh.remoteExecutor.ExecCommandInContainerWithFullOutput(context.TODO(), "rook-ceph-tools", "rook-ceph-tools", namespace, cliFinal...)
  189. if err == nil {
  190. return output, nil
  191. }
  192. if i < retries-1 {
  193. logger.Warningf("remote command %v execution failed trying again... %v", cliFinal, kerrors.ReasonForError(err))
  194. time.Sleep(3 * time.Second)
  195. }
  196. }
  197. return "", fmt.Errorf("remote exec command %v failed on pod in namespace %s. %s. %s. %+v", cliFinal, namespace, output, stderr, err)
  198. }
  199. // ResourceOperation performs a kubectl action on a pod definition
  200. func (k8sh *K8sHelper) ResourceOperation(action string, manifest string) error {
  201. args := []string{action, "-f", "-"}
  202. maxManifestCharsToPrint := 4000
  203. if len(manifest) > maxManifestCharsToPrint {
  204. logger.Infof("kubectl %s manifest (too long to print)", action)
  205. } else {
  206. logger.Infof("kubectl %s manifest:\n%s", action, manifest)
  207. }
  208. _, err := k8sh.KubectlWithStdin(manifest, args...)
  209. if err == nil {
  210. return nil
  211. }
  212. logger.Errorf("Failed to execute kubectl %v -- %v", args, err)
  213. return fmt.Errorf("Could Not create resource in args : %v -- %v", args, err)
  214. }
  215. // DeletePod performs a kubectl delete pod on the given pod
  216. func (k8sh *K8sHelper) DeletePod(namespace, name string) error {
  217. args := append([]string{"--grace-period=0", "pod"}, name)
  218. if namespace != "" {
  219. args = append(args, []string{"-n", namespace}...)
  220. }
  221. return k8sh.DeleteResourceAndWait(true, args...)
  222. }
  223. // DeleteResource performs a kubectl delete on the given args
  224. func (k8sh *K8sHelper) DeleteResource(args ...string) error {
  225. return k8sh.DeleteResourceAndWait(true, args...)
  226. }
  227. // WaitForCustomResourceDeletion waits for the CRD deletion
  228. func (k8sh *K8sHelper) WaitForCustomResourceDeletion(namespace, name string, checkerFunc func() error) error {
  229. // wait for the operator to finalize and delete the CRD
  230. for i := 0; i < 90; i++ {
  231. err := checkerFunc()
  232. if err == nil {
  233. logger.Infof("custom resource %q in namespace %q still exists", name, namespace)
  234. time.Sleep(2 * time.Second)
  235. continue
  236. }
  237. if kerrors.IsNotFound(err) {
  238. logger.Infof("custom resource %q in namespace %s deleted", name, namespace)
  239. return nil
  240. }
  241. return err
  242. }
  243. logger.Errorf("gave up deleting custom resource %q ", name)
  244. return fmt.Errorf("Timed out waiting for deletion of custom resource %q", name)
  245. }
  246. // DeleteResource performs a kubectl delete on give args.
  247. // If wait is false, a flag will be passed to indicate the delete should return immediately
  248. func (k8sh *K8sHelper) DeleteResourceAndWait(wait bool, args ...string) error {
  249. if !wait {
  250. args = append(args, "--wait=false")
  251. }
  252. args = append([]string{"delete"}, args...)
  253. _, err := k8sh.Kubectl(args...)
  254. if err == nil {
  255. return nil
  256. }
  257. return fmt.Errorf("Could Not delete resource in k8s -- %v", err)
  258. }
  259. // GetResource performs a kubectl get on give args
  260. func (k8sh *K8sHelper) GetResource(args ...string) (string, error) {
  261. args = append([]string{"get"}, args...)
  262. result, err := k8sh.Kubectl(args...)
  263. if err == nil {
  264. return result, nil
  265. }
  266. return result, fmt.Errorf("Could Not get resource in k8s -- %v", err)
  267. }
  268. func (k8sh *K8sHelper) CreateNamespace(namespace string) error {
  269. ctx := context.TODO()
  270. ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
  271. _, err := k8sh.Clientset.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
  272. if err != nil && !kerrors.IsAlreadyExists(err) {
  273. return fmt.Errorf("failed to create namespace %s. %+v", namespace, err)
  274. }
  275. return nil
  276. }
  277. func (k8sh *K8sHelper) CountPodsWithLabel(label string, namespace string) (int, error) {
  278. ctx := context.TODO()
  279. options := metav1.ListOptions{LabelSelector: label}
  280. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, options)
  281. if err != nil {
  282. if kerrors.IsNotFound(err) {
  283. return 0, nil
  284. }
  285. return 0, err
  286. }
  287. return len(pods.Items), nil
  288. }
  289. // WaitForPodCount waits until the desired number of pods with the label are started
  290. func (k8sh *K8sHelper) WaitForPodCount(label, namespace string, count int) error {
  291. options := metav1.ListOptions{LabelSelector: label}
  292. ctx := context.TODO()
  293. for i := 0; i < RetryLoop; i++ {
  294. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, options)
  295. if err != nil {
  296. return fmt.Errorf("failed to find pod with label %s. %+v", label, err)
  297. }
  298. if len(pods.Items) >= count {
  299. logger.Infof("found %d pods with label %s", count, label)
  300. return nil
  301. }
  302. logger.Infof("waiting for %d pods (found %d) with label %s in namespace %s", count, len(pods.Items), label, namespace)
  303. time.Sleep(RetryInterval * time.Second)
  304. }
  305. return fmt.Errorf("Giving up waiting for pods with label %s in namespace %s", label, namespace)
  306. }
  307. func (k8sh *K8sHelper) WaitForStatusPhase(namespace, kind, name, desiredPhase string, timeout time.Duration) error {
  308. baseErr := fmt.Sprintf("waiting for resource %q %q in namespace %q to have status.phase %q", kind, name, namespace, desiredPhase)
  309. err := wait.PollUntilContextTimeout(context.TODO(), 3*time.Second, timeout, true, func(context context.Context) (done bool, err error) {
  310. phase, err := k8sh.GetResource("--namespace", namespace, kind, name, "--output", "jsonpath={.status.phase}")
  311. if err != nil {
  312. logger.Warningf("error %s. %v", baseErr, err)
  313. }
  314. if phase == desiredPhase {
  315. return true, nil
  316. }
  317. logger.Infof(baseErr)
  318. return false, nil
  319. })
  320. if err != nil {
  321. return errors.Wrapf(err, "failed %s", baseErr)
  322. }
  323. return nil
  324. }
  325. // IsPodWithLabelPresent return true if there is at least one Pod with the label is present.
  326. func (k8sh *K8sHelper) IsPodWithLabelPresent(label string, namespace string) bool {
  327. count, err := k8sh.CountPodsWithLabel(label, namespace)
  328. if err != nil {
  329. return false
  330. }
  331. return count > 0
  332. }
  333. // WaitForLabeledPodsToRun calls WaitForLabeledPodsToRunWithRetries with the default number of retries
  334. func (k8sh *K8sHelper) WaitForLabeledPodsToRun(label, namespace string) error {
  335. return k8sh.WaitForLabeledPodsToRunWithRetries(label, namespace, RetryLoop)
  336. }
  337. // WaitForLabeledPodsToRunWithRetries returns true if a Pod is running status or goes to Running status within 90s else returns false
  338. func (k8sh *K8sHelper) WaitForLabeledPodsToRunWithRetries(label string, namespace string, retries int) error {
  339. options := metav1.ListOptions{LabelSelector: label}
  340. ctx := context.TODO()
  341. var lastPod v1.Pod
  342. for i := 0; i < retries; i++ {
  343. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, options)
  344. lastStatus := ""
  345. running := 0
  346. if err == nil && len(pods.Items) > 0 {
  347. for _, pod := range pods.Items {
  348. if pod.Status.Phase == "Running" {
  349. running++
  350. }
  351. lastPod = pod
  352. lastStatus = string(pod.Status.Phase)
  353. }
  354. if running == len(pods.Items) {
  355. logger.Infof("All %d pod(s) with label %s are running", len(pods.Items), label)
  356. return nil
  357. }
  358. }
  359. logger.Infof("waiting for pod(s) with label %s in namespace %s to be running. status=%s, running=%d/%d, err=%+v",
  360. label, namespace, lastStatus, running, len(pods.Items), err)
  361. time.Sleep(RetryInterval * time.Second)
  362. }
  363. if len(lastPod.Name) == 0 {
  364. logger.Infof("no pod was found with label %s", label)
  365. } else {
  366. k8sh.PrintPodDescribe(namespace, lastPod.Name)
  367. }
  368. return fmt.Errorf("Giving up waiting for pod with label %s in namespace %s to be running", label, namespace)
  369. }
  370. // WaitUntilPodWithLabelDeleted returns true if a Pod is deleted within 90s else returns false
  371. func (k8sh *K8sHelper) WaitUntilPodWithLabelDeleted(label string, namespace string) bool {
  372. options := metav1.ListOptions{LabelSelector: label}
  373. ctx := context.TODO()
  374. for i := 0; i < RetryLoop; i++ {
  375. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, options)
  376. if kerrors.IsNotFound(err) {
  377. logger.Infof("error Found err %v", err)
  378. return true
  379. }
  380. if len(pods.Items) == 0 {
  381. logger.Infof("no (more) pods with label %s in namespace %s to be deleted", label, namespace)
  382. return true
  383. }
  384. time.Sleep(RetryInterval * time.Second)
  385. logger.Infof("waiting for pod with label %s in namespace %s to be deleted", label, namespace)
  386. }
  387. logger.Infof("Giving up waiting for pod with label %s in namespace %s to be deleted", label, namespace)
  388. return false
  389. }
  390. // PrintPodStatus log out the status phase of a pod
  391. func (k8sh *K8sHelper) PrintPodStatus(namespace string) {
  392. ctx := context.TODO()
  393. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
  394. if err != nil {
  395. logger.Errorf("failed to get pod status in namespace %s. %+v", namespace, err)
  396. return
  397. }
  398. for _, pod := range pods.Items {
  399. logger.Infof("%s (%s) pod status: %+v", pod.Name, namespace, pod.Status)
  400. }
  401. }
  402. func (k8sh *K8sHelper) GetPodRestartsFromNamespace(namespace, testName, platformName string) {
  403. logger.Infof("will alert if any pods were restarted in namespace %s", namespace)
  404. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
  405. if err != nil {
  406. logger.Errorf("failed to list pods in namespace %s. %+v", namespace, err)
  407. return
  408. }
  409. for _, pod := range pods.Items {
  410. podName := pod.Name
  411. for _, status := range pod.Status.ContainerStatuses {
  412. if strings.Contains(podName, status.Name) {
  413. if status.RestartCount > int32(0) {
  414. logger.Infof("number of time pod %s has restarted is %d", podName, status.RestartCount)
  415. }
  416. // Skipping `mgr` pod count to get the CI green and seems like this is related to ceph Reef.
  417. // Refer to this issue https://github.com/rook/rook/issues/12646 and remove once it is fixed.
  418. if !strings.Contains(podName, "rook-ceph-mgr") && status.RestartCount == int32(1) {
  419. assert.Equal(k8sh.T(), int32(0), status.RestartCount)
  420. }
  421. }
  422. }
  423. }
  424. }
  425. func (k8sh *K8sHelper) GetPodDescribeFromNamespace(namespace, testName, platformName string) {
  426. ctx := context.TODO()
  427. logger.Infof("Gathering pod describe for all pods in namespace %s", namespace)
  428. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
  429. if err != nil {
  430. logger.Errorf("failed to list pods in namespace %s. %+v", namespace, err)
  431. return
  432. }
  433. file, err := k8sh.createTestLogFile(platformName, "podDescribe", namespace, testName, "")
  434. if err != nil {
  435. return
  436. }
  437. defer file.Close()
  438. for _, p := range pods.Items {
  439. k8sh.appendPodDescribe(file, namespace, p.Name)
  440. }
  441. }
  442. func (k8sh *K8sHelper) GetEventsFromNamespace(namespace, testName, platformName string) {
  443. logger.Infof("Gathering events in namespace %q", namespace)
  444. file, err := k8sh.createTestLogFile(platformName, "events", namespace, testName, "")
  445. if err != nil {
  446. logger.Errorf("failed to create event file. %v", err)
  447. return
  448. }
  449. defer file.Close()
  450. args := []string{"get", "events", "-n", namespace}
  451. events, err := k8sh.Kubectl(args...)
  452. if err != nil {
  453. logger.Errorf("failed to get events. %v. %v", args, err)
  454. }
  455. if events == "" {
  456. return
  457. }
  458. file.WriteString(events) //nolint // ok to ignore this test logging
  459. }
  460. func (k8sh *K8sHelper) appendPodDescribe(file *os.File, namespace, name string) {
  461. description := k8sh.getPodDescribe(namespace, name)
  462. if description == "" {
  463. return
  464. }
  465. writeHeader(file, fmt.Sprintf("Pod: %s\n", name)) //nolint // ok to ignore this test logging
  466. file.WriteString(description) //nolint // ok to ignore this test logging
  467. file.WriteString("\n") //nolint // ok to ignore this test logging
  468. }
  469. func (k8sh *K8sHelper) PrintPodDescribe(namespace string, args ...string) {
  470. description := k8sh.getPodDescribe(namespace, args...)
  471. if description == "" {
  472. return
  473. }
  474. logger.Infof("POD Description:\n%s", description)
  475. }
  476. func (k8sh *K8sHelper) getPodDescribe(namespace string, args ...string) string {
  477. args = append([]string{"describe", "pod", "-n", namespace}, args...)
  478. description, err := k8sh.Kubectl(args...)
  479. if err != nil {
  480. logger.Errorf("failed to describe pod. %v %+v", args, err)
  481. return ""
  482. }
  483. return description
  484. }
  485. // IsPodRunning returns true if a Pod is running status or goes to Running status within 90s else returns false
  486. func (k8sh *K8sHelper) IsPodRunning(name string, namespace string) bool {
  487. ctx := context.TODO()
  488. getOpts := metav1.GetOptions{}
  489. for i := 0; i < 60; i++ {
  490. pod, err := k8sh.Clientset.CoreV1().Pods(namespace).Get(ctx, name, getOpts)
  491. if err == nil {
  492. if pod.Status.Phase == "Running" {
  493. return true
  494. }
  495. }
  496. time.Sleep(RetryInterval * time.Second)
  497. logger.Infof("waiting for pod %s in namespace %s to be running", name, namespace)
  498. }
  499. pod, _ := k8sh.Clientset.CoreV1().Pods(namespace).Get(ctx, name, getOpts)
  500. k8sh.PrintPodDescribe(namespace, pod.Name)
  501. logger.Infof("Giving up waiting for pod %s in namespace %s to be running", name, namespace)
  502. return false
  503. }
  504. // IsPodTerminated wrapper around IsPodTerminatedWithOpts()
  505. func (k8sh *K8sHelper) IsPodTerminated(name string, namespace string) bool {
  506. return k8sh.IsPodTerminatedWithOpts(name, namespace, metav1.GetOptions{})
  507. }
  508. // IsPodTerminatedWithOpts returns true if a Pod is terminated status or goes to Terminated status
  509. // within 90s else returns false\
  510. func (k8sh *K8sHelper) IsPodTerminatedWithOpts(name string, namespace string, getOpts metav1.GetOptions) bool {
  511. ctx := context.TODO()
  512. for i := 0; i < RetryLoop; i++ {
  513. pod, err := k8sh.Clientset.CoreV1().Pods(namespace).Get(ctx, name, getOpts)
  514. if err != nil {
  515. k8slogger.Infof("Pod %s in namespace %s terminated ", name, namespace)
  516. return true
  517. }
  518. k8slogger.Infof("waiting for Pod %s in namespace %s to terminate, status : %+v", name, namespace, pod.Status)
  519. time.Sleep(RetryInterval * time.Second)
  520. }
  521. k8slogger.Infof("Pod %s in namespace %s did not terminate", name, namespace)
  522. return false
  523. }
  524. // IsServiceUp returns true if a service is up or comes up within 150s, else returns false
  525. func (k8sh *K8sHelper) IsServiceUp(name string, namespace string) bool {
  526. getOpts := metav1.GetOptions{}
  527. ctx := context.TODO()
  528. for i := 0; i < RetryLoop; i++ {
  529. _, err := k8sh.Clientset.CoreV1().Services(namespace).Get(ctx, name, getOpts)
  530. if err == nil {
  531. k8slogger.Infof("Service: %s in namespace: %s is up", name, namespace)
  532. return true
  533. }
  534. k8slogger.Infof("waiting for Service %s in namespace %s ", name, namespace)
  535. time.Sleep(RetryInterval * time.Second)
  536. }
  537. k8slogger.Infof("Giving up waiting for service: %s in namespace %s ", name, namespace)
  538. return false
  539. }
  540. // GetService returns output from "kubectl get svc $NAME" command
  541. func (k8sh *K8sHelper) GetService(servicename string, namespace string) (*v1.Service, error) {
  542. getOpts := metav1.GetOptions{}
  543. ctx := context.TODO()
  544. result, err := k8sh.Clientset.CoreV1().Services(namespace).Get(ctx, servicename, getOpts)
  545. if err != nil {
  546. return nil, fmt.Errorf("Cannot find service %s in namespace %s, err-- %v", servicename, namespace, err)
  547. }
  548. return result, nil
  549. }
  550. // IsCRDPresent returns true if custom resource definition is present
  551. func (k8sh *K8sHelper) IsCRDPresent(crdName string) bool {
  552. cmdArgs := []string{"get", "crd", crdName}
  553. for i := 0; i < RetryLoop; i++ {
  554. _, err := k8sh.Kubectl(cmdArgs...)
  555. if err == nil {
  556. k8slogger.Infof("Found the CRD resource: " + crdName)
  557. return true
  558. }
  559. time.Sleep(RetryInterval * time.Second)
  560. }
  561. return false
  562. }
  563. // WriteToPod write file in Pod
  564. func (k8sh *K8sHelper) WriteToPod(namespace, podName, filename, message string) error {
  565. return k8sh.WriteToPodRetry(namespace, podName, filename, message, 1)
  566. }
  567. // WriteToPodRetry WriteToPod in a retry loop
  568. func (k8sh *K8sHelper) WriteToPodRetry(namespace, podName, filename, message string, retries int) error {
  569. logger.Infof("Writing file %s to pod %s", filename, podName)
  570. var err error
  571. for i := 0; i < retries; i++ {
  572. if i > 0 {
  573. logger.Infof("retrying write in 5s...")
  574. time.Sleep(5 * time.Second)
  575. }
  576. err = k8sh.writeToPod(namespace, podName, filename, message)
  577. if err == nil {
  578. logger.Infof("write file %s in pod %s was successful", filename, podName)
  579. return nil
  580. }
  581. }
  582. return fmt.Errorf("failed to write file %s to pod %s. %+v", filename, podName, err)
  583. }
  584. func (k8sh *K8sHelper) ReadFromPod(namespace, podName, filename, expectedMessage string) error {
  585. return k8sh.ReadFromPodRetry(namespace, podName, filename, expectedMessage, 1)
  586. }
  587. func (k8sh *K8sHelper) ReadFromPodRetry(namespace, podName, filename, expectedMessage string, retries int) error {
  588. logger.Infof("Reading file %s from pod %s", filename, podName)
  589. var err error
  590. for i := 0; i < retries; i++ {
  591. if i > 0 {
  592. logger.Infof("retrying read in 5s...")
  593. time.Sleep(5 * time.Second)
  594. }
  595. var data string
  596. data, err = k8sh.readFromPod(namespace, podName, filename)
  597. if err == nil {
  598. logger.Infof("read file %s from pod %s was successful after %d attempt(s)", filename, podName, (i + 1))
  599. if !strings.Contains(data, expectedMessage) {
  600. return fmt.Errorf(`file %s in pod %s returned message "%s" instead of "%s"`, filename, podName, data, expectedMessage)
  601. }
  602. return nil
  603. }
  604. }
  605. return fmt.Errorf("failed to read file %s from pod %s. %+v", filename, podName, err)
  606. }
  607. func (k8sh *K8sHelper) writeToPod(namespace, name, filename, message string) error {
  608. wt := "echo \"" + message + "\">" + path.Join(TestMountPath, filename)
  609. args := []string{"exec", name}
  610. if namespace != "" {
  611. args = append(args, "-n", namespace)
  612. }
  613. args = append(args, "--", "sh", "-c", wt)
  614. _, err := k8sh.Kubectl(args...)
  615. if err != nil {
  616. return fmt.Errorf("failed to write file %s to pod %s. %+v", filename, name, err)
  617. }
  618. return nil
  619. }
  620. // RunCommandInPod runs the provided command inside the pod
  621. func (k8sh *K8sHelper) RunCommandInPod(namespace, name, cmd string) (string, error) {
  622. args := []string{"exec", name}
  623. if namespace != "" {
  624. args = append(args, "-n", namespace)
  625. }
  626. args = append(args, "--", "sh", "-c", cmd)
  627. resp, err := k8sh.Kubectl(args...)
  628. if err != nil {
  629. return "", fmt.Errorf("failed to execute command %q in pod %s. %+v", cmd, name, err)
  630. }
  631. return resp, err
  632. }
  633. func (k8sh *K8sHelper) readFromPod(namespace, name, filename string) (string, error) {
  634. rd := path.Join(TestMountPath, filename)
  635. args := []string{"exec", name}
  636. if namespace != "" {
  637. args = append(args, "-n", namespace)
  638. }
  639. args = append(args, "--", "cat", rd)
  640. result, err := k8sh.Kubectl(args...)
  641. if err != nil {
  642. return "", fmt.Errorf("failed to read file %s from pod %s. %+v", filename, name, err)
  643. }
  644. return result, nil
  645. }
  646. // GetVolumeResourceName gets the Volume object name from the PVC
  647. func (k8sh *K8sHelper) GetVolumeResourceName(namespace, pvcName string) (string, error) {
  648. ctx := context.TODO()
  649. getOpts := metav1.GetOptions{}
  650. pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, getOpts)
  651. if err != nil {
  652. return "", err
  653. }
  654. return pvc.Spec.VolumeName, nil
  655. }
  656. func (k8sh *K8sHelper) PrintPVs(detailed bool) {
  657. ctx := context.TODO()
  658. pvs, err := k8sh.Clientset.CoreV1().PersistentVolumes().List(ctx, metav1.ListOptions{})
  659. if err != nil {
  660. logger.Errorf("failed to list pvs. %+v", err)
  661. return
  662. }
  663. if detailed {
  664. logger.Infof("Found %d PVs", len(pvs.Items))
  665. for _, pv := range pvs.Items {
  666. logger.Infof("PV %s: %+v", pv.Name, pv)
  667. }
  668. } else {
  669. var names []string
  670. for _, pv := range pvs.Items {
  671. names = append(names, pv.Name)
  672. }
  673. logger.Infof("Found PVs: %v", names)
  674. }
  675. }
  676. func (k8sh *K8sHelper) PrintPVCs(namespace string, detailed bool) {
  677. ctx := context.TODO()
  678. pvcs, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(ctx, metav1.ListOptions{})
  679. if err != nil {
  680. logger.Errorf("failed to list pvcs. %+v", err)
  681. return
  682. }
  683. if detailed {
  684. logger.Infof("Found %d PVCs", len(pvcs.Items))
  685. for _, pvc := range pvcs.Items {
  686. logger.Infof("PVC %s: %+v", pvc.Name, pvc)
  687. }
  688. } else {
  689. var names []string
  690. for _, pvc := range pvcs.Items {
  691. names = append(names, pvc.Name)
  692. }
  693. logger.Infof("Found PVCs: %v", names)
  694. }
  695. }
  696. func (k8sh *K8sHelper) PrintResources(namespace, name string) {
  697. args := []string{"-n", namespace, "get", name, "-o", "yaml"}
  698. result, err := k8sh.Kubectl(args...)
  699. if err != nil {
  700. logger.Warningf("failed to get resource %s. %v", name, err)
  701. } else {
  702. logger.Infof("%s\n", result)
  703. }
  704. }
  705. func (k8sh *K8sHelper) PrintStorageClasses(detailed bool) {
  706. ctx := context.TODO()
  707. scs, err := k8sh.Clientset.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{})
  708. if err != nil {
  709. logger.Errorf("failed to list StorageClasses: %+v", err)
  710. return
  711. }
  712. if detailed {
  713. logger.Infof("Found %d StorageClasses", len(scs.Items))
  714. for _, sc := range scs.Items {
  715. logger.Infof("StorageClass %s: %+v", sc.Name, sc)
  716. }
  717. } else {
  718. var names []string
  719. for _, sc := range scs.Items {
  720. names = append(names, sc.Name)
  721. }
  722. logger.Infof("Found StorageClasses: %v", names)
  723. }
  724. }
  725. func (k8sh *K8sHelper) GetPodNamesForApp(appName, namespace string) ([]string, error) {
  726. args := []string{"get", "pod", "-n", namespace, "-l", fmt.Sprintf("app=%s", appName),
  727. "-o", "jsonpath={.items[*].metadata.name}"}
  728. result, err := k8sh.Kubectl(args...)
  729. if err != nil {
  730. return nil, fmt.Errorf("failed to get pod names for app %s: %+v. output: %s", appName, err, result)
  731. }
  732. podNames := strings.Split(result, " ")
  733. return podNames, nil
  734. }
  735. // GetPodDetails returns details about a pod
  736. func (k8sh *K8sHelper) GetPodDetails(podNamePattern string, namespace string) (string, error) {
  737. args := []string{"get", "pods", "-l", "app=" + podNamePattern, "-o", "wide", "--no-headers=true", "-o", "name"}
  738. if namespace != "" {
  739. args = append(args, []string{"-n", namespace}...)
  740. }
  741. result, err := k8sh.Kubectl(args...)
  742. if err != nil || strings.Contains(result, "No resources found") {
  743. return "", fmt.Errorf("Cannot find pod in with name like %s in namespace : %s -- %v", podNamePattern, namespace, err)
  744. }
  745. return strings.TrimSpace(result), nil
  746. }
  747. // GetPodEvents returns events about a pod
  748. func (k8sh *K8sHelper) GetPodEvents(podNamePattern string, namespace string) (*v1.EventList, error) {
  749. ctx := context.TODO()
  750. uri := fmt.Sprintf("api/v1/namespaces/%s/events?fieldSelector=involvedObject.name=%s,involvedObject.namespace=%s", namespace, podNamePattern, namespace)
  751. result, err := k8sh.Clientset.CoreV1().RESTClient().Get().RequestURI(uri).DoRaw(ctx)
  752. if err != nil {
  753. logger.Errorf("Cannot get events for pod %v in namespace %v, err: %v", podNamePattern, namespace, err)
  754. return nil, fmt.Errorf("Cannot get events for pod %s in namespace %s, err: %v", podNamePattern, namespace, err)
  755. }
  756. events := v1.EventList{}
  757. err = json.Unmarshal(result, &events)
  758. if err != nil {
  759. return nil, fmt.Errorf("failed to unmarshal eventlist response: %v", err)
  760. }
  761. return &events, nil
  762. }
  763. // IsPodInError returns true if a Pod is in error status with the given reason and contains the given message
  764. func (k8sh *K8sHelper) IsPodInError(podNamePattern, namespace, reason, containingMessage string) bool {
  765. for i := 0; i < RetryLoop; i++ {
  766. events, err := k8sh.GetPodEvents(podNamePattern, namespace)
  767. if err != nil {
  768. k8slogger.Errorf("Cannot get Pod events for %s in namespace %s: %+v ", podNamePattern, namespace, err)
  769. return false
  770. }
  771. for _, e := range events.Items {
  772. if e.Reason == reason && strings.Contains(e.Message, containingMessage) {
  773. return true
  774. }
  775. }
  776. k8slogger.Infof("waiting for Pod %s in namespace %s to error with reason %s and containing the message: %s", podNamePattern, namespace, reason, containingMessage)
  777. time.Sleep(RetryInterval * time.Second)
  778. }
  779. k8slogger.Infof("Pod %s in namespace %s did not error with reason %s", podNamePattern, namespace, reason)
  780. return false
  781. }
  782. // GetPodHostIP returns HostIP address of a pod
  783. func (k8sh *K8sHelper) GetPodHostIP(podNamePattern string, namespace string) (string, error) {
  784. ctx := context.TODO()
  785. listOpts := metav1.ListOptions{LabelSelector: "app=" + podNamePattern}
  786. podList, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, listOpts)
  787. if err != nil {
  788. logger.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
  789. return "", fmt.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
  790. }
  791. if len(podList.Items) < 1 {
  792. logger.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
  793. return "", fmt.Errorf("Cannot get hostIp for app : %v in namespace %v, err: %v", podNamePattern, namespace, err)
  794. }
  795. return podList.Items[0].Status.HostIP, nil
  796. }
  797. // GetServiceNodePort returns nodeProt of service
  798. func (k8sh *K8sHelper) GetServiceNodePort(serviceName string, namespace string) (string, error) {
  799. ctx := context.TODO()
  800. getOpts := metav1.GetOptions{}
  801. svc, err := k8sh.Clientset.CoreV1().Services(namespace).Get(ctx, serviceName, getOpts)
  802. if err != nil {
  803. logger.Errorf("Cannot get service : %v in namespace %v, err: %v", serviceName, namespace, err)
  804. return "", fmt.Errorf("Cannot get service : %v in namespace %v, err: %v", serviceName, namespace, err)
  805. }
  806. np := svc.Spec.Ports[0].NodePort
  807. return strconv.FormatInt(int64(np), 10), nil
  808. }
  809. // IsStorageClassPresent returns true if storageClass is present, if not false
  810. func (k8sh *K8sHelper) IsStorageClassPresent(name string) (bool, error) {
  811. args := []string{"get", "storageclass", "-o", "jsonpath='{.items[*].metadata.name}'"}
  812. result, err := k8sh.Kubectl(args...)
  813. if strings.Contains(result, name) {
  814. return true, nil
  815. }
  816. return false, fmt.Errorf("Storageclass %s not found, err ->%v", name, err)
  817. }
  818. func (k8sh *K8sHelper) IsDefaultStorageClassPresent() (bool, error) {
  819. ctx := context.TODO()
  820. scs, err := k8sh.Clientset.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{})
  821. if err != nil {
  822. return false, fmt.Errorf("failed to list StorageClasses: %+v", err)
  823. }
  824. for _, sc := range scs.Items {
  825. if isDefaultAnnotation(sc.ObjectMeta) {
  826. return true, nil
  827. }
  828. }
  829. return false, nil
  830. }
  831. // CheckPvcCount returns True if expected number pvs for a app are found
  832. func (k8sh *K8sHelper) CheckPvcCountAndStatus(podName string, namespace string, expectedPvcCount int, expectedStatus string) bool {
  833. logger.Infof("wait until %d pvc for app=%s are present", expectedPvcCount, podName)
  834. listOpts := metav1.ListOptions{LabelSelector: "app=" + podName}
  835. pvcCountCheck := false
  836. ctx := context.TODO()
  837. actualPvcCount := 0
  838. for i := 0; i < RetryLoop; i++ {
  839. pvcList, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(ctx, listOpts)
  840. if err != nil {
  841. logger.Errorf("Cannot get pvc for app : %v in namespace %v, err: %v", podName, namespace, err)
  842. return false
  843. }
  844. actualPvcCount = len(pvcList.Items)
  845. if actualPvcCount == expectedPvcCount {
  846. pvcCountCheck = true
  847. break
  848. }
  849. time.Sleep(RetryInterval * time.Second)
  850. }
  851. if !pvcCountCheck {
  852. logger.Errorf("Expecting %d number of PVCs for %s app, found %d ", expectedPvcCount, podName, actualPvcCount)
  853. return false
  854. }
  855. for i := 0; i < RetryLoop; i++ {
  856. checkAllPVCsStatus := true
  857. pl, _ := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(ctx, listOpts)
  858. for _, pvc := range pl.Items {
  859. if !(pvc.Status.Phase == v1.PersistentVolumeClaimPhase(expectedStatus)) {
  860. checkAllPVCsStatus = false
  861. logger.Infof("waiting for pvc %v to be in %s Phase, currently in %v Phase", pvc.Name, expectedStatus, pvc.Status.Phase)
  862. }
  863. }
  864. if checkAllPVCsStatus {
  865. return true
  866. }
  867. time.Sleep(RetryInterval * time.Second)
  868. }
  869. logger.Errorf("Giving up waiting for %d PVCs for %s app to be in %s phase", expectedPvcCount, podName, expectedStatus)
  870. return false
  871. }
  872. // GetPVCStatus returns status of PVC
  873. func (k8sh *K8sHelper) GetPVCStatus(namespace string, name string) (v1.PersistentVolumeClaimPhase, error) {
  874. getOpts := metav1.GetOptions{}
  875. ctx := context.TODO()
  876. pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, getOpts)
  877. if err != nil {
  878. return v1.ClaimLost, fmt.Errorf("PVC %s not found,err->%v", name, err)
  879. }
  880. return pvc.Status.Phase, nil
  881. }
  882. // GetPVCVolumeName returns volume name of PVC
  883. func (k8sh *K8sHelper) GetPVCVolumeName(namespace string, name string) (string, error) {
  884. getOpts := metav1.GetOptions{}
  885. ctx := context.TODO()
  886. pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, getOpts)
  887. if err != nil {
  888. return "", fmt.Errorf("PVC %s not found,err->%v", name, err)
  889. }
  890. return pvc.Spec.VolumeName, nil
  891. }
  892. // GetPVCAccessModes returns AccessModes on PVC
  893. func (k8sh *K8sHelper) GetPVCAccessModes(namespace string, name string) ([]v1.PersistentVolumeAccessMode, error) {
  894. getOpts := metav1.GetOptions{}
  895. ctx := context.TODO()
  896. pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, getOpts)
  897. if err != nil {
  898. return []v1.PersistentVolumeAccessMode{}, fmt.Errorf("PVC %s not found,err->%v", name, err)
  899. }
  900. return pvc.Status.AccessModes, nil
  901. }
  902. // GetPV returns PV by name
  903. func (k8sh *K8sHelper) GetPV(name string) (*v1.PersistentVolume, error) {
  904. getOpts := metav1.GetOptions{}
  905. ctx := context.TODO()
  906. pv, err := k8sh.Clientset.CoreV1().PersistentVolumes().Get(ctx, name, getOpts)
  907. if err != nil {
  908. return nil, fmt.Errorf("PV %s not found,err->%v", name, err)
  909. }
  910. return pv, nil
  911. }
  912. // IsPodInExpectedState waits for 90s for a pod to be an expected state
  913. // If the pod is in expected state within 90s true is returned, if not false
  914. func (k8sh *K8sHelper) IsPodInExpectedState(podNamePattern string, namespace string, state string) bool {
  915. listOpts := metav1.ListOptions{LabelSelector: "app=" + podNamePattern}
  916. ctx := context.TODO()
  917. for i := 0; i < RetryLoop; i++ {
  918. podList, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, listOpts)
  919. if err == nil {
  920. for _, pod := range podList.Items {
  921. if pod.Status.Phase == v1.PodPhase(state) {
  922. return true
  923. }
  924. }
  925. }
  926. logger.Infof("waiting for pod with label app=%s in namespace %q to be in state %q...", podNamePattern, namespace, state)
  927. time.Sleep(RetryInterval * time.Second)
  928. }
  929. return false
  930. }
  931. // CheckPodCountAndState returns true if expected number of pods with matching name are found and are in expected state
  932. func (k8sh *K8sHelper) CheckPodCountAndState(podName string, namespace string, minExpected int, expectedPhase string) bool {
  933. listOpts := metav1.ListOptions{LabelSelector: "app=" + podName}
  934. podCountCheck := false
  935. actualPodCount := 0
  936. ctx := context.TODO()
  937. for i := 0; i < RetryLoop; i++ {
  938. podList, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, listOpts)
  939. if err != nil {
  940. logger.Errorf("Cannot list pods for app=%s in namespace %s, err: %+v", podName, namespace, err)
  941. return false
  942. }
  943. actualPodCount = len(podList.Items)
  944. if actualPodCount >= minExpected {
  945. logger.Infof("%d of %d pods with label app=%s were found", actualPodCount, minExpected, podName)
  946. podCountCheck = true
  947. break
  948. }
  949. logger.Infof("waiting for %d pods with label app=%s, found %d", minExpected, podName, actualPodCount)
  950. time.Sleep(RetryInterval * time.Second)
  951. }
  952. if !podCountCheck {
  953. logger.Errorf("Expecting %d number of pods for %s app, found %d ", minExpected, podName, actualPodCount)
  954. return false
  955. }
  956. for i := 0; i < RetryLoop; i++ {
  957. checkAllPodsStatus := true
  958. pl, _ := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, listOpts)
  959. for _, pod := range pl.Items {
  960. if !(pod.Status.Phase == v1.PodPhase(expectedPhase)) {
  961. checkAllPodsStatus = false
  962. logger.Infof("waiting for pod %v to be in %s Phase, currently in %v Phase", pod.Name, expectedPhase, pod.Status.Phase)
  963. }
  964. }
  965. if checkAllPodsStatus {
  966. return true
  967. }
  968. time.Sleep(RetryInterval * time.Second)
  969. }
  970. logger.Errorf("All pods with app Name %v not in %v phase ", podName, expectedPhase)
  971. k8sh.PrintPodDescribe(namespace, "-l", listOpts.LabelSelector)
  972. return false
  973. }
  974. // WaitUntilPodInNamespaceIsDeleted waits for 90s for a pod in a namespace to be terminated
  975. // If the pod disappears within 90s true is returned, if not false
  976. func (k8sh *K8sHelper) WaitUntilPodInNamespaceIsDeleted(podNamePattern string, namespace string) bool {
  977. for i := 0; i < RetryLoop; i++ {
  978. out, _ := k8sh.GetResource("-n", namespace, "pods", "-l", "app="+podNamePattern)
  979. if !strings.Contains(out, podNamePattern) {
  980. return true
  981. }
  982. time.Sleep(RetryInterval * time.Second)
  983. }
  984. logger.Infof("Pod %s in namespace %s not deleted", podNamePattern, namespace)
  985. return false
  986. }
  987. // WaitUntilPodIsDeleted waits for 90s for a pod to be terminated
  988. // If the pod disappears within 90s true is returned, if not false
  989. func (k8sh *K8sHelper) WaitUntilPodIsDeleted(name, namespace string) bool {
  990. ctx := context.TODO()
  991. for i := 0; i < RetryLoop; i++ {
  992. _, err := k8sh.Clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
  993. if err != nil && kerrors.IsNotFound(err) {
  994. return true
  995. }
  996. logger.Infof("pod %s in namespace %s is not deleted yet", name, namespace)
  997. time.Sleep(RetryInterval * time.Second)
  998. }
  999. return false
  1000. }
  1001. // WaitUntilPVCIsBound waits for a PVC to be in bound state for 90 seconds
  1002. // if PVC goes to Bound state within 90s True is returned, if not false
  1003. func (k8sh *K8sHelper) WaitUntilPVCIsBound(namespace string, pvcname string) bool {
  1004. for i := 0; i < RetryLoop; i++ {
  1005. out, err := k8sh.GetPVCStatus(namespace, pvcname)
  1006. if err == nil {
  1007. if out == v1.PersistentVolumeClaimPhase(v1.ClaimBound) {
  1008. logger.Infof("PVC %s is bound", pvcname)
  1009. return true
  1010. }
  1011. }
  1012. logger.Infof("waiting for PVC %s to be bound. current=%s. err=%+v", pvcname, out, err)
  1013. time.Sleep(RetryInterval * time.Second)
  1014. }
  1015. return false
  1016. }
  1017. // WaitUntilPVCIsExpanded waits for a PVC to be resized for specified value
  1018. func (k8sh *K8sHelper) WaitUntilPVCIsExpanded(namespace, pvcname, size string) bool {
  1019. getOpts := metav1.GetOptions{}
  1020. ctx := context.TODO()
  1021. for i := 0; i < RetryLoop; i++ {
  1022. // PVC specs changes immediately, but status will change only if resize process is successfully completed.
  1023. pvc, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcname, getOpts)
  1024. if err == nil {
  1025. currentSize := pvc.Status.Capacity[v1.ResourceStorage]
  1026. if currentSize.String() == size {
  1027. logger.Infof("PVC %s is resized", pvcname)
  1028. return true
  1029. }
  1030. logger.Infof("waiting for PVC %s to be resized, current: %s, expected: %s", pvcname, currentSize.String(), size)
  1031. } else {
  1032. logger.Infof("error while getting PVC specs: %+v", err)
  1033. }
  1034. time.Sleep(RetryInterval * time.Second)
  1035. }
  1036. return false
  1037. }
  1038. func (k8sh *K8sHelper) WaitUntilPVCIsDeleted(namespace string, pvcname string) bool {
  1039. getOpts := metav1.GetOptions{}
  1040. ctx := context.TODO()
  1041. for i := 0; i < RetryLoop; i++ {
  1042. _, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcname, getOpts)
  1043. if err != nil && kerrors.IsNotFound(err) {
  1044. return true
  1045. }
  1046. logger.Infof("waiting for PVC %s to be deleted.", pvcname)
  1047. time.Sleep(RetryInterval * time.Second)
  1048. }
  1049. return false
  1050. }
  1051. func (k8sh *K8sHelper) WaitUntilZeroPVs() bool {
  1052. ListOpts := metav1.ListOptions{}
  1053. ctx := context.TODO()
  1054. for i := 0; i < RetryLoop; i++ {
  1055. pvList, err := k8sh.Clientset.CoreV1().PersistentVolumes().List(ctx, ListOpts)
  1056. if err != nil && kerrors.IsNotFound(err) {
  1057. return true
  1058. }
  1059. if len(pvList.Items) == 0 {
  1060. return true
  1061. }
  1062. logger.Infof("waiting for PV count to be zero.")
  1063. time.Sleep(RetryInterval * time.Second)
  1064. }
  1065. return false
  1066. }
  1067. func (k8sh *K8sHelper) DeletePvcWithLabel(namespace string, podName string) bool {
  1068. delOpts := metav1.DeleteOptions{}
  1069. listOpts := metav1.ListOptions{LabelSelector: "app=" + podName}
  1070. ctx := context.TODO()
  1071. err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).DeleteCollection(ctx, delOpts, listOpts)
  1072. if err != nil {
  1073. logger.Errorf("cannot deleted PVCs for pods with label app=%s", podName)
  1074. return false
  1075. }
  1076. for i := 0; i < RetryLoop; i++ {
  1077. pvcs, err := k8sh.Clientset.CoreV1().PersistentVolumeClaims(namespace).List(ctx, listOpts)
  1078. if err == nil {
  1079. if len(pvcs.Items) == 0 {
  1080. return true
  1081. }
  1082. }
  1083. logger.Infof("waiting for PVCs for pods with label=%s to be deleted.", podName)
  1084. time.Sleep(RetryInterval * time.Second)
  1085. }
  1086. return false
  1087. }
  1088. // WaitUntilNameSpaceIsDeleted waits for namespace to be deleted for 180s.
  1089. // If namespace is deleted True is returned, if not false.
  1090. func (k8sh *K8sHelper) WaitUntilNameSpaceIsDeleted(namespace string) bool {
  1091. getOpts := metav1.GetOptions{}
  1092. ctx := context.TODO()
  1093. for i := 0; i < RetryLoop; i++ {
  1094. ns, err := k8sh.Clientset.CoreV1().Namespaces().Get(ctx, namespace, getOpts)
  1095. if err != nil {
  1096. return true
  1097. }
  1098. logger.Infof("Namespace %s %v", namespace, ns.Status.Phase)
  1099. time.Sleep(RetryInterval * time.Second)
  1100. }
  1101. return false
  1102. }
  1103. // CreateExternalRGWService creates a service for rgw access external to the cluster on a node port
  1104. func (k8sh *K8sHelper) CreateExternalRGWService(namespace, storeName string) error {
  1105. svcName := "rgw-external-" + storeName
  1106. externalSvc := `apiVersion: v1
  1107. kind: Service
  1108. metadata:
  1109. name: ` + svcName + `
  1110. namespace: ` + namespace + `
  1111. labels:
  1112. app: rook-ceph-rgw
  1113. rook_cluster: ` + namespace + `
  1114. spec:
  1115. ports:
  1116. - name: rook-ceph-rgw
  1117. port: 53390
  1118. protocol: TCP
  1119. selector:
  1120. app: rook-ceph-rgw
  1121. rook_cluster: ` + namespace + `
  1122. sessionAffinity: None
  1123. type: NodePort
  1124. `
  1125. _, err := k8sh.KubectlWithStdin(externalSvc, []string{"apply", "-f", "-"}...)
  1126. if err != nil && !kerrors.IsAlreadyExists(err) {
  1127. return fmt.Errorf("failed to create external service. %+v", err)
  1128. }
  1129. return nil
  1130. }
  1131. func (k8sh *K8sHelper) GetRGWServiceURL(storeName string, namespace string) (string, error) {
  1132. if k8sh.RunningInCluster {
  1133. return k8sh.getInternalRGWServiceURL(storeName, namespace)
  1134. }
  1135. return k8sh.getExternalRGWServiceURL(storeName, namespace)
  1136. }
  1137. // GetRGWServiceURL returns URL of ceph RGW service in the cluster
  1138. func (k8sh *K8sHelper) getInternalRGWServiceURL(storeName string, namespace string) (string, error) {
  1139. name := "rook-ceph-rgw-" + storeName
  1140. svc, err := k8sh.GetService(name, namespace)
  1141. if err != nil {
  1142. return "", fmt.Errorf("RGW service not found/object. %+v", err)
  1143. }
  1144. endpoint := fmt.Sprintf("%s:%d", svc.Spec.ClusterIP, svc.Spec.Ports[0].Port)
  1145. logger.Infof("internal rgw endpoint: %s", endpoint)
  1146. return endpoint, nil
  1147. }
  1148. // GetRGWServiceURL returns URL of ceph RGW service in the cluster
  1149. func (k8sh *K8sHelper) getExternalRGWServiceURL(storeName string, namespace string) (string, error) {
  1150. hostip, err := k8sh.GetPodHostIP("rook-ceph-rgw", namespace)
  1151. if err != nil {
  1152. return "", fmt.Errorf("RGW pods not found. %+v", err)
  1153. }
  1154. serviceName := "rgw-external-" + storeName
  1155. nodePort, err := k8sh.GetServiceNodePort(serviceName, namespace)
  1156. if err != nil {
  1157. return "", fmt.Errorf("RGW service not found. %+v", err)
  1158. }
  1159. endpoint := hostip + ":" + nodePort
  1160. logger.Infof("external rgw endpoint: %s", endpoint)
  1161. return endpoint, err
  1162. }
  1163. // ChangeHostnames modifies the node hostname label to run tests in an environment where the node name is different from the hostname label
  1164. func (k8sh *K8sHelper) ChangeHostnames() error {
  1165. ctx := context.TODO()
  1166. nodes, err := k8sh.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
  1167. if err != nil {
  1168. return err
  1169. }
  1170. for _, node := range nodes.Items {
  1171. hostname := node.Labels[v1.LabelHostname]
  1172. if !strings.HasPrefix(hostname, hostnameTestPrefix) {
  1173. node.Labels[v1.LabelHostname] = hostnameTestPrefix + hostname
  1174. logger.Infof("changed hostname of node %s to %s", node.Name, node.Labels[v1.LabelHostname])
  1175. _, err := k8sh.Clientset.CoreV1().Nodes().Update(ctx, &node, metav1.UpdateOptions{}) //nolint:gosec // We safely suppress gosec in tests file
  1176. if err != nil {
  1177. return err
  1178. }
  1179. }
  1180. }
  1181. return nil
  1182. }
  1183. // RestoreHostnames removes the test suffix from the node hostname labels
  1184. func (k8sh *K8sHelper) RestoreHostnames() ([]string, error) {
  1185. ctx := context.TODO()
  1186. nodes, err := k8sh.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
  1187. if err != nil {
  1188. return nil, err
  1189. }
  1190. for _, node := range nodes.Items {
  1191. hostname := node.Labels[v1.LabelHostname]
  1192. if strings.HasPrefix(hostname, hostnameTestPrefix) {
  1193. node.Labels[v1.LabelHostname] = hostname[len(hostnameTestPrefix):]
  1194. logger.Infof("restoring hostname of node %s to %s", node.Name, node.Labels[v1.LabelHostname])
  1195. _, err := k8sh.Clientset.CoreV1().Nodes().Update(ctx, &node, metav1.UpdateOptions{}) //nolint:gosec // We safely suppress gosec in tests file
  1196. if err != nil {
  1197. return nil, err
  1198. }
  1199. }
  1200. }
  1201. return nil, nil
  1202. }
  1203. // IsRookInstalled returns true is rook-ceph-mgr service is running(indicating rook is installed)
  1204. func (k8sh *K8sHelper) IsRookInstalled(namespace string) bool {
  1205. ctx := context.TODO()
  1206. opts := metav1.GetOptions{}
  1207. _, err := k8sh.Clientset.CoreV1().Services(namespace).Get(ctx, "rook-ceph-mgr", opts)
  1208. return err == nil
  1209. }
  1210. // CollectPodLogsFromLabel collects logs for pods with the given label
  1211. func (k8sh *K8sHelper) CollectPodLogsFromLabel(podLabel, namespace, testName, platformName string) {
  1212. ctx := context.TODO()
  1213. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: podLabel})
  1214. if err != nil {
  1215. logger.Errorf("failed to list pods in namespace %s. %+v", namespace, err)
  1216. return
  1217. }
  1218. k8sh.getPodsLogs(pods, namespace, testName, platformName)
  1219. }
  1220. // GetLogsFromNamespace collects logs for all containers in all pods in the namespace
  1221. func (k8sh *K8sHelper) GetLogsFromNamespace(namespace, testName, platformName string) {
  1222. ctx := context.TODO()
  1223. logger.Infof("Gathering logs for all pods in namespace %s", namespace)
  1224. pods, err := k8sh.Clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
  1225. if err != nil {
  1226. logger.Errorf("failed to list pods in namespace %s. %+v", namespace, err)
  1227. return
  1228. }
  1229. k8sh.getPodsLogs(pods, namespace, testName, platformName)
  1230. }
  1231. func (k8sh *K8sHelper) getPodsLogs(pods *v1.PodList, namespace, testName, platformName string) {
  1232. for _, p := range pods.Items {
  1233. k8sh.getPodLogs(p, platformName, namespace, testName, false)
  1234. if strings.Contains(p.Name, "operator") {
  1235. // get the previous logs for the operator
  1236. k8sh.getPodLogs(p, platformName, namespace, testName, true)
  1237. }
  1238. }
  1239. }
  1240. func (k8sh *K8sHelper) createTestLogFile(platformName, name, namespace, testName, suffix string) (*os.File, error) {
  1241. dir, _ := os.Getwd()
  1242. logDir := path.Join(dir, "_output/tests/")
  1243. if _, err := os.Stat(logDir); os.IsNotExist(err) {
  1244. err := os.MkdirAll(logDir, 0777)
  1245. if err != nil {
  1246. logger.Errorf("Cannot get logs files dir for app : %v in namespace %v, err: %v", name, namespace, err)
  1247. return nil, err
  1248. }
  1249. }
  1250. fileName := fmt.Sprintf("%s_%s_%s_%s%s_%d.log", testName, platformName, namespace, name, suffix, time.Now().Unix())
  1251. filePath := path.Join(logDir, strings.ReplaceAll(fileName, "/", "_"))
  1252. file, err := os.Create(filePath)
  1253. if err != nil {
  1254. logger.Errorf("Cannot create file %s. %v", filePath, err)
  1255. return nil, err
  1256. }
  1257. logger.Debugf("created log file: %s", filePath)
  1258. return file, nil
  1259. }
  1260. func (k8sh *K8sHelper) getPodLogs(pod v1.Pod, platformName, namespace, testName string, previousLog bool) {
  1261. suffix := ""
  1262. if previousLog {
  1263. suffix = "_previous"
  1264. }
  1265. file, err := k8sh.createTestLogFile(platformName, pod.Name, namespace, testName, suffix)
  1266. if err != nil {
  1267. return
  1268. }
  1269. defer file.Close()
  1270. for _, container := range pod.Spec.InitContainers {
  1271. k8sh.appendContainerLogs(file, pod, container.Name, previousLog, true)
  1272. }
  1273. for _, container := range pod.Spec.Containers {
  1274. k8sh.appendContainerLogs(file, pod, container.Name, previousLog, false)
  1275. }
  1276. }
  1277. func writeHeader(file *os.File, message string) error {
  1278. file.WriteString("\n-----------------------------------------\n") //nolint // ok to ignore this test logging
  1279. file.WriteString(message) //nolint // ok to ignore this test logging
  1280. file.WriteString("\n-----------------------------------------\n") //nolint // ok to ignore this test logging
  1281. return nil
  1282. }
  1283. func (k8sh *K8sHelper) appendContainerLogs(file *os.File, pod v1.Pod, containerName string, previousLog, initContainer bool) {
  1284. message := fmt.Sprintf("CONTAINER: %s", containerName)
  1285. if initContainer {
  1286. message = "INIT " + message
  1287. }
  1288. writeHeader(file, message) //nolint // ok to ignore this test logging
  1289. ctx := context.TODO()
  1290. logOpts := &v1.PodLogOptions{Previous: previousLog}
  1291. if containerName != "" {
  1292. logOpts.Container = containerName
  1293. }
  1294. res := k8sh.Clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, logOpts).Do(ctx)
  1295. rawData, err := res.Raw()
  1296. if err != nil {
  1297. // Sometimes we fail to get logs for pods using this method, notably the operator pod. It is
  1298. // unknown why this happens. Pod logs are VERY important, so try again using kubectl.
  1299. l, err := k8sh.Kubectl("-n", pod.Namespace, "logs", pod.Name, "-c", containerName)
  1300. if err != nil {
  1301. logger.Errorf("Cannot get logs for pod %s and container %s. %v", pod.Name, containerName, err)
  1302. return
  1303. }
  1304. rawData = []byte(l)
  1305. }
  1306. if _, err := file.Write(rawData); err != nil {
  1307. logger.Errorf("Errors while writing logs for pod %s and container %s. %v", pod.Name, containerName, err)
  1308. }
  1309. }
  1310. // CreateAnonSystemClusterBinding Creates anon-user-access clusterrolebinding for cluster-admin role - used by kubeadm env.
  1311. func (k8sh *K8sHelper) CreateAnonSystemClusterBinding() {
  1312. ctx := context.TODO()
  1313. _, err := k8sh.Clientset.RbacV1beta1().ClusterRoleBindings().Get(ctx, "anon-user-access", metav1.GetOptions{})
  1314. if err != nil {
  1315. logger.Warningf("anon-user-access clusterrolebinding not found. %v", err)
  1316. args := []string{"create", "clusterrolebinding", "anon-user-access", "--clusterrole", "cluster-admin", "--user", "system:anonymous"}
  1317. _, err := k8sh.Kubectl(args...)
  1318. if err != nil {
  1319. logger.Errorf("failed to create anon-user-access. %v", err)
  1320. return
  1321. }
  1322. logger.Info("anon-user-access creation completed, waiting for it to exist in API")
  1323. }
  1324. for i := 0; i < RetryLoop; i++ {
  1325. var err error
  1326. if _, err = k8sh.Clientset.RbacV1().ClusterRoleBindings().Get(ctx, "anon-user-access", metav1.GetOptions{}); err == nil {
  1327. break
  1328. }
  1329. logger.Warningf("failed to get anon-user-access clusterrolebinding, will try again: %+v", err)
  1330. time.Sleep(RetryInterval * time.Second)
  1331. }
  1332. }
  1333. func IsKubectlErrorNotFound(output string, err error) bool {
  1334. return err != nil && strings.Contains(output, "Error from server (NotFound)")
  1335. }
  1336. // WaitForDeploymentCount waits until the desired number of deployments with the label exist. The
  1337. // deployments are not guaranteed to be running, only existing.
  1338. func (k8sh *K8sHelper) WaitForDeploymentCount(label, namespace string, count int) error {
  1339. return k8sh.waitForDeploymentCountWithRetries(label, namespace, count, RetryLoop)
  1340. }
  1341. // WaitForDeploymentCountWithRetries waits until the desired number of deployments with the label
  1342. // exist, retrying the specified number of times. The deployments are not guaranteed to be running,
  1343. // only existing.
  1344. func (k8sh *K8sHelper) waitForDeploymentCountWithRetries(label, namespace string, count, retries int) error {
  1345. ctx := context.TODO()
  1346. options := metav1.ListOptions{LabelSelector: label}
  1347. for i := 0; i < retries; i++ {
  1348. deps, err := k8sh.Clientset.AppsV1().Deployments(namespace).List(ctx, options)
  1349. numDeps := 0
  1350. if err == nil {
  1351. numDeps = len(deps.Items)
  1352. }
  1353. if numDeps >= count {
  1354. logger.Infof("found %d of %d deployments with label %s in namespace %s", numDeps, count, label, namespace)
  1355. return nil
  1356. }
  1357. logger.Infof("waiting for %d deployments (found %d) with label %s in namespace %s", count, numDeps, label, namespace)
  1358. time.Sleep(RetryInterval * time.Second)
  1359. }
  1360. return fmt.Errorf("giving up waiting for %d deployments with label %s in namespace %s", count, label, namespace)
  1361. }
  1362. // WaitForLabeledDeploymentsToBeReady waits for all deployments matching the given label selector to
  1363. // be fully ready with a default timeout.
  1364. func (k8sh *K8sHelper) WaitForLabeledDeploymentsToBeReady(label, namespace string) error {
  1365. return k8sh.WaitForLabeledDeploymentsToBeReadyWithRetries(label, namespace, RetryLoop)
  1366. }
  1367. // WaitForLabeledDeploymentsToBeReadyWithRetries waits for all deployments matching the given label
  1368. // selector to be fully ready. Retry the number of times given.
  1369. func (k8sh *K8sHelper) WaitForLabeledDeploymentsToBeReadyWithRetries(label, namespace string, retries int) error {
  1370. listOpts := metav1.ListOptions{LabelSelector: label}
  1371. ctx := context.TODO()
  1372. var lastDep apps.Deployment
  1373. for i := 0; i < retries; i++ {
  1374. deps, err := k8sh.Clientset.AppsV1().Deployments(namespace).List(ctx, listOpts)
  1375. ready := 0
  1376. if err == nil && len(deps.Items) > 0 {
  1377. for _, dep := range deps.Items {
  1378. if dep.Status.Replicas == dep.Status.ReadyReplicas {
  1379. ready++
  1380. } else {
  1381. lastDep = dep // make it the last non-ready dep
  1382. }
  1383. if ready == len(deps.Items) {
  1384. logger.Infof("all %d deployments with label %s are running", len(deps.Items), label)
  1385. return nil
  1386. }
  1387. }
  1388. }
  1389. logger.Infof("waiting for deployment(s) with label %s in namespace %s to be running. ready=%d/%d, err=%+v",
  1390. label, namespace, ready, len(deps.Items), err)
  1391. time.Sleep(RetryInterval * time.Second)
  1392. }
  1393. if len(lastDep.Name) == 0 {
  1394. logger.Infof("no deployment was found with label %s", label)
  1395. } else {
  1396. r, err := k8sh.Kubectl("-n", namespace, "get", "-o", "yaml", "deployments", "--selector", label)
  1397. if err != nil {
  1398. logger.Infof("deployments with label %s:\n%s", label, r)
  1399. }
  1400. }
  1401. return fmt.Errorf("giving up waiting for deployment(s) with label %s in namespace %s to be ready", label, namespace)
  1402. }
  1403. func (k8sh *K8sHelper) WaitForCronJob(name, namespace string) error {
  1404. k8sVersion, err := k8sutil.GetK8SVersion(k8sh.Clientset)
  1405. if err != nil {
  1406. return errors.Wrap(err, "failed to get k8s version")
  1407. }
  1408. useCronJobV1 := k8sVersion.AtLeast(version.MustParseSemantic(nodedaemon.MinVersionForCronV1))
  1409. for i := 0; i < RetryLoop; i++ {
  1410. var err error
  1411. if useCronJobV1 {
  1412. _, err = k8sh.Clientset.BatchV1().CronJobs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
  1413. } else {
  1414. _, err = k8sh.Clientset.BatchV1beta1().CronJobs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
  1415. }
  1416. if err != nil {
  1417. if kerrors.IsNotFound(err) {
  1418. logger.Infof("waiting for CronJob named %s in namespace %s", name, namespace)
  1419. time.Sleep(RetryInterval * time.Second)
  1420. continue
  1421. }
  1422. return fmt.Errorf("failed to find CronJob named %s. %+v", name, err)
  1423. }
  1424. logger.Infof("found CronJob with name %s in namespace %s", name, namespace)
  1425. return nil
  1426. }
  1427. return fmt.Errorf("giving up waiting for CronJob named %s in namespace %s", name, namespace)
  1428. }
  1429. func (k8sh *K8sHelper) GetResourceStatus(kind, name, namespace string) (string, error) {
  1430. return k8sh.Kubectl("-n", namespace, "get", kind, name) // TODO: -o status
  1431. }
  1432. func (k8sh *K8sHelper) WaitUntilResourceIsDeleted(kind, namespace, name string) error {
  1433. var err error
  1434. var out string
  1435. for i := 0; i < RetryLoop; i++ {
  1436. out, err = k8sh.Kubectl("-n", namespace, "get", kind, name, "-o", "name")
  1437. if strings.Contains(out, "Error from server (NotFound): ") {
  1438. return nil
  1439. }
  1440. logger.Infof("waiting %d more seconds for resource %s %q to be deleted:\n%s", RetryInterval, kind, name, out)
  1441. time.Sleep(RetryInterval * time.Second)
  1442. }
  1443. return errors.Wrapf(err, "timed out waiting for resource %s %q to be deleted:\n%s", kind, name, out)
  1444. }