Skip to content

Commit

Permalink
Merge from v2.0.12
Browse files Browse the repository at this point in the history
  • Loading branch information
htch committed Jan 11, 2019
2 parents bd59031 + 69b9068 commit 1e1153b
Show file tree
Hide file tree
Showing 34 changed files with 1,678 additions and 144 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Provides you with a configured client on startup. It is handy to use this for qu
> Just handy shortcut to import skuber inside ammonite-repl:
```scala
import $ivy.`io.skuber::skuber:2.0.10`, skuber._, skuber.json.format._
import $ivy.`io.skuber::skuber:2.0.12`, skuber._, skuber.json.format._
```

### Interactive with sbt
Expand Down Expand Up @@ -119,7 +119,7 @@ To get minikube follow the instructions [here](https://github.com/kubernetes/min
You can use the latest release (for Scala 2.11 or 2.12) by adding to your build:
```sbt
libraryDependencies += "io.skuber" %% "skuber" % "2.0.10"
libraryDependencies += "io.skuber" %% "skuber" % "2.0.11"
```
Meanwhile users of skuber v1 can continue to use the latest (and possibly final, with exception of important fixes) v1.x release, which is available only on Scala 2.11:
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ scalacOptions += "-target:jvm-1.8"

scalacOptions in Test ++= Seq("-Yrangepos")

version in ThisBuild := "2.0.10"
version in ThisBuild := "2.0.12"

sonatypeProfileName := "io.skuber"

Expand Down
116 changes: 116 additions & 0 deletions client/src/it/scala/skuber/ExecSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package skuber

import akka.Done
import akka.stream.scaladsl.{Sink, Source}
import org.scalatest.{BeforeAndAfterAll, Matchers}
import org.scalatest.concurrent.Eventually
import skuber.json.format._

import scala.concurrent.duration._
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future, Promise}


class ExecSpec extends K8SFixture with Eventually with Matchers with BeforeAndAfterAll {
val nginxPodName: String = java.util.UUID.randomUUID().toString

behavior of "Exec"

override def beforeAll(): Unit = {
super.beforeAll()

val k8s = k8sInit
Await.result(k8s.create(getNginxPod(nginxPodName, "1.7.9")), 3 second)
// Let the pod running
Thread.sleep(3000)
k8s.close
}

override def afterAll(): Unit = {
val k8s = k8sInit
Await.result(k8s.delete[Pod](nginxPodName), 3 second)
Thread.sleep(3000)
k8s.close

super.afterAll()
}

it should "execute a command in the running pod" in { k8s =>
var output = ""
val stdout: Sink[String, Future[Done]] = Sink.foreach(output += _)
var errorOutput = ""
val stderr: Sink[String, Future[Done]] = Sink.foreach(errorOutput += _)
k8s.exec(nginxPodName, Seq("whoami"), maybeStdout = Some(stdout), maybeStderr = Some(stderr), maybeClose = Some(closeAfter(1 second))).map { _ =>
assert(output == "root\n")
assert(errorOutput == "")
}
}

it should "execute a command in the specified container of the running pod" in { k8s =>
var output = ""
val stdout: Sink[String, Future[Done]] = Sink.foreach(output += _)
var errorOutput = ""
val stderr: Sink[String, Future[Done]] = Sink.foreach(errorOutput += _)
k8s.exec(nginxPodName, Seq("whoami"), maybeContainerName = Some("nginx"),
maybeStdout = Some(stdout), maybeStderr = Some(stderr), maybeClose = Some(closeAfter(1 second))).map { _ =>
assert(output == "root\n")
assert(errorOutput == "")
}
}

it should "execute a command that outputs to stderr in the running pod" in { k8s =>
var output = ""
val stdout: Sink[String, Future[Done]] = Sink.foreach(output += _)
var errorOutput = ""
val stderr: Sink[String, Future[Done]] = Sink.foreach(errorOutput += _)
k8s.exec(nginxPodName, Seq("sh", "-c", "whoami >&2"),
maybeStdout = Some(stdout), maybeStderr = Some(stderr), maybeClose = Some(closeAfter(1 second))).map { _ =>
assert(output == "")
assert(errorOutput == "root\n")
}
}

it should "execute a command in an interactive shell of the running pod" in { k8s =>
val stdin = Source.single("whoami\n")
var output = ""
val stdout: Sink[String, Future[Done]] = Sink.foreach(output += _)
var errorOutput = ""
val stderr: Sink[String, Future[Done]] = Sink.foreach(errorOutput += _)
k8s.exec(nginxPodName, Seq("sh"), maybeStdin = Some(stdin),
maybeStdout = Some(stdout), maybeStderr = Some(stderr), tty = true, maybeClose = Some(closeAfter(1 second))).map { _ =>
assert(output == "# whoami\r\nroot\r\n# ")
assert(errorOutput == "")
}
}

it should "throw an exception without stdin, stdout nor stderr in the running pod" in { k8s =>
k8s.exec(nginxPodName, Seq("whoami")).failed.map {
case e: K8SException =>
assert(e.status.code == Some(400))
}
}

it should "throw an exception against an unexisting pod" in { k8s =>
k8s.exec(nginxPodName + "x", Seq("whoami")).failed.map {
case e: K8SException =>
assert(e.status.code == Some(404))
}
}

def closeAfter(duration: Duration) = {
val promise = Promise[Unit]()
Future {
Thread.sleep(duration.toMillis)
promise.success(())
}
promise
}

def getNginxContainer(version: String): Container = Container(name = "nginx", image = "nginx:" + version).exposePort(80)

def getNginxPod(name: String, version: String): Pod = {
val nginxContainer = getNginxContainer(version)
val nginxPodSpec = Pod.Spec(containers = List((nginxContainer)))
Pod.named(nginxPodName).copy(spec = Some(nginxPodSpec))
}
}
101 changes: 101 additions & 0 deletions client/src/it/scala/skuber/HorizontalPodAutoscalerV2Beta1Spec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package skuber

