rabbah closed pull request #3109: Add binary, image, and main properties to WhiskActionMetaData URL: https://github.com/apache/incubator-openwhisk/pull/3109
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/common/scala/src/main/scala/whisk/core/entity/Exec.scala b/common/scala/src/main/scala/whisk/core/entity/Exec.scala index bf066f6e71..8f318129d0 100644 --- a/common/scala/src/main/scala/whisk/core/entity/Exec.scala +++ b/common/scala/src/main/scala/whisk/core/entity/Exec.scala @@ -95,9 +95,23 @@ sealed abstract class CodeExec[+T <% SizeConversion] extends Exec { sealed abstract class ExecMetaData extends ExecMetaDataBase { + /** An entrypoint (typically name of 'main' function). 'None' means a default value will be used. */ + val entryPoint: Option[String] + + /** The runtime image (either built-in or a public image). */ + val image: ImageName + /** Indicates if a container image is required from the registry to execute the action. */ val pull: Boolean + /** + * Indicates whether the code is stored in a text-readable or binary format. + * The binary bit may be read from the database but currently it is always computed + * when the "code" is moved to an attachment this may get changed to avoid recomputing + * the binary property. + */ + val binary: Boolean + override def size = 0.B } @@ -114,8 +128,12 @@ protected[core] case class CodeExecAsString(manifest: RuntimeManifest, override def codeAsJson = JsString(code) } -protected[core] case class CodeExecMetaDataAsString(manifest: RuntimeManifest) extends ExecMetaData { +protected[core] case class CodeExecMetaDataAsString(manifest: RuntimeManifest, + override val binary: Boolean = false, + override val entryPoint: Option[String]) + extends ExecMetaData { override val kind = manifest.kind + override val image = manifest.image override val deprecated = manifest.deprecated.getOrElse(false) override val pull = false } @@ -144,8 +162,12 @@ protected[core] case class CodeExecAsAttachment(manifest: RuntimeManifest, } } -protected[core] case class CodeExecMetaDataAsAttachment(manifest: RuntimeManifest) extends ExecMetaData { +protected[core] case class CodeExecMetaDataAsAttachment(manifest: RuntimeManifest, + override val binary: Boolean = false, + override val entryPoint: Option[String]) + extends ExecMetaData { override val kind = manifest.kind + override val image = manifest.image override val deprecated = manifest.deprecated.getOrElse(false) override val pull = false } @@ -168,7 +190,11 @@ protected[core] case class BlackBoxExec(override val image: ImageName, override def size = super.size + image.publicImageName.sizeInBytes } -protected[core] case class BlackBoxExecMetaData(val native: Boolean) extends ExecMetaData { +protected[core] case class BlackBoxExecMetaData(override val image: ImageName, + override val entryPoint: Option[String], + val native: Boolean, + override val binary: Boolean = false) + extends ExecMetaData { override val kind = ExecMetaDataBase.BLACKBOX override val deprecated = false override val pull = !native @@ -334,21 +360,24 @@ protected[core] object ExecMetaDataBase extends ArgNormalizer[ExecMetaDataBase] override def write(e: ExecMetaDataBase) = e match { case c: CodeExecMetaDataAsString => - val base = Map("kind" -> JsString(c.kind)) - JsObject(base) + val base = Map("kind" -> JsString(c.kind), "binary" -> JsBoolean(c.binary)) + val main = c.entryPoint.map("main" -> JsString(_)) + JsObject(base ++ main) case a: CodeExecMetaDataAsAttachment => val base = - Map("kind" -> JsString(a.kind)) - JsObject(base) + Map("kind" -> JsString(a.kind), "binary" -> JsBoolean(a.binary)) + val main = a.entryPoint.map("main" -> JsString(_)) + JsObject(base ++ main) case s @ SequenceExecMetaData(comp) => JsObject("kind" -> JsString(s.kind), "components" -> comp.map(_.qualifiedNameWithLeadingSlash).toJson) case b: BlackBoxExecMetaData => val base = - Map("kind" -> JsString(b.kind)) - JsObject(base) + Map("kind" -> JsString(b.kind), "image" -> JsString(b.image.publicImageName), "binary" -> JsBoolean(b.binary)) + val main = b.entryPoint.map("main" -> JsString(_)) + JsObject(base ++ main) } override def read(v: JsValue) = { @@ -368,6 +397,11 @@ protected[core] object ExecMetaDataBase extends ArgNormalizer[ExecMetaDataBase] case None => None } + lazy val binary: Boolean = obj.fields.get("binary") match { + case Some(JsBoolean(b)) => b + case _ => throw new DeserializationException("'binary' must be a boolean defined in 'exec'") + } + kind match { case ExecMetaDataBase.SEQUENCE => val comp: Vector[FullyQualifiedEntityName] = obj.fields.get("components") match { @@ -385,7 +419,7 @@ protected[core] object ExecMetaDataBase extends ArgNormalizer[ExecMetaDataBase] s"'image' must be a string defined in 'exec' for '${Exec.BLACKBOX}' actions") } val native = execManifests.skipDockerPull(image) - BlackBoxExecMetaData(native) + BlackBoxExecMetaData(image, optMainField, native, binary) case _ => // map "default" virtual runtime versions to the currently blessed actual runtime version @@ -396,10 +430,16 @@ protected[core] object ExecMetaDataBase extends ArgNormalizer[ExecMetaDataBase] manifest.attached .map { a => - CodeExecMetaDataAsAttachment(manifest) + val main = optMainField.orElse { + if (manifest.requireMain.exists(identity)) { + throw new DeserializationException(s"'main' must be a string defined in 'exec' for '$kind' actions") + } else None + } + + CodeExecMetaDataAsAttachment(manifest, binary, main) } .getOrElse { - CodeExecMetaDataAsString(manifest) + CodeExecMetaDataAsString(manifest, binary, optMainField) } } } diff --git a/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala b/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala index 3696063942..b2d60d12fa 100644 --- a/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala +++ b/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala @@ -159,26 +159,136 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { } } + def getExecPermutations() = { + implicit val tid = transid() + + // BlackBox: binary: true, main: bbMain + val bbAction1 = WhiskAction(namespace, aname(), bb("bb", "RHViZWU=", Some("bbMain"))) + val bbAction1Content = Map("exec" -> Map( + "kind" -> Exec.BLACKBOX, + "code" -> "RHViZWU=", + "image" -> "bb", + "main" -> "bbMain")).toJson.asJsObject + val bbAction1ExecMetaData = blackBoxMetaData("bb", Some("bbMain"), true) + + // BlackBox: binary: false, main: bbMain + val bbAction2 = WhiskAction(namespace, aname(), bb("bb", "", Some("bbMain"))) + val bbAction2Content = + Map("exec" -> Map("kind" -> Exec.BLACKBOX, "code" -> "", "image" -> "bb", "main" -> "bbMain")).toJson.asJsObject + val bbAction2ExecMetaData = blackBoxMetaData("bb", Some("bbMain"), false) + + // BlackBox: binary: true, no main + val bbAction3 = WhiskAction(namespace, aname(), bb("bb", "RHViZWU=")) + val bbAction3Content = + Map("exec" -> Map("kind" -> Exec.BLACKBOX, "code" -> "RHViZWU=", "image" -> "bb")).toJson.asJsObject + val bbAction3ExecMetaData = blackBoxMetaData("bb", None, true) + + // BlackBox: binary: false, no main + val bbAction4 = WhiskAction(namespace, aname(), bb("bb", "")) + val bbAction4Content = Map("exec" -> Map("kind" -> Exec.BLACKBOX, "code" -> "", "image" -> "bb")).toJson.asJsObject + val bbAction4ExecMetaData = blackBoxMetaData("bb", None, false) + + // Attachment: binary: true, main: javaMain + val javaAction1 = WhiskAction(namespace, aname(), javaDefault("RHViZWU=", Some("javaMain"))) + val javaAction1Content = + Map("exec" -> Map("kind" -> JAVA_DEFAULT, "code" -> "RHViZWU=", "main" -> "javaMain")).toJson.asJsObject + val javaAction1ExecMetaData = javaMetaData(Some("javaMain"), true) + + // String: binary: true, main: jsMain + val jsAction1 = WhiskAction(namespace, aname(), jsDefault("RHViZWU=", Some("jsMain"))) + val jsAction1Content = + Map("exec" -> Map("kind" -> NODEJS6, "code" -> "RHViZWU=", "main" -> "jsMain")).toJson.asJsObject + val jsAction1ExecMetaData = js6MetaData(Some("jsMain"), true) + + // String: binary: false, main: jsMain + val jsAction2 = WhiskAction(namespace, aname(), jsDefault("", Some("jsMain"))) + val jsAction2Content = Map("exec" -> Map("kind" -> NODEJS6, "code" -> "", "main" -> "jsMain")).toJson.asJsObject + val jsAction2ExecMetaData = js6MetaData(Some("jsMain"), false) + + // String: binary: true, no main + val jsAction3 = WhiskAction(namespace, aname(), jsDefault("RHViZWU=")) + val jsAction3Content = Map("exec" -> Map("kind" -> NODEJS6, "code" -> "RHViZWU=")).toJson.asJsObject + val jsAction3ExecMetaData = js6MetaData(None, true) + + // String: binary: false, no main + val jsAction4 = WhiskAction(namespace, aname(), jsDefault("")) + val jsAction4Content = Map("exec" -> Map("kind" -> NODEJS6, "code" -> "")).toJson.asJsObject + val jsAction4ExecMetaData = js6MetaData(None, false) + + // Sequence + val component = WhiskAction(namespace, aname(), jsDefault("??")) + put(entityStore, component) + val components = Vector(s"/$namespace/${component.name}").map(stringToFullyQualifiedName(_)) + val seqAction = WhiskAction(namespace, aname(), sequence(components), seqParameters(components)) + val seqActionContent = JsObject( + "exec" -> JsObject("kind" -> "sequence".toJson, "components" -> JsArray(s"/$namespace/${component.name}".toJson))) + val seqActionExecMetaData = sequenceMetaData(components) + + Seq( + (bbAction1, bbAction1Content, bbAction1ExecMetaData), + (bbAction2, bbAction2Content, bbAction2ExecMetaData), + (bbAction3, bbAction3Content, bbAction3ExecMetaData), + (bbAction4, bbAction4Content, bbAction4ExecMetaData), + (javaAction1, javaAction1Content, javaAction1ExecMetaData), + (jsAction1, jsAction1Content, jsAction1ExecMetaData), + (jsAction2, jsAction2Content, jsAction2ExecMetaData), + (jsAction3, jsAction3Content, jsAction3ExecMetaData), + (jsAction4, jsAction4Content, jsAction4ExecMetaData), + (seqAction, seqActionContent, seqActionExecMetaData)) + } + it should "get action using code query parameter" in { implicit val tid = transid() - val action = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b")) - put(entityStore, action) + getExecPermutations.foreach { + case (action, content, execMetaData) => + val expectedWhiskAction = WhiskAction( + action.namespace, + action.name, + action.exec, + action.parameters, + action.limits, + action.version, + action.publish, + action.annotations ++ Parameters(WhiskAction.execFieldName, action.exec.kind)) - Get(s"$collectionPath/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check { - status should be(OK) - val response = responseAs[JsObject] - response.fields("exec").asJsObject.fields should not(contain key "code") - responseAs[WhiskActionMetaData] shouldBe a[WhiskActionMetaData] - } + val expectedWhiskActionMetaData = WhiskActionMetaData( + action.namespace, + action.name, + execMetaData, + action.parameters, + action.limits, + action.version, + action.publish, + action.annotations ++ Parameters(WhiskActionMetaData.execFieldName, action.exec.kind)) - Seq(s"$collectionPath/${action.name}", s"$collectionPath/${action.name}?code=true").foreach { path => - Get(path) ~> Route.seal(routes(creds)) ~> check { - status should be(OK) - val response = responseAs[JsObject] - response.fields("exec").asJsObject.fields("code") should be("??".toJson) - responseAs[WhiskAction] shouldBe a[WhiskAction] - } + Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val response = responseAs[WhiskAction] + response should be(expectedWhiskAction) + } + + Get(s"$collectionPath/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val responseJson = responseAs[JsObject] + responseJson.fields("exec").asJsObject.fields should not(contain key "code") + val response = responseAs[WhiskActionMetaData] + response should be(expectedWhiskActionMetaData) + } + + Seq(s"$collectionPath/${action.name}", s"$collectionPath/${action.name}?code=true").foreach { path => + Get(path) ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val response = responseAs[WhiskAction] + response should be(expectedWhiskAction) + } + } + + Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val response = responseAs[WhiskAction] + response should be(expectedWhiskAction) + } } } @@ -423,7 +533,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { } private implicit val fqnSerdes = FullyQualifiedEntityName.serdes - private def seqParameters(seq: Vector[FullyQualifiedEntityName]) = Parameters("_actions", seq.toJson) + private def seqParameters(seq: Vector[FullyQualifiedEntityName]) = + Parameters("_actions", seq.map("/" + _.asString).toJson) // this test is sneaky; the installation of the sequence is done directly in the db // and api checks are skipped diff --git a/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala b/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala index 57c8401a08..2bcfe9122f 100644 --- a/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala +++ b/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala @@ -190,7 +190,7 @@ trait WebActionsApiBaseTests extends ControllerTestCommon with BeforeAndAfterEac WhiskActionMetaData( actionName.path, actionName.name, - js6MetaData(), + js6MetaData(binary = false), defaultActionParameters, annotations = { if (actionName.name.asString.startsWith("export_")) { 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 c0f4ea873d..688fcd5b7e 100644 --- a/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala +++ b/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala @@ -68,9 +68,11 @@ trait ExecHelpers extends Matchers with WskActorSystem with StreamLogging { js6(code, main) } - protected def js6MetaData(main: Option[String] = None) = { + 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)), + binary, + main.map(_.trim)) } protected def javaDefault(code: String, main: Option[String] = None) = { @@ -80,6 +82,12 @@ trait ExecHelpers extends Matchers with WskActorSystem with StreamLogging { CodeExecAsAttachment(manifest, attachment, main.map(_.trim)) } + protected def javaMetaData(main: Option[String] = None, binary: Boolean) = { + val manifest = ExecManifest.runtimesManifest.resolveDefaultRuntime(JAVA_DEFAULT).get + + CodeExecMetaDataAsAttachment(manifest, binary, main.map(_.trim)) + } + protected def swift(code: String, main: Option[String] = None) = { CodeExecAsString(RuntimeManifest(SWIFT, imagename(SWIFT), deprecated = Some(true)), trim(code), main.map(_.trim)) } @@ -94,9 +102,15 @@ trait ExecHelpers extends Matchers with WskActorSystem with StreamLogging { protected def sequence(components: Vector[FullyQualifiedEntityName]) = SequenceExec(components) + protected def sequenceMetaData(components: Vector[FullyQualifiedEntityName]) = SequenceExecMetaData(components) + protected def bb(image: String) = BlackBoxExec(ExecManifest.ImageName(trim(image)), None, None, false) protected def bb(image: String, code: String, main: Option[String] = None) = { BlackBoxExec(ExecManifest.ImageName(trim(image)), Some(trim(code)).filter(_.nonEmpty), main, false) } + + protected def blackBoxMetaData(image: String, main: Option[String] = None, binary: Boolean) = { + BlackBoxExecMetaData(ExecManifest.ImageName(trim(image)), main, false, binary) + } } ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services