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 2017/11/09 14:55:21 UTC

[incubator-openwhisk] branch master updated: Reduce memory consumption for action invocation (#2730)

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 9be13e2  Reduce memory consumption for action invocation (#2730)
9be13e2 is described below

commit 9be13e2c6d581f42b60351f05a11e4aa71ca31d9
Author: James Dubee <jw...@us.ibm.com>
AuthorDate: Thu Nov 9 09:55:19 2017 -0500

    Reduce memory consumption for action invocation (#2730)
---
 .../core/database/ArtifactStoreProvider.scala      |   2 +
 .../whisk/core/database/CouchDbRestStore.scala     |  12 +-
 .../whisk/core/database/CouchDbStoreProvider.scala |   2 +
 .../core/database/RemoteCacheInvalidation.scala    |   5 +
 .../DocumentReader.scala}                          |  22 +--
 .../src/main/scala/whisk/core/entity/Exec.scala    | 155 ++++++++++++++++++++-
 .../main/scala/whisk/core/entity/WhiskAction.scala | 147 +++++++++++++++++++
 .../main/scala/whisk/core/entity/WhiskEntity.scala |  14 ++
 .../main/scala/whisk/core/entity/WhiskStore.scala  |  11 +-
 .../src/main/scala/whisk/http/ErrorResponse.scala  |   7 +
 .../main/scala/whisk/core/controller/Actions.scala |  16 ++-
 .../scala/whisk/core/controller/Controller.scala   |   5 +-
 .../scala/whisk/core/controller/WebActions.scala   |  14 +-
 .../controller/actions/PostActionActivation.scala  |   4 +-
 .../core/controller/actions/PrimitiveActions.scala |   2 +-
 .../core/controller/actions/SequenceActions.scala  |  16 +--
 .../scala/whisk/core/entitlement/Entitlement.scala |   4 +
 .../core/loadBalancer/LoadBalancerService.scala    |  12 +-
 .../core/controller/test/ActionsApiTests.scala     |  13 ++
 .../core/controller/test/ActivationsApiTests.scala |   7 +-
 .../controller/test/ControllerTestCommon.scala     |   2 +-
 .../core/controller/test/WebActionsApiTests.scala  |   8 +-
 .../scala/whisk/core/entity/test/ExecHelpers.scala |   9 ++
 23 files changed, 432 insertions(+), 57 deletions(-)

diff --git a/common/scala/src/main/scala/whisk/core/database/ArtifactStoreProvider.scala b/common/scala/src/main/scala/whisk/core/database/ArtifactStoreProvider.scala
index 2080d47..e8cac18 100644
--- a/common/scala/src/main/scala/whisk/core/database/ArtifactStoreProvider.scala
+++ b/common/scala/src/main/scala/whisk/core/database/ArtifactStoreProvider.scala
@@ -23,6 +23,7 @@ import spray.json.RootJsonFormat
 import whisk.common.Logging
 import whisk.core.WhiskConfig
 import whisk.spi.Spi
+import whisk.core.entity.DocumentReader
 
 /**
  * An Spi for providing ArtifactStore implementations
@@ -32,6 +33,7 @@ trait ArtifactStoreProvider extends Spi {
                                          name: WhiskConfig => String,
                                          useBatching: Boolean = false)(
     implicit jsonFormat: RootJsonFormat[D],
+    docReader: DocumentReader,
     actorSystem: ActorSystem,
     logging: Logging,
     materializer: ActorMaterializer): ArtifactStore[D]
diff --git a/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala b/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala
index 0e66aee..298cf09 100644
--- a/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala
+++ b/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala
@@ -36,6 +36,7 @@ import whisk.core.entity.DocInfo
 import whisk.core.entity.DocRevision
 import whisk.core.entity.WhiskDocument
 import whisk.http.Messages
+import whisk.core.entity.DocumentReader
 
 /**
  * Basic client to put and delete artifacts in a data store.
@@ -58,7 +59,8 @@ class CouchDbRestStore[DocumentAbstraction <: DocumentSerializer](dbProtocol: St
   implicit system: ActorSystem,
   val logging: Logging,
   jsonFormat: RootJsonFormat[DocumentAbstraction],
-  materializer: ActorMaterializer)
+  materializer: ActorMaterializer,
+  docReader: DocumentReader)
     extends ArtifactStore[DocumentAbstraction]
     with DefaultJsonProtocol {
 
@@ -223,7 +225,13 @@ class CouchDbRestStore[DocumentAbstraction <: DocumentSerializer](dbProtocol: St
       e match {
         case Right(response) =>
           transid.finished(this, start, s"[GET] '$dbName' completed: found document '$doc'")
-          val asFormat = jsonFormat.read(response)
+
+          val asFormat = try {
+            docReader.read(ma, response)
+          } catch {
+            case e: Exception => jsonFormat.read(response)
+          }
+
           if (asFormat.getClass != ma.runtimeClass) {
             throw DocumentTypeMismatchException(
               s"document type ${asFormat.getClass} did not match expected type ${ma.runtimeClass}.")
diff --git a/common/scala/src/main/scala/whisk/core/database/CouchDbStoreProvider.scala b/common/scala/src/main/scala/whisk/core/database/CouchDbStoreProvider.scala
index d2c08dd..08b915e 100644
--- a/common/scala/src/main/scala/whisk/core/database/CouchDbStoreProvider.scala
+++ b/common/scala/src/main/scala/whisk/core/database/CouchDbStoreProvider.scala
@@ -22,11 +22,13 @@ import akka.stream.ActorMaterializer
 import spray.json.RootJsonFormat
 import whisk.common.Logging
 import whisk.core.WhiskConfig
+import whisk.core.entity.DocumentReader
 
 object CouchDbStoreProvider extends ArtifactStoreProvider {
 
   def makeStore[D <: DocumentSerializer](config: WhiskConfig, name: WhiskConfig => String, useBatching: Boolean)(
     implicit jsonFormat: RootJsonFormat[D],
+    docReader: DocumentReader,
     actorSystem: ActorSystem,
     logging: Logging,
     materializer: ActorMaterializer): ArtifactStore[D] = {
diff --git a/common/scala/src/main/scala/whisk/core/database/RemoteCacheInvalidation.scala b/common/scala/src/main/scala/whisk/core/database/RemoteCacheInvalidation.scala
index 426b602..b615708 100644
--- a/common/scala/src/main/scala/whisk/core/database/RemoteCacheInvalidation.scala
+++ b/common/scala/src/main/scala/whisk/core/database/RemoteCacheInvalidation.scala
@@ -36,6 +36,7 @@ import whisk.core.connector.MessagingProvider
 import whisk.core.entity.CacheKey
 import whisk.core.entity.InstanceId
 import whisk.core.entity.WhiskAction
+import whisk.core.entity.WhiskActionMetaData
 import whisk.core.entity.WhiskPackage
 import whisk.core.entity.WhiskRule
 import whisk.core.entity.WhiskTrigger
@@ -76,12 +77,16 @@ class RemoteCacheInvalidation(config: WhiskConfig, component: String, instance:
       removeFromLocalCache)
   })
 
+  def invalidateWhiskActionMetaData(key: CacheKey) =
+    WhiskActionMetaData.removeId(key)
+
   private def removeFromLocalCache(bytes: Array[Byte]): Future[Unit] = Future {
     val raw = new String(bytes, StandardCharsets.UTF_8)
 
     CacheInvalidationMessage.parse(raw) match {
       case Success(msg: CacheInvalidationMessage) => {
         if (msg.instanceId != instanceId) {
+          WhiskActionMetaData.removeId(msg.key)
           WhiskAction.removeId(msg.key)
           WhiskPackage.removeId(msg.key)
           WhiskRule.removeId(msg.key)
diff --git a/common/scala/src/main/scala/whisk/core/database/ArtifactStoreProvider.scala b/common/scala/src/main/scala/whisk/core/entity/DocumentReader.scala
similarity index 54%
copy from common/scala/src/main/scala/whisk/core/database/ArtifactStoreProvider.scala
copy to common/scala/src/main/scala/whisk/core/entity/DocumentReader.scala
index 2080d47..ec53cf7 100644
--- a/common/scala/src/main/scala/whisk/core/database/ArtifactStoreProvider.scala
+++ b/common/scala/src/main/scala/whisk/core/entity/DocumentReader.scala
@@ -15,24 +15,10 @@
  * limitations under the License.
  */
 
-package whisk.core.database
+package whisk.core.entity
 
-import akka.actor.ActorSystem
-import akka.stream.ActorMaterializer
-import spray.json.RootJsonFormat
-import whisk.common.Logging
-import whisk.core.WhiskConfig
-import whisk.spi.Spi
+import spray.json._
 
-/**
- * An Spi for providing ArtifactStore implementations
- */
-trait ArtifactStoreProvider extends Spi {
-  def makeStore[D <: DocumentSerializer](config: WhiskConfig,
-                                         name: WhiskConfig => String,
-                                         useBatching: Boolean = false)(
-    implicit jsonFormat: RootJsonFormat[D],
-    actorSystem: ActorSystem,
-    logging: Logging,
-    materializer: ActorMaterializer): ArtifactStore[D]
+protected[core] abstract class DocumentReader {
+  def read[A](ma: Manifest[A], value: JsValue): WhiskDocument
 }
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 49ede1d..3ea1ec5 100644
--- a/common/scala/src/main/scala/whisk/core/entity/Exec.scala
+++ b/common/scala/src/main/scala/whisk/core/entity/Exec.scala
@@ -45,7 +45,7 @@ import whisk.core.entity.size.SizeString
  *   main  : name of the entry point function, when using a non-default value (for Java, the name of the main class)" }
  */
 sealed abstract class Exec extends ByteSizeable {
-  override def toString = Exec.serdes.write(this).compactPrint
+  override def toString: String = Exec.serdes.write(this).compactPrint
 
   /** A type descriptor. */
   val kind: String
@@ -54,6 +54,10 @@ sealed abstract class Exec extends ByteSizeable {
   val deprecated: Boolean
 }
 
+sealed abstract class ExecMetaDataBase extends Exec {
+  override def toString: String = ExecMetaDataBase.serdes.write(this).compactPrint
+}
+
 /**
  * A common super class for all action exec types that contain their executable
  * code explicitly (i.e., any action other than a sequence).
@@ -89,6 +93,14 @@ sealed abstract class CodeExec[+T <% SizeConversion] extends Exec {
   override def size = code.sizeInBytes + entryPoint.map(_.sizeInBytes).getOrElse(0.B)
 }
 
+sealed abstract class ExecMetaData extends ExecMetaDataBase {
+
+  /** Indicates if a container image is required from the registry to execute the action. */
+  val pull: Boolean
+
+  override def size = 0.B
+}
+
 protected[core] case class CodeExecAsString(manifest: RuntimeManifest,
                                             override val code: String,
                                             override val entryPoint: Option[String])
@@ -102,6 +114,12 @@ protected[core] case class CodeExecAsString(manifest: RuntimeManifest,
   override def codeAsJson = JsString(code)
 }
 
+protected[core] case class CodeExecMetaDataAsString(manifest: RuntimeManifest) extends ExecMetaData {
+  override val kind = manifest.kind
+  override val deprecated = manifest.deprecated.getOrElse(false)
+  override val pull = false
+}
+
 protected[core] case class CodeExecAsAttachment(manifest: RuntimeManifest,
                                                 override val code: Attachment[String],
                                                 override val entryPoint: Option[String])
@@ -126,6 +144,12 @@ protected[core] case class CodeExecAsAttachment(manifest: RuntimeManifest,
   }
 }
 
+protected[core] case class CodeExecMetaDataAsAttachment(manifest: RuntimeManifest) extends ExecMetaData {
+  override val kind = manifest.kind
+  override val deprecated = manifest.deprecated.getOrElse(false)
+  override val pull = false
+}
+
 /**
  * @param image the image name
  * @param code an optional script or zip archive (as base64 encoded) string
@@ -144,12 +168,24 @@ 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 {
+  override val kind = ExecMetaDataBase.BLACKBOX
+  override val deprecated = false
+  override val pull = !native
+}
+
 protected[core] case class SequenceExec(components: Vector[FullyQualifiedEntityName]) extends Exec {
   override val kind = Exec.SEQUENCE
   override val deprecated = false
   override def size = components.map(_.size).reduceOption(_ + _).getOrElse(0.B)
 }
 
+protected[core] case class SequenceExecMetaData(components: Vector[FullyQualifiedEntityName]) extends ExecMetaDataBase {
+  override val kind = ExecMetaDataBase.SEQUENCE
+  override val deprecated = false
+  override def size = components.map(_.size).reduceOption(_ + _).getOrElse(0.B)
+}
+
 protected[core] object Exec extends ArgNormalizer[Exec] with DefaultJsonProtocol {
 
   val sizeLimit = 48 MB
@@ -187,6 +223,7 @@ protected[core] object Exec extends ArgNormalizer[Exec] with DefaultJsonProtocol
         val code = b.code.filter(_.trim.nonEmpty).map("code" -> JsString(_))
         val main = b.entryPoint.map("main" -> JsString(_))
         JsObject(base ++ code ++ main)
+      case _ => JsObject()
     }
 
     override def read(v: JsValue) = {
@@ -278,3 +315,119 @@ protected[core] object Exec extends ArgNormalizer[Exec] with DefaultJsonProtocol
     } else false
   }
 }
+
+protected[core] object ExecMetaDataBase extends ArgNormalizer[ExecMetaDataBase] with DefaultJsonProtocol {
+
+  val sizeLimit = 48 MB
+
+  // The possible values of the JSON 'kind' field for certain runtimes:
+  // - Sequence because it is an intrinsic
+  // - Black Box because it is a type marker
+  protected[core] val SEQUENCE = "sequence"
+  protected[core] val BLACKBOX = "blackbox"
+
+  private def execManifests = ExecManifest.runtimesManifest
+
+  override protected[core] implicit lazy val serdes = new RootJsonFormat[ExecMetaDataBase] {
+    private def attFmt[T: JsonFormat] = Attachments.serdes[T]
+    private lazy val runtimes: Set[String] = execManifests.knownContainerRuntimes ++ Set(SEQUENCE, BLACKBOX)
+
+    override def write(e: ExecMetaDataBase) = e match {
+      case c: CodeExecMetaDataAsString =>
+        val base = Map("kind" -> JsString(c.kind))
+        JsObject(base)
+
+      case a: CodeExecMetaDataAsAttachment =>
+        val base =
+          Map("kind" -> JsString(a.kind))
+        JsObject(base)
+
+      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)
+    }
+
+    override def read(v: JsValue) = {
+      require(v != null)
+
+      val obj = v.asJsObject
+
+      val kind = obj.fields.get("kind") match {
+        case Some(JsString(k)) => k.trim.toLowerCase
+        case _                 => throw new DeserializationException("'kind' must be a string defined in 'exec'")
+      }
+
+      lazy val optMainField: Option[String] = obj.fields.get("main") match {
+        case Some(JsString(m)) => Some(m)
+        case Some(_) =>
+          throw new DeserializationException(s"if defined, 'main' be a string in 'exec' for '$kind' actions")
+        case None => None
+      }
+
+      kind match {
+        case ExecMetaDataBase.SEQUENCE =>
+          val comp: Vector[FullyQualifiedEntityName] = obj.fields.get("components") match {
+            case Some(JsArray(components)) => components map (FullyQualifiedEntityName.serdes.read(_))
+            case Some(_)                   => throw new DeserializationException(s"'components' must be an array")
+            case None                      => throw new DeserializationException(s"'components' must be defined for sequence kind")
+          }
+          SequenceExecMetaData(comp)
+
+        case ExecMetaDataBase.BLACKBOX =>
+          val image: ImageName = obj.fields.get("image") match {
+            case Some(JsString(i)) => ImageName.fromString(i).get // throws deserialization exception on failure
+            case _ =>
+              throw new DeserializationException(
+                s"'image' must be a string defined in 'exec' for '${Exec.BLACKBOX}' actions")
+          }
+          val code: Option[String] = obj.fields.get("code") match {
+            case Some(JsString(i)) => if (i.trim.nonEmpty) Some(i) else None
+            case Some(_) =>
+              throw new DeserializationException(
+                s"if defined, 'code' must a string defined in 'exec' for '${Exec.BLACKBOX}' actions")
+            case None => None
+          }
+          val native = execManifests.blackboxImages.contains(image)
+          BlackBoxExecMetaData(native)
+
+        case _ =>
+          // map "default" virtual runtime versions to the currently blessed actual runtime version
+          val manifest = execManifests.resolveDefaultRuntime(kind) match {
+            case Some(k) => k
+            case None    => throw new DeserializationException(s"kind '$kind' not in $runtimes")
+          }
+
+          manifest.attached
+            .map { a =>
+              val jar: Attachment[String] = {
+                // java actions once stored the attachment in "jar" instead of "code"
+                obj.fields.get("code").orElse(obj.fields.get("jar"))
+              } map {
+                attFmt[String].read(_)
+              } getOrElse {
+                throw new DeserializationException(
+                  s"'code' must be a valid base64 string in 'exec' for '$kind' actions")
+              }
+              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)
+            }
+            .getOrElse {
+              val code: String = obj.fields.get("code") match {
+                case Some(JsString(c)) => c
+                case _ =>
+                  throw new DeserializationException(s"'code' must be a string defined in 'exec' for '$kind' actions")
+              }
+              CodeExecMetaDataAsString(manifest)
+            }
+      }
+    }
+  }
+}
diff --git a/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala
index c90367e..41af1aa 100644
--- a/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala
+++ b/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala
@@ -100,6 +100,10 @@ abstract class WhiskActionLike(override val name: EntityName) extends WhiskEntit
       "annotations" -> annotations.toJson)
 }
 
+abstract class WhiskActionLikeMetaData(override val name: EntityName) extends WhiskActionLike(name) {
+  override def exec: ExecMetaDataBase
+}
+
 /**
  * A WhiskAction provides an abstraction of the meta-data
  * for a whisk action.
@@ -161,6 +165,51 @@ case class WhiskAction(namespace: EntityPath,
   }
 }
 
+@throws[IllegalArgumentException]
+case class WhiskActionMetaData(namespace: EntityPath,
+                               override val name: EntityName,
+                               exec: ExecMetaDataBase,
+                               parameters: Parameters = Parameters(),
+                               limits: ActionLimits = ActionLimits(),
+                               version: SemVer = SemVer(),
+                               publish: Boolean = false,
+                               annotations: Parameters = Parameters())
+    extends WhiskActionLikeMetaData(name) {
+
+  require(exec != null, "exec undefined")
+  require(limits != null, "limits undefined")
+
+  /**
+   * Merges parameters (usually from package) with existing action parameters.
+   * Existing parameters supersede those in p.
+   */
+  def inherit(p: Parameters) = copy(parameters = p ++ parameters).revision[WhiskActionMetaData](rev)
+
+  /**
+   * Resolves sequence components if they contain default namespace.
+   */
+  protected[core] def resolve(userNamespace: EntityName): WhiskActionMetaData = {
+    exec match {
+      case SequenceExecMetaData(components) =>
+        val newExec = SequenceExecMetaData(components map { c =>
+          FullyQualifiedEntityName(c.path.resolveNamespace(userNamespace), c.name)
+        })
+        copy(exec = newExec).revision[WhiskActionMetaData](rev)
+      case _ => this
+    }
+  }
+
+  def toExecutableWhiskAction = exec match {
+    case execMetaData: ExecMetaData =>
+      Some(
+        ExecutableWhiskActionMetaData(namespace, name, execMetaData, parameters, limits, version, publish, annotations)
+          .revision[ExecutableWhiskActionMetaData](rev))
+    case _ =>
+      None
+  }
+
+}
+
 /**
  * Variant of WhiskAction which only includes information necessary to be
  * executed by an Invoker.
@@ -206,6 +255,25 @@ case class ExecutableWhiskAction(namespace: EntityPath,
     WhiskAction(namespace, name, exec, parameters, limits, version, publish, annotations).revision[WhiskAction](rev)
 }
 
+@throws[IllegalArgumentException]
+case class ExecutableWhiskActionMetaData(namespace: EntityPath,
+                                         override val name: EntityName,
+                                         exec: ExecMetaData,
+                                         parameters: Parameters = Parameters(),
+                                         limits: ActionLimits = ActionLimits(),
+                                         version: SemVer = SemVer(),
+                                         publish: Boolean = false,
+                                         annotations: Parameters = Parameters())
+    extends WhiskActionLikeMetaData(name) {
+
+  require(exec != null, "exec undefined")
+  require(limits != null, "limits undefined")
+
+  def toWhiskAction =
+    WhiskActionMetaData(namespace, name, exec, parameters, limits, version, publish, annotations)
+      .revision[WhiskActionMetaData](rev)
+}
+
 object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[WhiskAction] with DefaultJsonProtocol {
 
   val execFieldName = "exec"
@@ -342,6 +410,85 @@ object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[
   }
 }
 
+object WhiskActionMetaData
+    extends DocumentFactory[WhiskActionMetaData]
+    with WhiskEntityQueries[WhiskActionMetaData]
+    with DefaultJsonProtocol {
+
+  val execFieldName = "exec"
+  val finalParamsAnnotationName = "final"
+
+  override val collectionName = "actions"
+
+  override implicit val serdes = jsonFormat(
+    WhiskActionMetaData.apply,
+    "namespace",
+    "name",
+    "exec",
+    "parameters",
+    "limits",
+    "version",
+    "publish",
+    "annotations")
+
+  override val cacheEnabled = true
+
+  /**
+   * Resolves an action name if it is contained in a package.
+   * Look up the package to determine if it is a binding or the actual package.
+   * If it's a binding, rewrite the fully qualified name of the action using the actual package path name.
+   * If it's the actual package, use its name directly as the package path name.
+   */
+  def resolveAction(db: EntityStore, fullyQualifiedActionName: FullyQualifiedEntityName)(
+    implicit ec: ExecutionContext,
+    transid: TransactionId): Future[FullyQualifiedEntityName] = {
+    // first check that there is a package to be resolved
+    val entityPath = fullyQualifiedActionName.path
+    if (entityPath.defaultPackage) {
+      // this is the default package, nothing to resolve
+      Future.successful(fullyQualifiedActionName)
+    } else {
+      // there is a package to be resolved
+      val pkgDocId = fullyQualifiedActionName.path.toDocId
+      val actionName = fullyQualifiedActionName.name
+      WhiskPackage.resolveBinding(db, pkgDocId) map {
+        _.fullyQualifiedName(withVersion = false).add(actionName)
+      }
+    }
+  }
+
+  /**
+   * Resolves an action name if it is contained in a package.
+   * Look up the package to determine if it is a binding or the actual package.
+   * If it's a binding, rewrite the fully qualified name of the action using the actual package path name.
+   * If it's the actual package, use its name directly as the package path name.
+   * While traversing the package bindings, merge the parameters.
+   */
+  def resolveActionAndMergeParameters(entityStore: EntityStore, fullyQualifiedName: FullyQualifiedEntityName)(
+    implicit ec: ExecutionContext,
+    transid: TransactionId): Future[WhiskActionMetaData] = {
+    // first check that there is a package to be resolved
+    val entityPath = fullyQualifiedName.path
+    if (entityPath.defaultPackage) {
+      // this is the default package, nothing to resolve
+      WhiskActionMetaData.get(entityStore, fullyQualifiedName.toDocId)
+    } else {
+      // there is a package to be resolved
+      val pkgDocid = fullyQualifiedName.path.toDocId
+      val actionName = fullyQualifiedName.name
+      val wp = WhiskPackage.resolveBinding(entityStore, pkgDocid, mergeParameters = true)
+      wp flatMap { resolvedPkg =>
+        // fully resolved name for the action
+        val fqnAction = resolvedPkg.fullyQualifiedName(withVersion = false).add(actionName)
+        // get the whisk action associate with it and inherit the parameters from the package/binding
+        WhiskActionMetaData.get(entityStore, fqnAction.toDocId) map {
+          _.inherit(resolvedPkg.parameters)
+        }
+      }
+    }
+  }
+}
+
 object ActionLimitsOption extends DefaultJsonProtocol {
   implicit val serdes = jsonFormat3(ActionLimitsOption.apply)
 }