import org.scalatest.Matchers
import org.scalatest.concurrent.Eventually
import skuber.apps.v1.Deployment
import skuber.autoscaling.v2beta1.HorizontalPodAutoscaler
import skuber.autoscaling.v2beta1.HorizontalPodAutoscaler.ResourceMetricSource

class HorizontalPodAutoscalerV2Beta1Spec extends K8SFixture with Eventually with Matchers {
behavior of "HorizontalPodAutoscalerV2Beta1"

it should "create a HorizontalPodAutoscaler" in { k8s =>
val name: String = java.util.UUID.randomUUID().toString
println(name)
k8s.create(getNginxDeployment(name, "1.7.9")) flatMap { d =>
k8s.create(
HorizontalPodAutoscaler(name).withSpec(
HorizontalPodAutoscaler.Spec("v1", "Deployment", "nginx")
.withMinReplicas(1)
.withMaxReplicas(2)
.addResourceMetric(ResourceMetricSource(Resource.cpu, Some(80), None))
)
).map { result =>
assert(result.name == name)
assert(result.spec.contains(
HorizontalPodAutoscaler.Spec("v1", "Deployment", "nginx")
.withMinReplicas(1)
.withMaxReplicas(2)
.addResourceMetric(ResourceMetricSource(Resource.cpu, Some(80), None)))
)
}
}
}

it should "update a HorizontalPodAutoscaler" in { k8s =>
val name: String = java.util.UUID.randomUUID().toString
k8s.create(getNginxDeployment(name, "1.7.9")) flatMap { d =>
k8s.create(
HorizontalPodAutoscaler(name).withSpec(
HorizontalPodAutoscaler.Spec("v1", "Deployment", "nginx")
.withMinReplicas(1)
.withMaxReplicas(2)
.addResourceMetric(ResourceMetricSource(Resource.cpu, Some(80), None))
)
).flatMap(created =>
eventually(
k8s.get[HorizontalPodAutoscaler](created.name).flatMap { existing =>
val udpated = existing.withSpec(HorizontalPodAutoscaler.Spec("v1", "Deployment", "nginx")
.withMinReplicas(1)
.withMaxReplicas(3)
.addResourceMetric(ResourceMetricSource(Resource.cpu, Some(80), None)))

k8s.update(udpated).map { result =>
assert(result.name == name)
assert(result.spec.contains(
HorizontalPodAutoscaler.Spec("v1", "Deployment", "nginx")
.withMinReplicas(1)
.withMaxReplicas(3)
.addResourceMetric(ResourceMetricSource(Resource.cpu, Some(80), None))
))
}
}
)
)
}
}

it should "delete a HorizontalPodAutoscaler" in { k8s =>
val name: String = java.util.UUID.randomUUID().toString
k8s.create(getNginxDeployment(name, "1.7.9")) flatMap { d =>
k8s.create(
HorizontalPodAutoscaler(name).withSpec(
HorizontalPodAutoscaler.Spec("v1", "Deployment", "nginx")
.withMinReplicas(1)
.withMaxReplicas(2)
.addResourceMetric(ResourceMetricSource(Resource.cpu, Some(80), None))
)
).flatMap { created =>
k8s.delete[HorizontalPodAutoscaler](created.name).flatMap { deleteResult =>
k8s.get[HorizontalPodAutoscaler](created.name).map { x =>
assert(false)
} recoverWith {
case ex: K8SException if ex.status.code.contains(404) => assert(true)
case _ => assert(false)
}
}
}
}
}

def getNginxDeployment(name: String, version: String): Deployment = {
import LabelSelector.dsl._
val nginxContainer = getNginxContainer(version)
val nginxTemplate = Pod.Template.Spec.named("nginx").addContainer(nginxContainer).addLabel("app" -> "nginx")
Deployment(name).withTemplate(nginxTemplate).withLabelSelector("app" is "nginx")
}

def getNginxContainer(version: String): Container = {
Container(name = "nginx", image = "nginx:" + version).exposePort(80)
}
}
4 changes: 3 additions & 1 deletion client/src/it/scala/skuber/K8SFixture.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ trait K8SFixture extends fixture.AsyncFlatSpec {
implicit val materializer = ActorMaterializer()
implicit val dispatcher = system.dispatcher

val config = ConfigFactory.load()

override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
val k8s = k8sInit
val k8s = k8sInit(config)
complete {
withFixture(test.toNoArgAsyncTest(k8s))
} lastly {
Expand Down
97 changes: 65 additions & 32 deletions client/src/it/scala/skuber/NamespaceSpec.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package skuber

import java.util.UUID.randomUUID

import org.scalatest.Matchers
import org.scalatest.concurrent.Eventually
import json.format.{namespaceFormat, podFormat}

import json.format.{namespaceFormat,podFormat}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
Expand All @@ -13,27 +15,49 @@ import scala.util.{Failure, Success}
*/
class NamespaceSpec extends K8SFixture with Eventually with Matchers {

val nginxPodName: String = java.util.UUID.randomUUID().toString
val nginxPodName1: String = randomUUID().toString
val nginxPodName2: String = randomUUID().toString

val namespace1Name: String = "namespace1"
val namespace2Name: String = "namespace2"
val testNamespaces = List(namespace1Name,namespace2Name)

val namespace1Name="namespace1"
val pod1: Pod = getNginxPod(namespace1Name,nginxPodName1)
val pod2: Pod = getNginxPod(namespace2Name, nginxPodName2)

behavior of "Namespace"

it should "create a namespace" in { k8s =>
k8s.create(Namespace.forName(namespace1Name)).map { ns =>
assert(ns.name == namespace1Name)
it should "create namespace1" in { k8s =>
k8s.create(Namespace(namespace1Name)).map { ns => assert(ns.name == namespace1Name) }
}

it should "create namespace2" in { k8s =>
k8s.create(Namespace(namespace2Name)).map { ns => assert(ns.name == namespace2Name)}
}

it should "create pod1 in namespace1" in { k8s =>
k8s.usingNamespace(namespace1Name).create(pod1)
.map { p =>
assert(p.name == nginxPodName1)
assert(p.namespace == namespace1Name)
}
}

it should "create a pod in the newly created namespace" in { k8s =>
k8s.usingNamespace(namespace1Name).create(getNginxPod(namespace1Name, nginxPodName, "1.7.9")) map { p =>
assert(p.name == nginxPodName)
assert(p.namespace == namespace1Name)
it should "honor namespace precedence hierarchy: object > client" in { k8s =>
k8s.usingNamespace(namespace1Name).create(pod2).map { p =>
assert(p.name == nginxPodName2)
assert(p.namespace == namespace2Name)
}
}

it should "not find the newly created pod in the default namespace" in { k8s =>
val retrievePod = k8s.get[Pod](nginxPodName)
it should "find the pod2 in namespace2" in { k8s =>
k8s.usingNamespace(namespace2Name).get[Pod](nginxPodName2) map { p =>
assert(p.name == nginxPodName2)
}
}

it should "not find pod1 in the default namespace" in { k8s =>
val retrievePod = k8s.get[Pod](nginxPodName1)
val podRetrieved=Await.ready(retrievePod, 2 seconds).value.get
podRetrieved match {
case s: Success[_] => assert(false)
Expand All @@ -44,34 +68,43 @@ class NamespaceSpec extends K8SFixture with Eventually with Matchers {
}
}


it should "find the newly created pod in the newly created namespace" in { k8s =>
k8s.usingNamespace(namespace1Name).get[Pod](nginxPodName) map { p =>
assert(p.name == nginxPodName)
it should "find the pod1 in namespace1" in { k8s =>
k8s.usingNamespace(namespace1Name).get[Pod](nginxPodName1) map { p =>
assert(p.name == nginxPodName1)
}
}

it should "delete the namespace" in { k8s =>
val deleteNs = k8s.delete[Namespace](namespace1Name)
eventually(timeout(100 seconds), interval(3 seconds)) {
val retrieveNs = k8s.get[Namespace](namespace1Name)
val nsRetrieved = Await.ready(retrieveNs, 2 seconds).value.get
nsRetrieved match {
case s: Success[_] => assert(false)
case Failure(ex) => ex match {
case ex: K8SException if ex.status.code.contains(404) => assert(true)
case _ => assert(false)
}
}
it should "delete all test namespaces" in { k8s =>
val t = timeout(100.seconds)
val i = interval(3.seconds)
// Delete namespaces
testNamespaces.foreach { ns => k8s.delete[Namespace](ns) }

eventually(t, i) {
testNamespaces.map { ns => k8s.get[Namespace](ns) }

assert(!testNamespaces
.map { n => k8s.get[Namespace](n) } // get every namespace
.map { f => Await.ready(f, 2.seconds).value.get } // await completion of each get
.map { // find out if deletion was successful
case s: Success[_] => false
case Failure(ex) => ex match {
case ex: K8SException if ex.status.code.contains(404)
=> true
case _ => false
}
}.reduceLeft(_ && _)) // consider success only if all namespaces were deleted
}
}

def getNginxContainer(version: String): Container = Container(name = "nginx", image = "nginx:" + version).exposePort(80)
def getNginxContainer(version: String): Container =
Container(name = "nginx", image = "nginx:" + version)
.exposePort(port = 80)

def getNginxPod(namespace: String, name: String, version: String): Pod = {
def getNginxPod(namespace: String, name: String, version: String = "1.7.8"): Pod = {
val nginxContainer = getNginxContainer(version)
val nginxPodSpec = Pod.Spec(containers=List((nginxContainer)))
val podMeta=ObjectMeta(namespace=namespace, name = name)
val nginxPodSpec = Pod.Spec(containers=List(nginxContainer))
val podMeta = ObjectMeta(namespace=namespace, name = name)
Pod(metadata=podMeta, spec=Some(nginxPodSpec))
}
}
Loading

0 comments on commit 1e1153b

Please sign in to comment.