You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by ra...@apache.org on 2018/02/13 19:53:59 UTC

[incubator-openwhisk] branch master updated: Add binary, image, and main properties to WhiskActionMetaData (#3109)

This is an automated email from the ASF dual-hosted git repository.

rabbah 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 a2dcace  Add binary, image, and main properties to WhiskActionMetaData (#3109)
a2dcace is described below

commit a2dcace3d4cbef737cb5875c758f7e5d2b6d9f3c
Author: James Dubee <jw...@us.ibm.com>
AuthorDate: Tue Feb 13 14:53:57 2018 -0500

    Add binary, image, and main properties to WhiskActionMetaData (#3109)
    
    Make the reduced action view more like the full view.
---
 .../src/main/scala/whisk/core/entity/Exec.scala    |  64 +++++++--
 .../core/controller/test/ActionsApiTests.scala     | 143 ++++++++++++++++++---
 .../core/controller/test/WebActionsApiTests.scala  |   2 +-
 .../scala/whisk/core/entity/test/ExecHelpers.scala |  18 ++-
 4 files changed, 196 insertions(+), 31 deletions(-)

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 bf066f6..8f31812 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 9018759..fb00184 100644
--- a/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala
@@ -173,26 +173,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)
+        }
     }
   }
 
@@ -437,7 +547,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 57c8401..2bcfe91 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 c0f4ea8..688fcd5 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)
+  }
 }

-- 
To stop receiving notification emails like this one, please contact
rabbah@apache.org.