diff --git a/common/scala/src/main/scala/whisk/core/entity/WhiskEntity.scala b/common/scala/src/main/scala/whisk/core/entity/WhiskEntity.scala
index 166b4c8..eafb7d5 100644
--- a/common/scala/src/main/scala/whisk/core/entity/WhiskEntity.scala
+++ b/common/scala/src/main/scala/whisk/core/entity/WhiskEntity.scala
@@ -106,6 +106,20 @@ object WhiskEntity {
   }
 }
 
+object WhiskDocumentReader extends DocumentReader {
+  override def read[A](ma: Manifest[A], value: JsValue) = {
+    ma.runtimeClass match {
+      case x if x == classOf[WhiskAction]         => WhiskAction.serdes.read(value)
+      case x if x == classOf[WhiskActionMetaData] => WhiskActionMetaData.serdes.read(value)
+      case x if x == classOf[WhiskPackage]        => WhiskPackage.serdes.read(value)
+      case x if x == classOf[WhiskActivation]     => WhiskActivation.serdes.read(value)
+      case x if x == classOf[WhiskTrigger]        => WhiskTrigger.serdes.read(value)
+      case x if x == classOf[WhiskRule]           => WhiskRule.serdes.read(value)
+      case _                                      => throw DocumentUnreadable(Messages.corruptedEntity)
+    }
+  }
+}
+
 /**
  * Dispatches to appropriate serdes. This object is not itself implicit so as to
  * avoid multiple implicit alternatives when working with one of the subtypes.
diff --git a/common/scala/src/main/scala/whisk/core/entity/WhiskStore.scala b/common/scala/src/main/scala/whisk/core/entity/WhiskStore.scala
index 28addc2..b114450 100644
--- a/common/scala/src/main/scala/whisk/core/entity/WhiskStore.scala
+++ b/common/scala/src/main/scala/whisk/core/entity/WhiskStore.scala
@@ -89,6 +89,8 @@ protected[core] trait WhiskDocument extends DocumentSerializer with DocumentRevi
 }
 
 object WhiskAuthStore {
+  implicit val docReader = WhiskDocumentReader
+
   def requiredProperties =
     Map(
       dbProvider -> null,
@@ -118,11 +120,16 @@ object WhiskEntityStore {
   def datastore(config: WhiskConfig)(implicit system: ActorSystem, logging: Logging, materializer: ActorMaterializer) =
     SpiLoader
       .get[ArtifactStoreProvider]
-      .makeStore[WhiskEntity](config, _.dbWhisk)(WhiskEntityJsonFormat, system, logging, materializer)
-
+      .makeStore[WhiskEntity](config, _.dbWhisk)(
+        WhiskEntityJsonFormat,
+        WhiskDocumentReader,
+        system,
+        logging,
+        materializer)
 }
 
 object WhiskActivationStore {
+  implicit val docReader = WhiskDocumentReader
   def requiredProperties =
     Map(
       dbProvider -> null,
diff --git a/common/scala/src/main/scala/whisk/http/ErrorResponse.scala b/common/scala/src/main/scala/whisk/http/ErrorResponse.scala
index 719a6c5..5130578 100644
--- a/common/scala/src/main/scala/whisk/http/ErrorResponse.scala
+++ b/common/scala/src/main/scala/whisk/http/ErrorResponse.scala
@@ -35,6 +35,7 @@ import whisk.common.TransactionId
 import whisk.core.entity.SizeError
 import whisk.core.entity.ByteSize
 import whisk.core.entity.Exec
+import whisk.core.entity.ExecMetaDataBase
 import whisk.core.entity.ActivationId
 
 object Messages {
@@ -55,6 +56,12 @@ object Messages {
   def runtimeDeprecated(e: Exec) =
     s"The '${e.kind}' runtime is no longer supported. You may read and delete but not update or invoke this action."
 
+  /**
+   * Standard message for reporting deprecated runtimes.
+   */
+  def runtimeDeprecated(e: ExecMetaDataBase) =
+    s"The '${e.kind}' runtime is no longer supported. You may read and delete but not update or invoke this action."
+
   /** Standard message for resource not found. */
   val resourceDoesNotExist = "The requested resource does not exist."
 
