Skip to content

Commit

Permalink
Tweaking tests (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
yurique authored Nov 24, 2023
1 parent c7cc83e commit 1c8159c
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.goyeau.kubernetes.client

import cats.syntax.all.*
import io.k8s.api.core.v1.{Container, PodSpec, ResourceRequirements}
import io.k8s.apimachinery.pkg.api.resource.Quantity

object TestPodSpec {

val alpine: PodSpec = alpine(None)

def alpine(command: Seq[String]): PodSpec = alpine(command.some)

private def alpine(command: Option[Seq[String]]): PodSpec = PodSpec(
containers = Seq(
Container(
name = "test",
image = "alpine".some,
command = command,
imagePullPolicy = "IfNotPresent".some,
resources = ResourceRequirements(
requests = Map(
"memory" -> Quantity("10Mi")
).some,
limits = Map(
"memory" -> Quantity("10Mi")
).some
).some
)
),
terminationGracePeriodSeconds = 0L.some
)

}
38 changes: 27 additions & 11 deletions kubernetes-client/test/src/com/goyeau/kubernetes/client/Utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,37 @@ object Utils {
def retry[F[_], Result](
f: F[Result],
initialDelay: FiniteDuration = 1.second,
maxRetries: Int = 50,
actionClue: Option[String] = None
maxRetries: Int = 10,
actionClue: Option[String] = None,
firstRun: Boolean = true
)(implicit
temporal: Temporal[F],
F: ApplicativeError[F, Throwable],
D: Defer[F],
log: Logger[F]
): F[Result] =
f.handleErrorWith { exception =>
if (maxRetries > 0)
log.info(
s"Retrying in $initialDelay${actionClue.map(c => s", action: $c").getOrElse("")}. Retries left: $maxRetries"
) *>
temporal.sleep(initialDelay) *>
D.defer(retry(f, initialDelay, maxRetries - 1, actionClue))
else F.raiseError(exception)
}
f
.flatTap { _ =>
F.whenA(!firstRun)(log.info(s"Succeeded after retrying${actionClue.map(c => s", action: $c").getOrElse("")}"))
}
.handleErrorWith { exception =>
val firstLine = exception.getMessage.takeWhile(_ != '\n')
val message =
if (firstLine.contains(".scala"))
firstLine.split('/').lastOption.getOrElse(firstLine)
else
firstLine

if (maxRetries > 0)
log.info(
s"$message. Retrying in $initialDelay${actionClue.map(c => s", action: $c").getOrElse("")}. Retries left: $maxRetries"
) *>
temporal.sleep(initialDelay) *>
D.defer(retry(f, initialDelay, maxRetries - 1, actionClue, firstRun = false))
else
log.info(
s"Giving up ${actionClue.map(c => s", action: $c").getOrElse("")}. No retries left"
) *>
F.raiseError(exception)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.goyeau.kubernetes.client.api

import cats.syntax.all.*
import cats.effect.{Async, IO}
import com.goyeau.kubernetes.client.operation.*
import com.goyeau.kubernetes.client.KubernetesClient
import com.goyeau.kubernetes.client.{KubernetesClient, TestPodSpec}
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import io.k8s.api.batch.v1.{CronJob, CronJobList, CronJobSpec, JobSpec, JobTemplateSpec}
Expand Down Expand Up @@ -40,12 +41,7 @@ class CronJobsApiTest
JobSpec(
template = PodTemplateSpec(
metadata = Option(ObjectMeta(name = Option(resourceName))),
spec = Option(
PodSpec(
containers = Seq(Container("test", image = Option("docker"))),
restartPolicy = Option("Never")
)
)
spec = TestPodSpec.alpine.copy(restartPolicy = "Never".some).some
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.goyeau.kubernetes.client.api

import cats.effect.{Async, IO}
import com.goyeau.kubernetes.client.operation._
import com.goyeau.kubernetes.client.{IntValue, KubernetesClient, StringValue}
import com.goyeau.kubernetes.client.operation.*
import com.goyeau.kubernetes.client.{IntValue, KubernetesClient, StringValue, TestPodSpec}
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import io.k8s.api.apps.v1._
import io.k8s.api.core.v1._
import io.k8s.api.apps.v1.*
import io.k8s.api.core.v1.*
import io.k8s.apimachinery.pkg.apis.meta.v1.{LabelSelector, ObjectMeta}
import munit.FunSuite

Expand Down Expand Up @@ -40,7 +40,7 @@ class DeploymentsApiTest
selector = LabelSelector(matchLabels = label),
template = PodTemplateSpec(
metadata = Option(ObjectMeta(name = Option(resourceName), labels = label)),
spec = Option(PodSpec(containers = Seq(Container("test", image = Option("docker")))))
spec = Option(TestPodSpec.alpine)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.goyeau.kubernetes.client.api

import cats.syntax.all.*
import cats.effect.{Async, IO}
import com.goyeau.kubernetes.client.operation._
import com.goyeau.kubernetes.client.KubernetesClient
import com.goyeau.kubernetes.client.operation.*
import com.goyeau.kubernetes.client.{KubernetesClient, TestPodSpec}
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import io.k8s.api.batch.v1.{Job, JobList, JobSpec}
import io.k8s.api.core.v1._
import io.k8s.api.core.v1.*
import io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
import munit.FunSuite

Expand Down Expand Up @@ -37,7 +38,7 @@ class JobsApiTest
template = PodTemplateSpec(
metadata = Option(ObjectMeta(name = Option(resourceName))),
spec = Option(
PodSpec(containers = Seq(Container("test", image = Option("docker"))), restartPolicy = Option("Never"))
TestPodSpec.alpine.copy(restartPolicy = "Never".some)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.goyeau.kubernetes.client.api

import cats.syntax.all.*
import cats.effect.unsafe.implicits.global
import cats.effect.{Async, IO}
import com.goyeau.kubernetes.client.KubernetesClient
import com.goyeau.kubernetes.client.{KubernetesClient, TestPodSpec}
import com.goyeau.kubernetes.client.Utils.retry
import com.goyeau.kubernetes.client.api.ExecStream.{StdErr, StdOut}
import com.goyeau.kubernetes.client.operation.*
Expand All @@ -15,7 +16,8 @@ import munit.FunSuite
import org.http4s.Status
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import java.nio.file.{Files => JFiles}
import io.circe.syntax.*
import java.nio.file.Files as JFiles
import scala.util.Random

class PodsApiTest
Expand All @@ -40,7 +42,7 @@ class PodsApiTest
override def sampleResource(resourceName: String, labels: Map[String, String]): Pod =
Pod(
metadata = Option(ObjectMeta(name = Option(resourceName), labels = Option(labels))),
spec = Option(PodSpec(containers = Seq(Container("test", image = Option("docker")))))
spec = Option(TestPodSpec.alpine)
)

private val activeDeadlineSeconds = Option(5L)
Expand All @@ -61,38 +63,20 @@ class PodsApiTest

def testPod(podName: String, labels: Map[String, String] = Map.empty): Pod = Pod(
metadata = Option(ObjectMeta(name = Option(podName), labels = Option(labels))),
spec = Option(
PodSpec(
containers = Seq(
Container(
"test",
image = Option("docker"),
command = Option(Seq("sh", "-c", "tail -f /dev/null"))
)
)
)
)
spec = TestPodSpec.alpine(command = Seq("sh", "-c", "sleep 120")).some
)

def testPodWithLogs(podName: String, labels: Map[String, String] = Map.empty): Pod = Pod(
metadata = Option(ObjectMeta(name = Option(podName), labels = Option(labels))),
spec = Option(
PodSpec(
containers = Seq(
Container(
"test",
image = Option("docker"),
command = Option(
Seq(
"sh",
"-c",
"echo line 1; sleep 1; echo line 2; sleep 2; echo line 3; echo line 4; echo line 5; echo line 6"
)
)
)
spec = TestPodSpec
.alpine(command =
Seq(
"sh",
"-c",
"echo line 1; sleep 1; echo line 2; sleep 2; echo line 3; echo line 4; echo line 5; echo line 6"
)
)
)
.some
)

private val successStatus = Some(Right(v1.Status(status = Some("Success"), metadata = Some(ListMeta()))))
Expand Down Expand Up @@ -303,9 +287,14 @@ class PodsApiTest
statusCount = pod.status.flatMap(_.conditions.map(_.length)).getOrElse(0)
_ <-
if (notStarted || statusCount != podStatusCount)
IO.raiseError(new RuntimeException("Pod is not started"))
IO.raiseError(
new RuntimeException(
s"Pod is not started: ${pod.status.flatMap(_.conditions).toSeq.flatten.map(_.asJson.noSpaces).mkString(", ")}"
)
)
else IO.unit
} yield pod,
maxRetries = 100,
actionClue = Some(s"Waiting for pod $name to be ready")
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.goyeau.kubernetes.client.api

import cats.effect.{Async, IO}
import com.goyeau.kubernetes.client.KubernetesClient
import com.goyeau.kubernetes.client.{KubernetesClient, TestPodSpec}
import com.goyeau.kubernetes.client.operation.*
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
Expand Down Expand Up @@ -41,7 +41,7 @@ class ReplicaSetsApiTest
template = Option(
PodTemplateSpec(
metadata = Option(ObjectMeta(name = Option(resourceName), labels = label)),
spec = Option(PodSpec(containers = Seq(Container("test", image = Option("docker")))))
spec = Option(TestPodSpec.alpine)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.goyeau.kubernetes.client.api

import cats.effect.{Async, IO}
import com.goyeau.kubernetes.client.KubernetesClient
import com.goyeau.kubernetes.client.{KubernetesClient, TestPodSpec}
import com.goyeau.kubernetes.client.operation.*
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
Expand Down Expand Up @@ -41,7 +41,7 @@ class StatefulSetsApiTest
selector = LabelSelector(matchLabels = label),
template = PodTemplateSpec(
metadata = Option(ObjectMeta(name = Option(resourceName), labels = label)),
spec = Option(PodSpec(containers = Seq(Container("test", image = Option("docker")))))
spec = Option(TestPodSpec.alpine)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ trait CreatableTests[F[_], Resource <: { def metadata: Option[ObjectMeta] }]
): F[Resource] = {
val resource = sampleResource(resourceName, labels)
for {
_ <- namespacedApi(namespaceName).create(resource)
status <- namespacedApi(namespaceName).create(resource)
_ <- logger.info(s"Created '$resourceName' in $namespaceName namespace: $status")
_ <- F.delay(assertEquals(status.isSuccess, true, s"$status should be successful"))
resource <- retry(
getChecked(namespaceName, resourceName),
actionClue = Some(s"Creating '$resourceName' in $namespaceName namespace")
actionClue = Some(s"Getting after create '$resourceName' in $namespaceName namespace"),
maxRetries = 2
)
} yield resource
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,29 @@ trait MinikubeClientProvider[F[_]] {
}.void

private def deleteNamespace(namespace: String) = kubernetesClient.use { client =>
client.namespaces.deleteTerminated(
client.namespaces.delete(
namespace,
DeleteOptions(gracePeriodSeconds = Some(0L)).some
DeleteOptions(gracePeriodSeconds = 0L.some, propagationPolicy = "Foreground".some).some
)
}.void

protected def createNamespaces() = {
val ns = defaultNamespace +: extraNamespace.toList
println(s"Creating namespaces: $ns")
ns.foreach(name => unsafeRunSync(createNamespace(name)))
protected def createNamespaces(): Unit = {
val ns = defaultNamespace +: extraNamespace
unsafeRunSync(
logger.info(s"Creating namespaces: $ns") *>
ns.traverse_(name => createNamespace(name))
)
}

override def beforeAll(): Unit =
createNamespaces()

override def afterAll(): Unit = {
val ns = defaultNamespace +: extraNamespace.toList
println(s"Deleting namespaces: $ns")
ns.foreach(name => unsafeRunSync(deleteNamespace(name)))
val ns = defaultNamespace +: extraNamespace
unsafeRunSync(
logger.info(s"Deleting namespaces: $ns") *>
ns.traverse_(name => deleteNamespace(name))
)
}

def usingMinikube[T](body: KubernetesClient[F] => F[T]): T =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,11 @@ trait WatchableTests[F[_], Resource <: { def metadata: Option[ObjectMeta] }]
val expected = Set[EventType](EventType.MODIFIED, EventType.DELETED)

for {
_ <- retry(createIfMissing(defaultNamespace, name), actionClue = Some(s"createIfMissing ${defaultNamespace}/${name}"))
resource <- getChecked(defaultNamespace, name)
_ <- retry(
createIfMissing(defaultNamespace, name),
actionClue = Some(s"createIfMissing $defaultNamespace/$name")
)
resource <- getChecked(defaultNamespace, name)
resourceVersion = resource.metadata.flatMap(_.resourceVersion).get
_ <- (
watchEvents(Map(defaultNamespace -> expected), name, Some(defaultNamespace), Some(resourceVersion)),
Expand Down

0 comments on commit 1c8159c

Please sign in to comment.