Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -558,13 +558,34 @@ class ContainerProxy(factory: (TransactionId,
val actionTimeout = job.action.limits.timeout.duration
val (env, parameters) = ContainerProxy.partitionArguments(job.msg.content, job.msg.initArgs)

val environment = Map(
"namespace" -> job.msg.user.namespace.name.toJson,
"action_name" -> job.msg.action.qualifiedNameWithLeadingSlash.toJson,
"activation_id" -> job.msg.activationId.toString.toJson,
"transaction_id" -> job.msg.transid.id.toJson)

// if the action requests the api key to be injected into the action context, add it here;
// treat a missing annotation as requesting the api key for backward compatibility
val authEnvironment = {
if (job.action.annotations.isTruthy(Annotations.ProvideApiKeyAnnotationName, valueForNonExistent = true)) {
job.msg.user.authkey.toEnvironment.fields
} else Map.empty
}

// Only initialize iff we haven't yet warmed the container
val initialize = stateData match {
case data: WarmedData =>
Future.successful(None)
case _ =>
val owEnv = (authEnvironment ++ environment + ("deadline" -> (Instant.now.toEpochMilli + actionTimeout.toMillis).toString.toJson)) map {
case (key, value) => "__OW_" + key.toUpperCase -> value
}

container
.initialize(job.action.containerInitializer(env), actionTimeout, job.action.limits.concurrency.maxConcurrent)
.initialize(
job.action.containerInitializer(env ++ owEnv),
actionTimeout,
job.action.limits.concurrency.maxConcurrent)
.map(Some(_))
}

Expand All @@ -575,29 +596,14 @@ class ContainerProxy(factory: (TransactionId,
self ! InitCompleted(WarmedData(container, job.msg.user.namespace.name, job.action, Instant.now, 1))
}

// if the action requests the api key to be injected into the action context, add it here;
// treat a missing annotation as requesting the api key for backward compatibility
val authEnvironment = {
if (job.action.annotations.isTruthy(Annotations.ProvideApiKeyAnnotationName, valueForNonExistent = true)) {
job.msg.user.authkey.toEnvironment
} else JsObject.empty
}

val environment = JsObject(
"namespace" -> job.msg.user.namespace.name.toJson,
"action_name" -> job.msg.action.qualifiedNameWithLeadingSlash.toJson,
"activation_id" -> job.msg.activationId.toString.toJson,
"transaction_id" -> job.msg.transid.id.toJson,
val env = authEnvironment ++ environment ++ Map(
// compute deadline on invoker side avoids discrepancies inside container
// but potentially under-estimates actual deadline
"deadline" -> (Instant.now.toEpochMilli + actionTimeout.toMillis).toString.toJson)

container
.run(
parameters,
JsObject(authEnvironment.fields ++ environment.fields),
actionTimeout,
job.action.limits.concurrency.maxConcurrent)(job.msg.transid)
.run(parameters, env.toJson.asJsObject, actionTimeout, job.action.limits.concurrency.maxConcurrent)(
job.msg.transid)
.map {
case (runInterval, response) =>
val initRunInterval = initInterval
Expand Down
8 changes: 7 additions & 1 deletion docs/actions-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,13 @@ The initialization route is `/init`. It must accept a `POST` request with a JSON
* `main` is the name of the function to execute.
* `code` is either plain text or a base64 encoded string for binary functions (i.e., a compiled executable).
* `binary` is false if `code` is in plain text, and true if `code` is base64 encoded.
* `env` is an map of key-value pairs of properties to export to the environment.
* `env` is a map of key-value pairs of properties to export to the environment. And contains several properties starting with the `__OW_` prefix that are specific to the running action.
* `__OW_API_KEY` the API key for the subject invoking the action, this key may be a restricted API key. This property is absent unless explicitly [requested](./annotations.md#annotations-for-all-actions).
* `__OW_NAMESPACE` the namespace for the _activation_ (this may not be the same as the namespace for the action).
* `__OW_ACTION_NAME` the fully qualified name of the running action.
* `__OW_ACTIVATION_ID` the activation id for this running action instance.
* `__OW_DEADLINE` the approximate time when this initializer will have consumed its entire duration quota (measured in epoch milliseconds).


The initialization route is called exactly once by the OpenWhisk platform, before executing a function.
The route should report an error if called more than once. It is possible however that a single initialization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1416,11 +1416,36 @@ class ContainerProxyTests
override def initialize(initializer: JsObject, timeout: FiniteDuration, concurrent: Int)(
implicit transid: TransactionId): Future[Interval] = {
initializeCount += 1
initializer shouldBe action.containerInitializer {

val envField = "env"

(initializer.fields - envField) shouldBe (action.containerInitializer {
activationArguments.fields.filter(k => filterEnvVar(k._1))
}
}.fields - envField)
timeout shouldBe action.limits.timeout.duration

val initializeEnv = initializer.fields(envField).asJsObject

initializeEnv.fields("__OW_NAMESPACE") shouldBe invocationNamespace.name.toJson
initializeEnv.fields("__OW_ACTION_NAME") shouldBe message.action.qualifiedNameWithLeadingSlash.toJson
initializeEnv.fields("__OW_ACTIVATION_ID") shouldBe message.activationId.toJson
initializeEnv.fields("__OW_TRANSACTION_ID") shouldBe transid.id.toJson

val convertedAuthKey = message.user.authkey.toEnvironment.fields.map(f => ("__OW_" + f._1.toUpperCase(), f._2))
val authEnvironment = initializeEnv.fields.filterKeys(convertedAuthKey.contains)
if (apiKeyMustBePresent) {
convertedAuthKey shouldBe authEnvironment
} else {
authEnvironment shouldBe empty
}

val deadline = Instant.ofEpochMilli(initializeEnv.fields("__OW_DEADLINE").convertTo[String].toLong)
val maxDeadline = Instant.now.plusMillis(timeout.toMillis)

// The deadline should be in the future but must be smaller than or equal
// a freshly computed deadline, as they get computed slightly after each other
deadline should (be <= maxDeadline and be >= Instant.now)

initPromise.map(_.future).getOrElse(Future.successful(initInterval))
}
override def run(parameters: JsObject, environment: JsObject, timeout: FiniteDuration, concurrent: Int)(
Expand Down