diff --git a/core/controller/src/main/scala/whisk/core/controller/Actions.scala b/core/controller/src/main/scala/whisk/core/controller/Actions.scala
index fe1ebe6..fcdea99 100644
--- a/core/controller/src/main/scala/whisk/core/controller/Actions.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/Actions.scala
@@ -222,11 +222,11 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
       'result ? false,
       'timeout.as[FiniteDuration] ? WhiskActionsApi.maxWaitForBlockingActivation) { (blocking, result, waitOverride) =>
       entity(as[Option[JsObject]]) { payload =>
-        getEntity(WhiskAction, entityStore, entityName.toDocId, Some {
-          act: WhiskAction =>
+        getEntity(WhiskActionMetaData, entityStore, entityName.toDocId, Some {
+          act: WhiskActionMetaData =>
             // resolve the action --- special case for sequences that may contain components with '_' as default package
             val action = act.resolve(user.namespace)
-            onComplete(entitleReferencedEntities(user, Privilege.ACTIVATE, Some(action.exec))) {
+            onComplete(entitleReferencedEntitiesMetaData(user, Privilege.ACTIVATE, Some(action.exec))) {
               case Success(_) =>
                 val actionWithMergedParams = env.map(action.inherit(_)) getOrElse action
                 val waitForResponse = if (blocking) Some(waitOverride) else None
@@ -386,6 +386,16 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
     }
   }
 
+  private def entitleReferencedEntitiesMetaData(user: Identity, right: Privilege, exec: Option[ExecMetaDataBase])(
+    implicit transid: TransactionId) = {
+    exec match {
+      case Some(seq: SequenceExecMetaData) =>
+        logging.info(this, "checking if sequence components are accessible")
+        entitlementProvider.check(user, right, referencedEntities(seq))
+      case _ => Future.successful(true)
+    }
+  }
+
   /** Creates a WhiskAction from PUT content, generating default values where necessary. */
   private def make(user: Identity, entityName: FullyQualifiedEntityName, content: WhiskActionPut)(
     implicit transid: TransactionId) = {
diff --git a/core/controller/src/main/scala/whisk/core/controller/Controller.scala b/core/controller/src/main/scala/whisk/core/controller/Controller.scala
index bb723bc..cc140b1 100644
--- a/core/controller/src/main/scala/whisk/core/controller/Controller.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/Controller.scala
@@ -110,7 +110,10 @@ class Controller(val instance: InstanceId,
   private implicit val activationStore = WhiskActivationStore.datastore(whiskConfig)
   private implicit val cacheChangeNotification = Some(new CacheChangeNotification {
     val remoteCacheInvalidaton = new RemoteCacheInvalidation(whiskConfig, "controller", instance)
-    override def apply(k: CacheKey) = remoteCacheInvalidaton.notifyOtherInstancesAboutInvalidation(k)
+    override def apply(k: CacheKey) = {
+      remoteCacheInvalidaton.invalidateWhiskActionMetaData(k)
+      remoteCacheInvalidaton.notifyOtherInstancesAboutInvalidation(k)
+    }
   })
 
   // initialize backend services
diff --git a/core/controller/src/main/scala/whisk/core/controller/WebActions.scala b/core/controller/src/main/scala/whisk/core/controller/WebActions.scala
index 0a12036..8cfb1e9 100644
--- a/core/controller/src/main/scala/whisk/core/controller/WebActions.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/WebActions.scala
@@ -449,8 +449,8 @@ trait WhiskWebActionsApi extends Directives with ValidateRequestSize with PostAc
    * This method is factored out to allow mock testing.
    */
   protected def getAction(actionName: FullyQualifiedEntityName)(
-    implicit transid: TransactionId): Future[WhiskAction] = {
-    WhiskAction.get(entityStore, actionName.toDocId)
+    implicit transid: TransactionId): Future[WhiskActionMetaData] = {
+    WhiskActionMetaData.get(entityStore, actionName.toDocId)
   }
 
   /**
@@ -549,7 +549,7 @@ trait WhiskWebActionsApi extends Directives with ValidateRequestSize with PostAc
   }
 
   private def extractEntityAndProcessRequest(actionOwnerIdentity: Identity,
-                                             action: WhiskAction,
+                                             action: WhiskActionMetaData,
                                              extension: MediaExtension,
                                              onBehalfOf: Option[Identity],
                                              context: Context,
@@ -597,7 +597,7 @@ trait WhiskWebActionsApi extends Directives with ValidateRequestSize with PostAc
   }
 
   private def processRequest(actionOwnerIdentity: Identity,
-                             action: WhiskAction,
+                             action: WhiskActionMetaData,
                              responseType: MediaExtension,
                              onBehalfOf: Option[Identity],
                              context: Context,
@@ -694,7 +694,7 @@ trait WhiskWebActionsApi extends Directives with ValidateRequestSize with PostAc
    * @return future action document or NotFound rejection
    */
   private def actionLookup(actionName: FullyQualifiedEntityName)(
-    implicit transid: TransactionId): Future[WhiskAction] = {
+    implicit transid: TransactionId): Future[WhiskActionMetaData] = {
     getAction(actionName) recoverWith {
       case _: ArtifactStoreException | DeserializationException(_, _, _) =>
         Future.failed(RejectRequest(NotFound))
@@ -717,8 +717,8 @@ trait WhiskWebActionsApi extends Directives with ValidateRequestSize with PostAc
   /**
    * Checks if an action is exported (i.e., carries the required annotation).
    */
-  private def confirmExportedAction(actionLookup: Future[WhiskAction], authenticated: Boolean)(
-    implicit transid: TransactionId): Future[WhiskAction] = {
+  private def confirmExportedAction(actionLookup: Future[WhiskActionMetaData], authenticated: Boolean)(
+    implicit transid: TransactionId): Future[WhiskActionMetaData] = {
     actionLookup flatMap { action =>
       val requiresAuthenticatedUser = action.annotations.asBool("require-whisk-auth").exists(identity)
       val isExported = action.annotations.asBool("web-export").exists(identity)
diff --git a/core/controller/src/main/scala/whisk/core/controller/actions/PostActionActivation.scala b/core/controller/src/main/scala/whisk/core/controller/actions/PostActionActivation.scala
index 34b3bb2..5548229 100644
--- a/core/controller/src/main/scala/whisk/core/controller/actions/PostActionActivation.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/actions/PostActionActivation.scala
@@ -46,14 +46,14 @@ protected[core] trait PostActionActivation extends PrimitiveActions with Sequenc
    */
   protected[controller] def invokeAction(
     user: Identity,
-    action: WhiskAction,
+    action: WhiskActionMetaData,
     payload: Option[JsObject],
     waitForResponse: Option[FiniteDuration],
     cause: Option[ActivationId])(implicit transid: TransactionId): Future[Either[ActivationId, WhiskActivation]] = {
     action.toExecutableWhiskAction match {
       // this is a topmost sequence
       case None =>
-        val SequenceExec(components) = action.exec
+        val SequenceExecMetaData(components) = action.exec
         invokeSequence(user, action, components, payload, waitForResponse, cause, topmost = true, 0).map(r => r._1)
       // a non-deprecated ExecutableWhiskAction
       case Some(executable) if !executable.exec.deprecated =>
diff --git a/core/controller/src/main/scala/whisk/core/controller/actions/PrimitiveActions.scala b/core/controller/src/main/scala/whisk/core/controller/actions/PrimitiveActions.scala
index 2a9fa1f..f604e63 100644
--- a/core/controller/src/main/scala/whisk/core/controller/actions/PrimitiveActions.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/actions/PrimitiveActions.scala
@@ -92,7 +92,7 @@ protected[actions] trait PrimitiveActions {
    */
   protected[actions] def invokeSingleAction(
     user: Identity,
-    action: ExecutableWhiskAction,
+    action: ExecutableWhiskActionMetaData,
     payload: Option[JsObject],
     waitForResponse: Option[FiniteDuration],
     cause: Option[ActivationId])(implicit transid: TransactionId): Future[Either[ActivationId, WhiskActivation]] = {
diff --git a/core/controller/src/main/scala/whisk/core/controller/actions/SequenceActions.scala b/core/controller/src/main/scala/whisk/core/controller/actions/SequenceActions.scala
index 92b2b80..8df2033 100644
--- a/core/controller/src/main/scala/whisk/core/controller/actions/SequenceActions.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/actions/SequenceActions.scala
@@ -63,7 +63,7 @@ protected[actions] trait SequenceActions {
   /** A method that knows how to invoke a single primitive action. */
   protected[actions] def invokeAction(
     user: Identity,
-    action: WhiskAction,
+    action: WhiskActionMetaData,
     payload: Option[JsObject],
     waitForResponse: Option[FiniteDuration],
     cause: Option[ActivationId])(implicit transid: TransactionId): Future[Either[ActivationId, WhiskActivation]]
@@ -84,7 +84,7 @@ protected[actions] trait SequenceActions {
    */
   protected[actions] def invokeSequence(
     user: Identity,
-    action: WhiskAction,
+    action: WhiskActionMetaData,
     components: Vector[FullyQualifiedEntityName],
     payload: Option[JsObject],
     waitForOutermostResponse: Option[FiniteDuration],
@@ -146,7 +146,7 @@ protected[actions] trait SequenceActions {
   private def completeSequenceActivation(seqActivationId: ActivationId,
                                          futureSeqResult: Future[SequenceAccounting],
                                          user: Identity,
-                                         action: WhiskAction,
+                                         action: WhiskActionMetaData,
                                          topmost: Boolean,
                                          start: Instant,
                                          cause: Option[ActivationId])(
@@ -188,7 +188,7 @@ protected[actions] trait SequenceActions {
    * Creates an activation for a sequence.
    */
   private def makeSequenceActivation(user: Identity,
-                                     action: WhiskAction,
+                                     action: WhiskActionMetaData,
                                      activationId: ActivationId,
                                      accounting: SequenceAccounting,
                                      topmost: Boolean,
@@ -248,7 +248,7 @@ protected[actions] trait SequenceActions {
    */
   private def invokeSequenceComponents(
     user: Identity,
-    seqAction: WhiskAction,
+    seqAction: WhiskActionMetaData,
     seqActivationId: ActivationId,
     inputPayload: Option[JsObject],
     components: Vector[FullyQualifiedEntityName],
@@ -264,7 +264,7 @@ protected[actions] trait SequenceActions {
     // This action/parameter resolution is done in futures; the execution starts as soon as the first component
     // is resolved.
     val resolvedFutureActions = resolveDefaultNamespace(components, user) map { c =>
-      WhiskAction.resolveActionAndMergeParameters(entityStore, c)
+      WhiskActionMetaData.resolveActionAndMergeParameters(entityStore, c)
     }
 
     // this holds the initial value of the accounting structure, including the input boxed as an ActivationResponse
@@ -314,7 +314,7 @@ protected[actions] trait SequenceActions {
    */
   private def invokeNextAction(
     user: Identity,
-    futureAction: Future[WhiskAction],
+    futureAction: Future[WhiskActionMetaData],
     accounting: SequenceAccounting,
     cause: Option[ActivationId])(implicit transid: TransactionId): Future[SequenceAccounting] = {
     futureAction.flatMap { action =>
@@ -327,7 +327,7 @@ protected[actions] trait SequenceActions {
       // invoke the action by calling the right method depending on whether it's an atomic action or a sequence
       val futureWhiskActivationTuple = action.toExecutableWhiskAction match {
         case None =>
-          val SequenceExec(components) = action.exec
+          val SequenceExecMetaData(components) = action.exec
           logging.info(this, s"sequence invoking an enclosed sequence $action")
           // call invokeSequence to invoke the inner sequence; this is a blocking activation by definition
           invokeSequence(
diff --git a/core/controller/src/main/scala/whisk/core/entitlement/Entitlement.scala b/core/controller/src/main/scala/whisk/core/entitlement/Entitlement.scala
index a2fc213..9afa83a 100644
--- a/core/controller/src/main/scala/whisk/core/entitlement/Entitlement.scala
+++ b/core/controller/src/main/scala/whisk/core/entitlement/Entitlement.scala
@@ -325,6 +325,10 @@ trait ReferencedEntities {
         e.components.map { c =>
           Resource(c.path, Collection(Collection.ACTIONS), Some(c.name.asString))
         }.toSet
+      case e: SequenceExecMetaData =>
+        e.components.map { c =>
+          Resource(c.path, Collection(Collection.ACTIONS), Some(c.name.asString))
+        }.toSet
       case _ => Set()
     }
   }
diff --git a/core/controller/src/main/scala/whisk/core/loadBalancer/LoadBalancerService.scala b/core/controller/src/main/scala/whisk/core/loadBalancer/LoadBalancerService.scala
index 0b5a06d..4237ed6 100644
--- a/core/controller/src/main/scala/whisk/core/loadBalancer/LoadBalancerService.scala
+++ b/core/controller/src/main/scala/whisk/core/loadBalancer/LoadBalancerService.scala
@@ -46,7 +46,7 @@ import whisk.core.connector.MessagingProvider
 import whisk.core.database.NoDocumentException
 import whisk.core.entity.{ActivationId, WhiskActivation}
 import whisk.core.entity.EntityName
-import whisk.core.entity.ExecutableWhiskAction
+import whisk.core.entity.ExecutableWhiskActionMetaData
 import whisk.core.entity.Identity
 import whisk.core.entity.InstanceId
 import whisk.core.entity.UUID
@@ -76,7 +76,7 @@ trait LoadBalancer {
    *         The future is guaranteed to complete within the declared action time limit
    *         plus a grace period (see activeAckTimeoutGrace).
    */
-  def publish(action: ExecutableWhiskAction, msg: ActivationMessage)(
+  def publish(action: ExecutableWhiskActionMetaData, msg: ActivationMessage)(
     implicit transid: TransactionId): Future[Future[Either[ActivationId, WhiskActivation]]]
 
 }
@@ -110,7 +110,7 @@ class LoadBalancerService(config: WhiskConfig, instance: InstanceId, entityStore
 
   override def totalActiveActivations = loadBalancerData.totalActivationCount
 
-  override def publish(action: ExecutableWhiskAction, msg: ActivationMessage)(
+  override def publish(action: ExecutableWhiskActionMetaData, msg: ActivationMessage)(
     implicit transid: TransactionId): Future[Future[Either[ActivationId, WhiskActivation]]] = {
     chooseInvoker(msg.user, action).flatMap { invokerName =>
       val entry = setupActivation(action, msg.activationId, msg.user.uuid, invokerName, transid)
@@ -173,7 +173,7 @@ class LoadBalancerService(config: WhiskConfig, instance: InstanceId, entityStore
   /**
    * Creates an activation entry and insert into various maps.
    */
-  private def setupActivation(action: ExecutableWhiskAction,
+  private def setupActivation(action: ExecutableWhiskActionMetaData,
                               activationId: ActivationId,
                               namespaceId: UUID,
                               invokerName: InstanceId,
@@ -312,7 +312,7 @@ class LoadBalancerService(config: WhiskConfig, instance: InstanceId, entityStore
   }
 
   /** Determine which invoker this activation should go to. Due to dynamic conditions, it may return no invoker. */
-  private def chooseInvoker(user: Identity, action: ExecutableWhiskAction): Future[InstanceId] = {
+  private def chooseInvoker(user: Identity, action: ExecutableWhiskActionMetaData): Future[InstanceId] = {
     val hash = generateHash(user.namespace, action)
 
     loadBalancerData.activationCountPerInvoker.flatMap { currentActivations =>
@@ -334,7 +334,7 @@ class LoadBalancerService(config: WhiskConfig, instance: InstanceId, entityStore
   }
 
   /** Generates a hash based on the string representation of namespace and action */
-  private def generateHash(namespace: EntityName, action: ExecutableWhiskAction): Int = {
+  private def generateHash(namespace: EntityName, action: ExecutableWhiskActionMetaData): Int = {
     (namespace.asString.hashCode() ^ action.fullyQualifiedName(false).asString.hashCode()).abs
   }
 }
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 6e65a80..aa60a25 100644
--- a/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala
@@ -812,6 +812,19 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi {
     }
   }
 
+  it should "ensure WhiskActionMetadata is used to invoke an action" in {
+    implicit val tid = transid()
+    val action = WhiskAction(namespace, aname(), jsDefault("??"))
+    put(entityStore, action)
+    Post(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check {
+      status should be(Accepted)
+      val response = responseAs[JsObject]
+      response.fields("activationId") should not be None
+    }
+    stream.toString should include(s"[WhiskActionMetaData] [GET] serving from datastore: ${CacheKey(action)}")
+    stream.reset()
+  }
+
   it should "report proper error when record is corrupted on delete" in {
     implicit val tid = transid()
     val entity = BadEntity(namespace, aname())
diff --git a/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala b/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala
index 9f99eda..5341836 100644
--- a/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala
@@ -491,7 +491,12 @@ class ActivationsApiTests extends ControllerTestCommon with WhiskActivationsApi
     implicit val materializer = ActorMaterializer()
     val activationStore = SpiLoader
       .get[ArtifactStoreProvider]
-      .makeStore[WhiskEntity](whiskConfig, _.dbActivations)(WhiskEntityJsonFormat, system, logging, materializer)
+      .makeStore[WhiskEntity](whiskConfig, _.dbActivations)(
+        WhiskEntityJsonFormat,
+        WhiskDocumentReader,
+        system,
+        logging,
+        materializer)
     implicit val tid = transid()
     val entity = BadEntity(namespace, EntityName(ActivationId().toString))
     put(activationStore, entity)
diff --git a/tests/src/test/scala/whisk/core/controller/test/ControllerTestCommon.scala b/tests/src/test/scala/whisk/core/controller/test/ControllerTestCommon.scala
index 1daa2a4..e7af616 100644
--- a/tests/src/test/scala/whisk/core/controller/test/ControllerTestCommon.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/ControllerTestCommon.scala
@@ -184,7 +184,7 @@ class DegenerateLoadBalancerService(config: WhiskConfig)(implicit ec: ExecutionC
   override def totalActiveActivations = Future.successful(0)
   override def activeActivationsFor(namespace: UUID) = Future.successful(0)
 
-  override def publish(action: ExecutableWhiskAction, msg: ActivationMessage)(
+  override def publish(action: ExecutableWhiskActionMetaData, msg: ActivationMessage)(
     implicit transid: TransactionId): Future[Future[Either[ActivationId, WhiskActivation]]] =
     Future.successful {
       whiskActivationStub map {
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 3a55d9b..9f5acad 100644
--- a/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala
@@ -185,12 +185,12 @@ trait WebActionsApiBaseTests extends ControllerTestCommon with BeforeAndAfterEac
   override protected def getAction(actionName: FullyQualifiedEntityName)(implicit transid: TransactionId) = {
     if (!failActionLookup) {
       def theAction = {
-        val annotations = Parameters(WhiskAction.finalParamsAnnotationName, JsBoolean(true))
+        val annotations = Parameters(WhiskActionMetaData.finalParamsAnnotationName, JsBoolean(true))
 
-        WhiskAction(
+        WhiskActionMetaData(
           actionName.path,
           actionName.name,
-          jsDefault("??"),
+          jsDefaultMetaData("??"),
           defaultActionParameters,
           annotations = {
             if (actionName.name.asString.startsWith("export_")) {
@@ -242,7 +242,7 @@ trait WebActionsApiBaseTests extends ControllerTestCommon with BeforeAndAfterEac
 
   override protected[controller] def invokeAction(
     user: Identity,
-    action: WhiskAction,
+    action: WhiskActionMetaData,
     payload: Option[JsObject],
     waitForResponse: Option[FiniteDuration],
     cause: Option[ActivationId])(implicit transid: TransactionId): Future[Either[ActivationId, WhiskActivation]] = {
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 a3cbde4..3d0be03 100644
--- a/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala
+++ b/tests/src/test/scala/whisk/core/entity/test/ExecHelpers.scala
@@ -56,6 +56,15 @@ trait ExecHelpers extends Matchers with WskActorSystem with StreamLogging {
     js6(code, main)
   }
 
+  protected def js6MetaData(code: String, main: Option[String] = None) = {
+    CodeExecMetaDataAsString(
+      RuntimeManifest(NODEJS6, imagename(NODEJS6), default = Some(true), deprecated = Some(false)))
+  }
+
+  protected def jsDefaultMetaData(code: String, main: Option[String] = None) = {
+    js6MetaData(code, main)
+  }
+
   protected def swift(code: String, main: Option[String] = None) = {
     CodeExecAsString(RuntimeManifest(SWIFT, imagename(SWIFT), deprecated = Some(true)), trim(code), main.map(_.trim))
   }

-- 
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].