Compute msec duration for whisk activations from actions.

Remove unnecessary var `boundParams` in container and inline `mergeParams` methods.

On failure where action limits are not known, omit limits:null from activation annotation.

Fix action invocation to that getting a container is wrapped in a future and can thus recover properly and not drop annotations from the action. Added test to confirm precense of limits when invoke fails.

Container pool's get action method returned None only when system was shutting down and throwing an exception otherwise. Change the type of the method accordingly to remove the Option and simplify the typing. Make invokeAction complete the transaction and ensure there is at most one completion on the path (ie if the complete transaction operation itself fails, it is not subject to the recovery clause.

Remove unnecessary promise.
diff --git a/tests/src/common/WskTestHelpers.scala b/tests/src/common/WskTestHelpers.scala
index 5289db8..55c0658 100644
--- a/tests/src/common/WskTestHelpers.scala
+++ b/tests/src/common/WskTestHelpers.scala
@@ -106,6 +106,7 @@
      * structure of "result" is not defined.
      */
     case class CliActivationResponse(result: Option[JsObject], status: String, success: Boolean)
+
     object CliActivationResponse extends DefaultJsonProtocol {
         implicit val serdes = jsonFormat3(CliActivationResponse.apply)
     }
@@ -113,9 +114,18 @@
     /**
      * Activation record as it is returned by the CLI.
      */
