This is an automated email from the ASF dual-hosted git repository.
tysonnorris pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push:
new e66945a Make stemcells configurable by deployment (#3669)
e66945a is described below
commit e66945ac2b579b57d3d0698ee97a36fa9b563369
Author: rodric rabbah <[email protected]>
AuthorDate: Wed May 30 20:14:44 2018 -0400
Make stemcells configurable by deployment (#3669)
* Augment RuntimeManifest with stem cell configuration per kind.
* Change StemCell memory type from String to ByteSize.
* Add test for parsing stem cell from manifest and generating an error.
* Construct prewarming configuration from runtime manifest and its stem
cell declarations.
Co-authored-by: Himavanth <[email protected]>
Co-authored-by: Rodric Rabbah <rodric.gmail.com>
* Add stem cell configuration for nodejs.
* Do not permit stemcell configuration with 0 or negative counts.
Provide a factory interface to map stemcells to other types.
Add more tests for stemcell configuration requirements and to test the
factory.
* Fix tests.
* Review comments.
* Do not include stem cell details when reporting the runtimes supported by
a deployment.
---
ansible/files/runtimes.json | 6 +-
.../scala/whisk/core/entity/ExecManifest.scala | 65 ++++++---
.../src/main/scala/whisk/core/entity/Size.scala | 11 ++
.../whisk/core/containerpool/ContainerPool.scala | 56 ++++----
.../scala/whisk/core/invoker/InvokerReactive.scala | 18 +--
.../containerpool/test/ContainerPoolTests.scala | 8 +-
.../scala/whisk/core/entity/test/ExecHelpers.scala | 15 ++-
.../whisk/core/entity/test/ExecManifestTests.scala | 145 ++++++++++++++++++++-
8 files changed, 254 insertions(+), 70 deletions(-)
diff --git a/ansible/files/runtimes.json b/ansible/files/runtimes.json
index 1866b69..44cb30f 100644
--- a/ansible/files/runtimes.json
+++ b/ansible/files/runtimes.json
@@ -14,7 +14,11 @@
"image": {
"name": "nodejs6action"
},
- "deprecated": false
+ "deprecated": false,
+ "stemCells": [{
+ "count": 2,
+ "memory": "256 MB"
+ }]
},
{
"kind": "nodejs:8",
diff --git a/common/scala/src/main/scala/whisk/core/entity/ExecManifest.scala
b/common/scala/src/main/scala/whisk/core/entity/ExecManifest.scala
index 668ef60..7436d16 100644
--- a/common/scala/src/main/scala/whisk/core/entity/ExecManifest.scala
+++ b/common/scala/src/main/scala/whisk/core/entity/ExecManifest.scala
@@ -122,6 +122,7 @@ protected[core] object ExecManifest {
* @param requireMain true iff main entry point is not optional
* @param sentinelledLogs true iff the runtime generates stdout/stderr log
sentinels after an activation
* @param image optional image name, otherwise inferred via fixed mapping
(remove colons and append 'action')
+ * @param stemCells optional list of stemCells to be initialized by invoker
per kind
*/
protected[core] case class RuntimeManifest(kind: String,
image: ImageName,
@@ -129,17 +130,17 @@ protected[core] object ExecManifest {
default: Option[Boolean] = None,
attached: Option[Attached] = None,
requireMain: Option[Boolean] =
None,
- sentinelledLogs: Option[Boolean]
= None) {
-
- protected[entity] def toJsonSummary = {
- JsObject(
- "kind" -> kind.toJson,
- "image" -> image.publicImageName.toJson,
- "deprecated" -> deprecated.getOrElse(false).toJson,
- "default" -> default.getOrElse(false).toJson,
- "attached" -> attached.isDefined.toJson,
- "requireMain" -> requireMain.getOrElse(false).toJson)
- }
+ sentinelledLogs: Option[Boolean]
= None,
+ stemCells: Option[List[StemCell]]
= None)
+
+ /**
+ * A stemcell configuration read from the manifest for a container image to
be initialized by the container pool.
+ *
+ * @param count the number of stemcell containers to create
+ * @param memory the max memory this stemcell will allocate
+ */
+ protected[entity] case class StemCell(count: Int, memory: ByteSize) {
+ require(count > 0, "count must be positive")
}
/**
@@ -240,6 +241,14 @@ protected[core] object ExecManifest {
val knownContainerRuntimes: Set[String] =
runtimes.flatMap(_.versions.map(_.kind))
+ val manifests: Map[String, RuntimeManifest] = {
+ runtimes.flatMap {
+ _.versions.map { m =>
+ m.kind -> m
+ }
+ }.toMap
+ }
+
def skipDockerPull(image: ImageName): Boolean = {
blackboxImages.contains(image) ||
image.prefix.flatMap(p => bypassPullForLocalImages.map(_ ==
p)).getOrElse(false)
@@ -248,7 +257,16 @@ protected[core] object ExecManifest {
def toJson: JsObject = {
runtimes
.map { family =>
- family.name -> family.versions.map(_.toJsonSummary)
+ family.name -> family.versions.map {
+ case rt =>
+ JsObject(
+ "kind" -> rt.kind.toJson,
+ "image" -> rt.image.publicImageName.toJson,
+ "deprecated" -> rt.deprecated.getOrElse(false).toJson,
+ "default" -> rt.default.getOrElse(false).toJson,
+ "attached" -> rt.attached.isDefined.toJson,
+ "requireMain" -> rt.requireMain.getOrElse(false).toJson)
+ }
}
.toMap
.toJson
@@ -262,12 +280,17 @@ protected[core] object ExecManifest {
}
}
- val manifests: Map[String, RuntimeManifest] = {
- runtimes.flatMap {
- _.versions.map { m =>
- m.kind -> m
+ /**
+ * Collects all runtimes for which there is a stemcell configuration
defined
+ *
+ * @return list of runtime manifests with stemcell configurations
+ */
+ def stemcells: Map[RuntimeManifest, List[StemCell]] = {
+ manifests
+ .flatMap {
+ case (_, m) => m.stemCells.map(m -> _)
}
- }.toMap
+ .filter(_._2.nonEmpty)
}
private val defaultRuntimes: Map[String, String] = {
@@ -286,5 +309,11 @@ protected[core] object ExecManifest {
}
protected[entity] implicit val imageNameSerdes = jsonFormat3(ImageName.apply)
- protected[entity] implicit val runtimeManifestSerdes =
jsonFormat7(RuntimeManifest)
+
+ protected[entity] implicit val stemCellSerdes = {
+ import whisk.core.entity.size.serdes
+ jsonFormat2(StemCell.apply)
+ }
+
+ protected[entity] implicit val runtimeManifestSerdes =
jsonFormat8(RuntimeManifest)
}
diff --git a/common/scala/src/main/scala/whisk/core/entity/Size.scala
b/common/scala/src/main/scala/whisk/core/entity/Size.scala
index 61e27b4..2d5b7d9 100644
--- a/common/scala/src/main/scala/whisk/core/entity/Size.scala
+++ b/common/scala/src/main/scala/whisk/core/entity/Size.scala
@@ -21,6 +21,8 @@ import java.nio.charset.StandardCharsets
import com.typesafe.config.ConfigValue
import pureconfig._
+import spray.json._
+import whisk.core.entity.ByteSize.formatError
object SizeUnits extends Enumeration {
@@ -124,6 +126,15 @@ object size {
// Creation of an intermediary Config object is necessary here, since
"getBytes" is only part of that interface.
implicit val pureconfigReader =
ConfigReader[ConfigValue].map(v =>
ByteSize(v.atKey("key").getBytes("key"), SizeUnits.BYTE))
+
+ protected[entity] implicit val serdes = new RootJsonFormat[ByteSize] {
+ def write(b: ByteSize) = JsString(b.toString)
+
+ def read(value: JsValue): ByteSize = value match {
+ case JsString(s) => ByteSize.fromString(s)
+ case _ => deserializationError(formatError)
+ }
+ }
}
trait SizeConversion {
diff --git
a/core/invoker/src/main/scala/whisk/core/containerpool/ContainerPool.scala
b/core/invoker/src/main/scala/whisk/core/containerpool/ContainerPool.scala
index 1835569..d26ebdc 100644
--- a/core/invoker/src/main/scala/whisk/core/containerpool/ContainerPool.scala
+++ b/core/invoker/src/main/scala/whisk/core/containerpool/ContainerPool.scala
@@ -18,15 +18,9 @@
package whisk.core.containerpool
import scala.collection.immutable
-
import whisk.common.{AkkaLogging, LoggingMarkers, TransactionId}
-
import akka.actor.{Actor, ActorRef, ActorRefFactory, Props}
-
-import whisk.core.entity.ByteSize
-import whisk.core.entity.CodeExec
-import whisk.core.entity.EntityName
-import whisk.core.entity.ExecutableWhiskAction
+import whisk.core.entity._
import whisk.core.entity.size._
import whisk.core.connector.MessageFeed
@@ -60,7 +54,7 @@ case class WorkerData(data: ContainerData, state: WorkerState)
*/
class ContainerPool(childFactory: ActorRefFactory => ActorRef,
feed: ActorRef,
- prewarmConfig: Option[PrewarmingConfig] = None,
+ prewarmConfig: List[PrewarmingConfig] = List.empty,
poolConfig: ContainerPoolConfig)
extends Actor {
implicit val logging = new AkkaLogging(context.system.log)
@@ -71,7 +65,8 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
val logMessageInterval = 10.seconds
prewarmConfig.foreach { config =>
- logging.info(this, s"pre-warming ${config.count} ${config.exec.kind}
containers")(TransactionId.invokerWarmup)
+ logging.info(this, s"pre-warming ${config.count} ${config.exec.kind}
${config.memoryLimit.toString}")(
+ TransactionId.invokerWarmup)
(1 to config.count).foreach { _ =>
prewarmContainer(config.exec, config.memoryLimit)
}
@@ -204,26 +199,25 @@ class ContainerPool(childFactory: ActorRefFactory =>
ActorRef,
* @param kind the kind you want to invoke
* @return the container iff found
*/
- def takePrewarmContainer(action: ExecutableWhiskAction): Option[(ActorRef,
ContainerData)] =
- prewarmConfig.flatMap { config =>
- val kind = action.exec.kind
- val memory = action.limits.memory.megabytes.MB
- prewarmedPool
- .find {
- case (_, PreWarmedData(_, `kind`, `memory`)) => true
- case _ => false
- }
- .map {
- case (ref, data) =>
- // Move the container to the usual pool
- freePool = freePool + (ref -> data)
- prewarmedPool = prewarmedPool - ref
- // Create a new prewarm container
- prewarmContainer(config.exec, config.memoryLimit)
-
- (ref, data)
- }
- }
+ def takePrewarmContainer(action: ExecutableWhiskAction): Option[(ActorRef,
ContainerData)] = {
+ val kind = action.exec.kind
+ val memory = action.limits.memory.megabytes.MB
+ prewarmedPool
+ .find {
+ case (_, PreWarmedData(_, `kind`, `memory`)) => true
+ case _ => false
+ }
+ .map {
+ case (ref, data) =>
+ // Move the container to the usual pool
+ freePool = freePool + (ref -> data)
+ prewarmedPool = prewarmedPool - ref
+ // Create a new prewarm container
+ // NOTE: prewarming ignores the action code in exec, but this is
dangerous as the field is accessible to the factory
+ prewarmContainer(action.exec, memory)
+ (ref, data)
+ }
+ }
/** Removes a container and updates state accordingly. */
def removeContainer(toDelete: ActorRef) = {
@@ -282,9 +276,9 @@ object ContainerPool {
def props(factory: ActorRefFactory => ActorRef,
poolConfig: ContainerPoolConfig,
feed: ActorRef,
- prewarmConfig: Option[PrewarmingConfig] = None) =
+ prewarmConfig: List[PrewarmingConfig] = List.empty) =
Props(new ContainerPool(factory, feed, prewarmConfig, poolConfig))
}
-/** Contains settings needed to perform container prewarming */
+/** Contains settings needed to perform container prewarming. */
case class PrewarmingConfig(count: Int, exec: CodeExec[_], memoryLimit:
ByteSize)
diff --git
a/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala
b/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala
index b132dd8..20cbbd4 100644
--- a/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala
+++ b/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala
@@ -33,7 +33,6 @@ import whisk.core.containerpool._
import whisk.core.containerpool.logging.LogStoreProvider
import whisk.core.database._
import whisk.core.entity._
-import whisk.core.entity.size._
import whisk.http.Messages
import whisk.spi.SpiLoader
@@ -173,14 +172,17 @@ class InvokerReactive(
ContainerProxy
.props(containerFactory.createContainer, ack, store,
logsProvider.collectLogs, instance, poolConfig))
- private val prewarmKind = "nodejs:6"
- private val prewarmExec = ExecManifest.runtimesManifest
- .resolveDefaultRuntime(prewarmKind)
- .map(manifest => CodeExecAsString(manifest, "", None))
- .get
+ val prewarmingConfigs: List[PrewarmingConfig] = {
+ ExecManifest.runtimesManifest.stemcells.flatMap {
+ case (mf, cells) =>
+ cells.map { cell =>
+ PrewarmingConfig(cell.count, new CodeExecAsString(mf, "", None),
cell.memory)
+ }
+ }.toList
+ }
- private val pool = actorSystem.actorOf(
- ContainerPool.props(childFactory, poolConfig, activationFeed,
Some(PrewarmingConfig(2, prewarmExec, 256.MB))))
+ private val pool =
+ actorSystem.actorOf(ContainerPool.props(childFactory, poolConfig,
activationFeed, prewarmingConfigs))
/** Is called when an ActivationMessage is read from Kafka */
def processActivationMessage(bytes: Array[Byte]): Future[Unit] = {
diff --git
a/tests/src/test/scala/whisk/core/containerpool/test/ContainerPoolTests.scala
b/tests/src/test/scala/whisk/core/containerpool/test/ContainerPoolTests.scala
index b61e6f8..3fe1253 100644
---
a/tests/src/test/scala/whisk/core/containerpool/test/ContainerPoolTests.scala
+++
b/tests/src/test/scala/whisk/core/containerpool/test/ContainerPoolTests.scala
@@ -240,7 +240,7 @@ class ContainerPoolTests
val pool =
system.actorOf(
- ContainerPool.props(factory, ContainerPoolConfig(0, 0), feed.ref,
Some(PrewarmingConfig(1, exec, memoryLimit))))
+ ContainerPool.props(factory, ContainerPoolConfig(0, 0), feed.ref,
List(PrewarmingConfig(1, exec, memoryLimit))))
containers(0).expectMsg(Start(exec, memoryLimit))
}
@@ -250,7 +250,7 @@ class ContainerPoolTests
val pool =
system.actorOf(
- ContainerPool.props(factory, ContainerPoolConfig(1, 1), feed.ref,
Some(PrewarmingConfig(1, exec, memoryLimit))))
+ ContainerPool.props(factory, ContainerPoolConfig(1, 1), feed.ref,
List(PrewarmingConfig(1, exec, memoryLimit))))
containers(0).expectMsg(Start(exec, memoryLimit))
containers(0).send(pool, NeedWork(preWarmedData(exec.kind)))
pool ! runMessage
@@ -265,7 +265,7 @@ class ContainerPoolTests
val pool = system.actorOf(
ContainerPool
- .props(factory, ContainerPoolConfig(1, 1), feed.ref,
Some(PrewarmingConfig(1, alternativeExec, memoryLimit))))
+ .props(factory, ContainerPoolConfig(1, 1), feed.ref,
List(PrewarmingConfig(1, alternativeExec, memoryLimit))))
containers(0).expectMsg(Start(alternativeExec, memoryLimit)) // container0
was prewarmed
containers(0).send(pool, NeedWork(preWarmedData(alternativeExec.kind)))
pool ! runMessage
@@ -281,7 +281,7 @@ class ContainerPoolTests
val pool =
system.actorOf(
ContainerPool
- .props(factory, ContainerPoolConfig(1, 1), feed.ref,
Some(PrewarmingConfig(1, exec, alternativeLimit))))
+ .props(factory, ContainerPoolConfig(1, 1), feed.ref,
List(PrewarmingConfig(1, exec, alternativeLimit))))
containers(0).expectMsg(Start(exec, alternativeLimit)) // container0 was
prewarmed
containers(0).send(pool, NeedWork(preWarmedData(exec.kind,
alternativeLimit)))
pool ! runMessage
diff --git a/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala
b/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala
index 688fcd5..7ff2810 100644
--- a/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala
+++ b/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala
@@ -26,6 +26,7 @@ import whisk.core.WhiskConfig
import whisk.core.entity._
import whisk.core.entity.ArgNormalizer.trim
import whisk.core.entity.ExecManifest._
+import whisk.core.entity.size._
import spray.json._
import spray.json.DefaultJsonProtocol._
@@ -59,7 +60,12 @@ trait ExecHelpers extends Matchers with WskActorSystem with
StreamLogging {
protected def js6(code: String, main: Option[String] = None) = {
CodeExecAsString(
- RuntimeManifest(NODEJS6, imagename(NODEJS6), default = Some(true),
deprecated = Some(false)),
+ RuntimeManifest(
+ NODEJS6,
+ imagename(NODEJS6),
+ default = Some(true),
+ deprecated = Some(false),
+ stemCells = Some(List(StemCell(2, 256.MB)))),
trim(code),
main.map(_.trim))
}
@@ -70,7 +76,12 @@ trait ExecHelpers extends Matchers with WskActorSystem with
StreamLogging {
protected def js6MetaData(main: Option[String] = None, binary: Boolean) = {
CodeExecMetaDataAsString(
- RuntimeManifest(NODEJS6, imagename(NODEJS6), default = Some(true),
deprecated = Some(false)),
+ RuntimeManifest(
+ NODEJS6,
+ imagename(NODEJS6),
+ default = Some(true),
+ deprecated = Some(false),
+ stemCells = Some(List(StemCell(2, 256.MB)))),
binary,
main.map(_.trim))
}
diff --git
a/tests/src/test/scala/whisk/core/entity/test/ExecManifestTests.scala
b/tests/src/test/scala/whisk/core/entity/test/ExecManifestTests.scala
index 71f0237..690e449 100644
--- a/tests/src/test/scala/whisk/core/entity/test/ExecManifestTests.scala
+++ b/tests/src/test/scala/whisk/core/entity/test/ExecManifestTests.scala
@@ -19,12 +19,14 @@ package whisk.core.entity.test
import common.{StreamLogging, WskActorSystem}
import org.junit.runner.RunWith
-import org.scalatest.{FlatSpec, Matchers}
import org.scalatest.junit.JUnitRunner
-import spray.json.DefaultJsonProtocol._
+import org.scalatest.{FlatSpec, Matchers}
import spray.json._
+import spray.json.DefaultJsonProtocol._
import whisk.core.entity.ExecManifest
import whisk.core.entity.ExecManifest._
+import whisk.core.entity.size._
+import whisk.core.entity.ByteSize
import scala.util.Success
@@ -63,10 +65,11 @@ class ExecManifestTests extends FlatSpec with
WskActorSystem with StreamLogging
val k1 = RuntimeManifest("k1", ImageName("???"))
val k2 = RuntimeManifest("k2", ImageName("???"), default = Some(true))
val p1 = RuntimeManifest("p1", ImageName("???"))
- val mf = manifestFactory(JsObject("ks" -> Set(k1, k2).toJson, "p1" ->
Set(p1).toJson))
+ val s1 = RuntimeManifest("s1", ImageName("???"), stemCells =
Some(List(StemCell(2, 256.MB))))
+ val mf = manifestFactory(JsObject("ks" -> Set(k1, k2).toJson, "p1" ->
Set(p1).toJson, "s1" -> Set(s1).toJson))
val runtimes = ExecManifest.runtimes(mf, RuntimeManifestConfig()).get
- Seq("k1", "k2", "p1").foreach {
+ Seq("k1", "k2", "p1", "s1").foreach {
runtimes.knownContainerRuntimes.contains(_) shouldBe true
}
@@ -75,9 +78,11 @@ class ExecManifestTests extends FlatSpec with WskActorSystem
with StreamLogging
runtimes.resolveDefaultRuntime("k1") shouldBe Some(k1)
runtimes.resolveDefaultRuntime("k2") shouldBe Some(k2)
runtimes.resolveDefaultRuntime("p1") shouldBe Some(p1)
+ runtimes.resolveDefaultRuntime("s1") shouldBe Some(s1)
runtimes.resolveDefaultRuntime("ks:default") shouldBe Some(k2)
runtimes.resolveDefaultRuntime("p1:default") shouldBe Some(p1)
+ runtimes.resolveDefaultRuntime("s1:default") shouldBe Some(s1)
}
it should "read a valid configuration without default prefix, default tag"
in {
@@ -85,9 +90,15 @@ class ExecManifestTests extends FlatSpec with WskActorSystem
with StreamLogging
val i2 = RuntimeManifest("i2", ImageName("???", Some("ppp")), default =
Some(true))
val j1 = RuntimeManifest("j1", ImageName("???", Some("ppp"), Some("ttt")))
val k1 = RuntimeManifest("k1", ImageName("???", None, Some("ttt")))
+ val s1 = RuntimeManifest("s1", ImageName("???"), stemCells =
Some(List(StemCell(2, 256.MB))))
val mf =
- JsObject("runtimes" -> JsObject("is" -> Set(i1, i2).toJson, "js" ->
Set(j1).toJson, "ks" -> Set(k1).toJson))
+ JsObject(
+ "runtimes" -> JsObject(
+ "is" -> Set(i1, i2).toJson,
+ "js" -> Set(j1).toJson,
+ "ks" -> Set(k1).toJson,
+ "ss" -> Set(s1).toJson))
val rmc = RuntimeManifestConfig(defaultImagePrefix = Some("pre"),
defaultImageTag = Some("test"))
val runtimes = ExecManifest.runtimes(mf, rmc).get
@@ -95,6 +106,9 @@ class ExecManifestTests extends FlatSpec with WskActorSystem
with StreamLogging
runtimes.resolveDefaultRuntime("i2").get.image.publicImageName shouldBe
"ppp/???:test"
runtimes.resolveDefaultRuntime("j1").get.image.publicImageName shouldBe
"ppp/???:ttt"
runtimes.resolveDefaultRuntime("k1").get.image.publicImageName shouldBe
"pre/???:ttt"
+ runtimes.resolveDefaultRuntime("s1").get.image.publicImageName shouldBe
"pre/???:test"
+ runtimes.resolveDefaultRuntime("s1").get.stemCells.get(0).count shouldBe 2
+ runtimes.resolveDefaultRuntime("s1").get.stemCells.get(0).memory shouldBe
256.MB
}
it should "read a valid configuration with blackbox images but without
default prefix or tag" in {
@@ -143,7 +157,7 @@ class ExecManifestTests extends FlatSpec with
WskActorSystem with StreamLogging
an[IllegalArgumentException] should be thrownBy ExecManifest.runtimes(mf,
RuntimeManifestConfig()).get
}
- it should "reject finding a default when none is specified for multiple
versions" in {
+ it should "reject finding a default when none specified for multiple
versions in the same family" in {
val k1 = RuntimeManifest("k1", ImageName("???"))
val k2 = RuntimeManifest("k2", ImageName("???"))
val mf = manifestFactory(JsObject("ks" -> Set(k1, k2).toJson))
@@ -180,4 +194,123 @@ class ExecManifestTests extends FlatSpec with
WskActorSystem with StreamLogging
manifest.get.skipDockerPull(ImageName(prefix = Some("localpre"), name =
"y")) shouldBe true
}
+ it should "de/serialize stem cell configuration" in {
+ val cell = StemCell(3, 128.MB)
+ val cellAsJson = JsObject("count" -> JsNumber(3), "memory" ->
JsString("128 MB"))
+ stemCellSerdes.write(cell) shouldBe cellAsJson
+ stemCellSerdes.read(cellAsJson) shouldBe cell
+
+ an[IllegalArgumentException] shouldBe thrownBy {
+ StemCell(-1, 128.MB)
+ }
+
+ an[IllegalArgumentException] shouldBe thrownBy {
+ StemCell(0, 128.MB)
+ }
+
+ an[IllegalArgumentException] shouldBe thrownBy {
+ val cellAsJson = JsObject("count" -> JsNumber(0), "memory" ->
JsString("128 MB"))
+ stemCellSerdes.read(cellAsJson)
+ }
+
+ the[IllegalArgumentException] thrownBy {
+ val cellAsJson = JsObject("count" -> JsNumber(1), "memory" ->
JsString("128"))
+ stemCellSerdes.read(cellAsJson)
+ } should have message {
+ ByteSize.formatError
+ }
+ }
+
+ it should "parse manifest from JSON string" in {
+ val json = """
+ |{ "runtimes": {
+ | "nodef": [
+ | {
+ | "kind": "nodejs:6",
+ | "image": {
+ | "name": "nodejsaction"
+ | },
+ | "stemCells": [{
+ | "count": 1,
+ | "memory": "128 MB"
+ | }]
+ | }, {
+ | "kind": "nodejs:8",
+ | "default": true,
+ | "image": {
+ | "name": "nodejsaction"
+ | },
+ | "stemCells": [{
+ | "count": 1,
+ | "memory": "128 MB"
+ | }, {
+ | "count": 1,
+ | "memory": "256 MB"
+ | }]
+ | }
+ | ],
+ | "pythonf": [{
+ | "kind": "python",
+ | "image": {
+ | "name": "pythonaction"
+ | },
+ | "stemCells": [{
+ | "count": 2,
+ | "memory": "256 MB"
+ | }]
+ | }],
+ | "swiftf": [{
+ | "kind": "swift",
+ | "image": {
+ | "name": "swiftaction"
+ | },
+ | "stemCells": []
+ | }],
+ | "phpf": [{
+ | "kind": "php",
+ | "image": {
+ | "name": "phpaction"
+ | }
+ | }]
+ | }
+ |}
+ |""".stripMargin.parseJson.asJsObject
+
+ val js6 = RuntimeManifest("nodejs:6", ImageName("nodejsaction"), stemCells
= Some(List(StemCell(1, 128.MB))))
+ val js8 = RuntimeManifest(
+ "nodejs:8",
+ ImageName("nodejsaction"),
+ default = Some(true),
+ stemCells = Some(List(StemCell(1, 128.MB), StemCell(1, 256.MB))))
+ val py = RuntimeManifest("python", ImageName("pythonaction"), stemCells =
Some(List(StemCell(2, 256.MB))))
+ val sw = RuntimeManifest("swift", ImageName("swiftaction"), stemCells =
Some(List.empty))
+ val ph = RuntimeManifest("php", ImageName("phpaction"))
+ val mf = ExecManifest.runtimes(json, RuntimeManifestConfig()).get
+
+ mf shouldBe {
+ Runtimes(
+ Set(
+ RuntimeFamily("nodef", Set(js6, js8)),
+ RuntimeFamily("pythonf", Set(py)),
+ RuntimeFamily("swiftf", Set(sw)),
+ RuntimeFamily("phpf", Set(ph))),
+ Set.empty,
+ None)
+ }
+
+ def stemCellFactory(m: RuntimeManifest, cells: List[StemCell]) = cells.map
{ c =>
+ (m.kind, m.image, c.count, c.memory)
+ }
+
+ mf.stemcells.flatMap {
+ case (m, cells) =>
+ cells.map { c =>
+ (m.kind, m.image, c.count, c.memory)
+ }
+ }.toList should contain theSameElementsAs List(
+ (js6.kind, js6.image, 1, 128.MB),
+ (js8.kind, js8.image, 1, 128.MB),
+ (js8.kind, js8.image, 1, 256.MB),
+ (py.kind, py.image, 2, 256.MB))
+ }
}
--
To stop receiving notification emails like this one, please contact
[email protected].