-    case class CliActivation(activationId: String, logs: Option[List[String]], response: CliActivationResponse, start: Long, end: Long, cause: Option[String], annotations: Option[List[JsObject]])
+    case class CliActivation(
+        activationId: String,
+        logs: Option[List[String]],
+        response: CliActivationResponse,
+        start: Long,
+        end: Long,
+        duration: Long,
+        cause: Option[String],
+        annotations: Option[List[JsObject]])
+
     object CliActivation extends DefaultJsonProtocol {
-        implicit val serdes = jsonFormat7(CliActivation.apply)
+        implicit val serdes = jsonFormat8(CliActivation.apply)
     }
 
     /**
diff --git a/tests/src/system/basic/WskBasicTests.scala b/tests/src/system/basic/WskBasicTests.scala
index 4c22987..bda657a 100644
--- a/tests/src/system/basic/WskBasicTests.scala
+++ b/tests/src/system/basic/WskBasicTests.scala
@@ -388,7 +388,16 @@
             withActivation(wsk.activation, run) {
                 activation =>
                     activation.response.result shouldBe Some(dynamicParams.toJson)
-                    activation.end shouldBe Instant.EPOCH.toEpochMilli
+                    activation.duration shouldBe 0L // shouldn't exist but CLI generates it
+                    activation.end shouldBe Instant.EPOCH.toEpochMilli // shouldn't exist but CLI generates it
+            }
+
+            val runWithNoParams = wsk.trigger.fire(name, Map())
+            withActivation(wsk.activation, runWithNoParams) {
+                activation =>
+                    activation.response.result shouldBe Some(JsObject())
+                    activation.duration shouldBe 0L // shouldn't exist but CLI generates it
+                    activation.end shouldBe Instant.EPOCH.toEpochMilli // shouldn't exist but CLI generates it
             }
 
             wsk.trigger.list().stdout should include(name)
diff --git a/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala b/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala
index c923d30..04bd017 100644
--- a/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala
+++ b/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala
@@ -457,6 +457,12 @@
                 activation =>
                     activation.response.status shouldBe ActivationResponse.messageForCode(ActivationResponse.ApplicationError)
                     activation.response.result.get.fields("error") shouldBe s"Failed to pull container image '$containerName'.".toJson
+                    activation.annotations shouldBe defined
+                    val limits = activation.annotations.get.filter(_.fields("key").convertTo[String] == "limits")
+                    withClue(limits) {
+                        limits.length should be > 0
+                        limits(0).fields("value") should not be JsNull
+                    }
             }
     }
 
@@ -697,8 +703,7 @@
             TestUtils.getTestActionFilename("invalidInput1.json"),
             TestUtils.getTestActionFilename("invalidInput2.json"),
             TestUtils.getTestActionFilename("invalidInput3.json"),
-            TestUtils.getTestActionFilename("invalidInput4.json")
-        )
+            TestUtils.getTestActionFilename("invalidInput4.json"))
         val paramCmds = Seq(
             Seq("action", "create", "actionName", TestUtils.getTestActionFilename("hello.js")),
             Seq("action", "update", "actionName", TestUtils.getTestActionFilename("hello.js")),
@@ -708,8 +713,7 @@
             Seq("package", "bind", "packageName", "boundPackageName"),
             Seq("trigger", "create", "triggerName"),
             Seq("trigger", "update", "triggerName"),
-            Seq("trigger", "fire", "triggerName")
-        )
+            Seq("trigger", "fire", "triggerName"))
         val annotCmds = Seq(
             Seq("action", "create", "actionName", TestUtils.getTestActionFilename("hello.js")),
             Seq("action", "update", "actionName", TestUtils.getTestActionFilename("hello.js")),
@@ -717,18 +721,17 @@
             Seq("package", "update", "packageName"),
             Seq("package", "bind", "packageName", "boundPackageName"),
             Seq("trigger", "create", "triggerName"),
-            Seq("trigger", "update", "triggerName")
-        )
+            Seq("trigger", "update", "triggerName"))
 
         for (cmd <- paramCmds) {
             for (invalid <- invalidJSONInputs) {
                 wsk.cli(cmd ++ Seq("-p", "key", invalid) ++ wskprops.overrides, expectedExitCode = ERROR_EXIT)
-                  .stderr should include("Invalid parameter argument")
+                    .stderr should include("Invalid parameter argument")
             }
 
             for (invalid <- invalidJSONFiles) {
                 wsk.cli(cmd ++ Seq("-P", invalid) ++ wskprops.overrides, expectedExitCode = ERROR_EXIT)
-                  .stderr should include("Invalid parameter argument")
+                    .stderr should include("Invalid parameter argument")
 
             }
         }
@@ -736,12 +739,12 @@
         for (cmd <- annotCmds) {
             for (invalid <- invalidJSONInputs) {
                 wsk.cli(cmd ++ Seq("-a", "key", invalid) ++ wskprops.overrides, expectedExitCode = ERROR_EXIT)
-                  .stderr should include("Invalid annotation argument")
+                    .stderr should include("Invalid annotation argument")
             }
 
             for (invalid <- invalidJSONFiles) {
                 wsk.cli(cmd ++ Seq("-A", invalid) ++ wskprops.overrides, expectedExitCode = ERROR_EXIT)
-                  .stderr should include("Invalid annotation argument")
+                    .stderr should include("Invalid annotation argument")
             }
         }
     }
@@ -753,9 +756,9 @@
         val missingFileMsg = s"File '$missingFile' is not a valid file or it does not exist"
         val invalidArgs = Seq(
             (Seq("action", "create", "actionName", TestUtils.getTestActionFilename("hello.js"), "-P", emptyFile),
-              emptyFileMsg),
+                emptyFileMsg),
             (Seq("action", "update", "actionName", TestUtils.getTestActionFilename("hello.js"), "-P", emptyFile),
-              emptyFileMsg),
+                emptyFileMsg),
             (Seq("action", "invoke", "actionName", "-P", emptyFile), emptyFileMsg),
             (Seq("action", "create", "actionName", "-P", emptyFile), emptyFileMsg),
             (Seq("action", "update", "actionName", "-P", emptyFile), emptyFileMsg),
@@ -773,9 +776,9 @@
             (Seq("trigger", "update", "triggerName", "-P", emptyFile), emptyFileMsg),
             (Seq("trigger", "fire", "triggerName", "-P", emptyFile), emptyFileMsg),
             (Seq("action", "create", "actionName", TestUtils.getTestActionFilename("hello.js"), "-A", missingFile),
-              missingFileMsg),
+                missingFileMsg),
             (Seq("action", "update", "actionName", TestUtils.getTestActionFilename("hello.js"), "-A", missingFile),
-              missingFileMsg),
+                missingFileMsg),
             (Seq("action", "invoke", "actionName", "-A", missingFile), missingFileMsg),
             (Seq("action", "create", "actionName", "-A", missingFile), missingFileMsg),
             (Seq("action", "update", "actionName", "-A", missingFile), missingFileMsg),
@@ -791,14 +794,13 @@
             (Seq("trigger", "fire", "triggerName", "-A", missingFile), missingFileMsg),
             (Seq("trigger", "create", "triggerName", "-A", missingFile), missingFileMsg),
             (Seq("trigger", "update", "triggerName", "-A", missingFile), missingFileMsg),
-            (Seq("trigger", "fire", "triggerName", "-A", missingFile), missingFileMsg)
-        )
+            (Seq("trigger", "fire", "triggerName", "-A", missingFile), missingFileMsg))
 
         invalidArgs foreach {
             case (cmd, err) =>
-            val stderr = wsk.cli(cmd, expectedExitCode = MISUSE_EXIT).stderr
-            stderr should include(err)
-            stderr should include("Run 'wsk --help' for usage.")
+                val stderr = wsk.cli(cmd, expectedExitCode = MISUSE_EXIT).stderr
+                stderr should include(err)
+                stderr should include("Run 'wsk --help' for usage.")
         }
     }
 
@@ -861,8 +863,7 @@
             (Seq("trigger", "update", "triggerName", "-A"), invalidAnnotFileMsg),
             (Seq("trigger", "fire", "triggerName", "-a"), invalidAnnotMsg),
             (Seq("trigger", "fire", "triggerName", "-a", "key"), invalidAnnotMsg),
-            (Seq("trigger", "fire", "triggerName", "-A"), invalidAnnotFileMsg)
-        )
+            (Seq("trigger", "fire", "triggerName", "-A"), invalidAnnotFileMsg))
 
         invalidArgs foreach {
             case (cmd, err) =>
diff --git a/tests/src/whisk/core/container/test/ContainerPoolTests.scala b/tests/src/whisk/core/container/test/ContainerPoolTests.scala
index fe1d369..29ed535 100644
--- a/tests/src/whisk/core/container/test/ContainerPoolTests.scala
+++ b/tests/src/whisk/core/container/test/ContainerPoolTests.scala
@@ -46,6 +46,7 @@
 
 import common.WskActorSystem
 
+
 /**
  * Unit tests for ContainerPool and, by association, Container and WhiskContainer.
  *
@@ -203,8 +204,7 @@
             val name = "foobar" + i
             val action = makeHelloAction(name, i)
             pool.getAction(action, defaultAuth) match {
-                case None => assert(false)
-                case Some((con, initResult)) => {
+                case (con, initResult) => {
                     val str = "QWERTY" + i.toString()
                     con.run(str, (20000 + i).toString()) // payload + activationId
                     if (i == max - 1) {
@@ -233,7 +233,7 @@
         ensureClean()
         val action = makeHelloAction("foobar", 0)
         // Make a whisk container and test init and a push
-        val Some((con, initRes)) = pool.getAction(action, defaultAuth)
+        val (con, initRes) = pool.getAction(action, defaultAuth)
         Thread.sleep(1000)
         assert(con.getLogs().contains("ABCXYZ"))
         con.run("QWERTY", "55555") // payload + activationId
@@ -241,7 +241,7 @@
         assert(con.getLogs().contains("QWERTY"))
         pool.putBack(con)
         // Test container reuse
-        val Some((con2, _)) = pool.getAction(action, defaultAuth)
+        val (con2, _) = pool.getAction(action, defaultAuth)
         assert(con == con2) // check re-use
         con.run("ASDFGH", "4444") // payload + activationId
         Thread.sleep(1000)