houshengbo commented on a change in pull request #2589: Add the fundamental 
framework of REST invocation for test cases
URL: 
https://github.com/apache/incubator-openwhisk/pull/2589#discussion_r145436189
 
 

 ##########
 File path: tests/src/test/scala/common/rest/WskRest.scala
 ##########
 @@ -0,0 +1,1482 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package common.rest
+
+import java.io.File
+import java.time.Clock
+import java.time.Instant
+import java.util.Base64
+
+import org.apache.commons.io.FileUtils
+import org.scalatest.Matchers
+import org.scalatest.FlatSpec
+import org.scalatest.concurrent.ScalaFutures
+import org.scalatest.time.Span.convertDurationToSpan
+
+import scala.Left
+import scala.Right
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.collection.mutable.Buffer
+import scala.collection.immutable.Seq
+import scala.concurrent.duration.Duration
+import scala.concurrent.duration.DurationInt
+import scala.concurrent.Future
+import scala.language.postfixOps
+import scala.util.Failure
+import scala.util.Success
+import scala.util.Try
+import scala.util.{Failure, Success}
+
+import akka.http.scaladsl.model.StatusCode
+import akka.http.scaladsl.model.StatusCodes.Accepted
+import akka.http.scaladsl.model.StatusCodes.NotFound
+import akka.http.scaladsl.model.StatusCodes.BadRequest
+import akka.http.scaladsl.model.StatusCodes.OK
+import akka.http.scaladsl.model.HttpRequest
+import akka.http.scaladsl.model.HttpMethod
+import akka.http.scaladsl.model.HttpResponse
+import akka.http.scaladsl.model.headers.Authorization
+import akka.http.scaladsl.model.HttpEntity
+import akka.http.scaladsl.model.ContentTypes
+import akka.http.scaladsl.Http
+
+import akka.http.scaladsl.model.headers.BasicHttpCredentials
+import akka.http.scaladsl.model.Uri
+import akka.http.scaladsl.model.Uri.Path
+
+import akka.http.scaladsl.model.HttpMethods.DELETE
+import akka.http.scaladsl.model.HttpMethods.GET
+import akka.http.scaladsl.model.HttpMethods.POST
+import akka.http.scaladsl.model.HttpMethods.PUT
+
+import akka.stream.ActorMaterializer
+
+import spray.json._
+import spray.json.DefaultJsonProtocol._
+import spray.json.JsObject
+import spray.json.JsValue
+import spray.json.pimpString
+
+import common._
+import common.BaseDeleteFromCollection
+import common.BaseListOrGetFromCollection
+import common.HasActivation
+import common.RunWskCmd
+import common.TestUtils
+import common.TestUtils.SUCCESS_EXIT
+import common.TestUtils.DONTCARE_EXIT
+import common.TestUtils.ANY_ERROR_EXIT
+import common.TestUtils.DONTCARE_EXIT
+import common.TestUtils.RunResult
+import common.WaitFor
+import common.WhiskProperties
+import common.WskActorSystem
+import common.WskProps
+
+import whisk.core.entity.ByteSize
+import whisk.utils.retry
+
+class WskRest() extends RunWskRestCmd with BaseWsk {
+  override implicit val action = new WskRestAction
+  override implicit val trigger = new WskRestTrigger
+  override implicit val rule = new WskRestRule
+  override implicit val activation = new WskRestActivation
+  override implicit val pkg = new WskRestPackage
+  override implicit val namespace = new WskRestNamespace
+  override implicit val api = new WskRestApi
+}
+
+trait ListOrGetFromCollectionRest extends BaseListOrGetFromCollection {
+  self: RunWskRestCmd =>
+
+  /**
+   * List entities in collection.
+   *
+   * @param namespace (optional) if specified must be  fully qualified 
namespace
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def list(namespace: Option[String] = None,
+                    limit: Option[Int] = None,
+                    nameSort: Option[Boolean] = None,
+                    expectedExitCode: Int = OK.intValue)(implicit wp: 
WskProps): RestResult = {
+    val NS = namespace map { ns =>
+      ns
+    } getOrElse ""
+    val (ns, name) = getNamespaceActionName(NS)
+
+    val pathToList = s"$basePath/namespaces/$ns/$noun"
+    val entPath =
+      if (name != "") Path(s"$pathToList/$name/")
+      else Path(s"$pathToList")
+    val paramMap = Map[String, String]() ++ { Map("skip" -> "0".toString, 
"docs" -> true.toString) } ++ {
+      limit map { l =>
+        Map("limit" -> l.toString)
+      } getOrElse Map[String, String]("limit" -> "30".toString)
+    }
+    val resp = getWhiskEntity(entPath, paramMap).futureValue
+    val r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT)
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    r
+  }
+
+  /**
+   * Gets entity from collection.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def get(name: String,
+                   expectedExitCode: Int = OK.intValue,
+                   summary: Boolean = false,
+                   fieldFilter: Option[String] = None,
+                   url: Option[Boolean] = None)(implicit wp: WskProps): 
RestResult = {
+    val (ns, entity) = getNamespaceActionName(name)
+    val entPath = Path(s"$basePath/namespaces/$ns/$noun/$entity")
+    val resp = getWhiskEntity(entPath)(wp).futureValue
+    val r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT)
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    r
+  }
+}
+
+trait DeleteFromCollectionRest extends BaseDeleteFromCollection {
+  self: RunWskRestCmd =>
+
+  /**
+   * Deletes entity from collection.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def delete(entity: String, expectedExitCode: Int = 
OK.intValue)(implicit wp: WskProps): RestResult = {
+    val r = deleteEntity(entity)(wp)
+    if (expectedExitCode != DONTCARE_EXIT)
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    r
+  }
+
+  def deleteEntity(name: String)(implicit wp: WskProps): RestResult = {
+    val (ns, entity) = getNamespaceActionName(name)
+    val path = Path(s"$basePath/namespaces/$ns/$noun/$entity")
+    val resp = deleteEntity(path)(wp)
+    new RestResult(resp.status, getRespData(resp))
+  }
+
+  /**
+   * Deletes entity from collection but does not assert that the command 
succeeds.
+   * Use this if deleting an entity that may not exist and it is OK if it does 
not.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   */
+  override def sanitize(name: String)(implicit wp: WskProps): RestResult = {
+    delete(name, DONTCARE_EXIT)
+  }
+}
+
+trait HasActivationRest extends HasActivation {
+
+  /**
+   * Extracts activation id from invoke (action or trigger) or activation get
+   */
+  override def extractActivationId(result: RunResult): Option[String] = {
+    extractActivationIdFromInvoke(result.asInstanceOf[RestResult])
+  }
+
+  /**
+   * Extracts activation id from 'wsk action invoke' or 'wsk trigger invoke'
+   */
+  private def extractActivationIdFromInvoke(result: RestResult): 
Option[String] = {
+    Try {
+      var activationID =
+        if ((result.statusCode == OK) || (result.statusCode == Accepted)) 
result.getField("activationId") else ""
+      activationID
+    } toOption
+  }
+}
+
+class WskRestAction
+    extends RunWskRestCmd
+    with ListOrGetFromCollectionRest
+    with DeleteFromCollectionRest
+    with HasActivationRest
+    with BaseAction {
+
+  override protected val noun = "actions"
+  override implicit val config = PatienceConfig(100 seconds, 15 milliseconds)
+
+  /**
+   * Creates action. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def create(
+    name: String,
+    artifact: Option[String],
+    kind: Option[String] = None, // one of docker, copy, sequence or none for 
autoselect else an explicit type
+    main: Option[String] = None,
+    docker: Option[String] = None,
+    parameters: Map[String, JsValue] = Map(),
+    annotations: Map[String, JsValue] = Map(),
+    parameterFile: Option[String] = None,
+    annotationFile: Option[String] = None,
+    timeout: Option[Duration] = None,
+    memory: Option[ByteSize] = None,
+    logsize: Option[ByteSize] = None,
+    shared: Option[Boolean] = None,
+    update: Boolean = false,
+    web: Option[String] = None,
+    expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
+
+    val (namespace, actName) = getNamespaceActionName(name)
+    var exec = Map[String, JsValue]()
+    var code = ""
+    var kindType = ""
+
+    val (ext, artifactFile) = artifact map { a =>
+      (getExt(a), a)
+    } getOrElse (null, "")
+
+    ext match {
+      case ".jar" => {
+        kindType = "java:default"
+        val jar = FileUtils.readFileToByteArray(new File(artifactFile))
+        code = Base64.getEncoder.encodeToString(jar)
+      }
+      case ".zip" => {
+        val zip = FileUtils.readFileToByteArray(new File(artifactFile))
+        code = Base64.getEncoder.encodeToString(zip)
+      }
+      case ".js" => {
+        kindType = "nodejs:default"
+        code = FileUtils.readFileToString(new File(artifactFile))
+      }
+      case ".py" => {
+        kindType = "python:default"
+        code = FileUtils.readFileToString(new File(artifactFile))
+      }
+      case ".swift" => {
+        kindType = "swift:default"
+        code = FileUtils.readFileToString(new File(artifactFile))
+      }
+      case ".php" => {
+        kindType = "php:default"
+        code = FileUtils.readFileToString(new File(artifactFile))
+      }
+      case _ =>
+    }
+
+    kindType = docker map { d =>
+      "blackbox"
+    } getOrElse kindType
+
+    var (params, annos) = getParamsAnnos(parameters, annotations, 
parameterFile, annotationFile, web = web)
+
+    val inputKindType = kind map { k =>
+      k
+    } getOrElse null
+
+    inputKindType match {
+      case null => {
+        exec = Map("code" -> code.toJson, "kind" -> kindType.toJson)
+      }
+      case "copy" => {
+        val actName = entityName(artifactFile)
+        val actionPath = 
Path(s"$basePath/namespaces/$namespace/$noun/$actName")
+        val resp = getWhiskEntity(actionPath).futureValue
+        if (resp == None)
+          return new RestResult(NotFound, null)
+        else {
+          val result = new RestResult(resp.status, getRespData(resp))
+          params = JsArray(result.getFieldListJsObject("parameters"))
+          annos = JsArray(result.getFieldListJsObject("annotations"))
+          exec = result.getFieldJsObject("exec").fields
+        }
+      }
+      case "sequence" => {
+        kindType = "sequence"
+        val comps = convertIntoComponents(artifactFile)
+        exec = Map("components" -> comps.toJson, "kind" -> kindType.toJson)
+      }
+      case "native" => {
+        exec = Map("code" -> code.toJson, "kind" -> "blackbox".toJson, "image" 
-> "openwhisk/dockerskeleton".toJson)
+      }
+      case _ => {
+        kindType = inputKindType
+        exec = Map("code" -> code.toJson, "kind" -> kindType.toJson)
+      }
+    }
+
+    exec = exec ++ {
+      main map { m =>
+        Map("main" -> m.toJson)
+      } getOrElse Map[String, JsValue]()
+    } ++ {
+      docker map { d =>
+        Map("kind" -> "blackbox".toJson, "image" -> d.toJson)
+      } getOrElse Map[String, JsValue]()
+    }
+
+    var bodyContent = Map("name" -> name.toJson, "namespace" -> 
namespace.toJson)
+
+    if (update) {
+      if ((kind != None) && ((inputKindType == "sequence") || (inputKindType 
== "native"))) {
+        bodyContent = bodyContent ++ Map("exec" -> exec.toJson)
+      }
+
+      bodyContent = bodyContent ++ {
+        shared map { s =>
+          Map("publish" -> s.toJson)
+        } getOrElse Map[String, JsValue]()
+      }
+
+      val inputParams = convertMapIntoKeyValue(parameters)
+      if (inputParams.elements.size > 0) {
+        bodyContent = bodyContent ++ Map("parameters" -> params)
+      }
+      val inputAnnos = convertMapIntoKeyValue(annotations)
+      if (inputAnnos.elements.size > 0) {
+        bodyContent = bodyContent ++ Map("annotations" -> annos)
+      }
+    } else {
+      bodyContent = bodyContent ++ Map("exec" -> exec.toJson, "parameters" -> 
params, "annotations" -> annos)
+
+      bodyContent = bodyContent ++ {
+        timeout map { t =>
+          Map("limits" -> JsObject("timeout" -> t.toMillis.toJson))
+        } getOrElse Map[String, JsValue]()
+      }
+    }
+
+    val path = Path(s"$basePath/namespaces/$namespace/$noun/$actName")
+    val respFuture =
+      if (update) createEntity(path, JsObject(bodyContent).toString, 
Map("overwrite" -> "true"))
+      else createEntity(path, JsObject(bodyContent).toString)
+    val resp = respFuture.futureValue
+    val r = new RestResult(resp.status, getRespData(resp))
+    if ((expectedExitCode != DONTCARE_EXIT) && (expectedExitCode != 
ANY_ERROR_EXIT))
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    r
+  }
+
+  override def invoke(name: String,
+                      parameters: Map[String, JsValue] = Map(),
+                      parameterFile: Option[String] = None,
+                      blocking: Boolean = false,
+                      result: Boolean = false,
+                      expectedExitCode: Int = Accepted.intValue)(implicit wp: 
WskProps): RestResult = {
+    super.invokeAction(name, parameters, parameterFile, blocking, result, 
expectedExitCode = expectedExitCode)
+  }
+}
+
+class WskRestTrigger
+    extends RunWskRestCmd
+    with ListOrGetFromCollectionRest
+    with DeleteFromCollectionRest
+    with HasActivationRest
+    with BaseTrigger {
+
+  override protected val noun = "triggers"
+
+  /**
+   * Creates trigger. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def create(name: String,
+                      parameters: Map[String, JsValue] = Map(),
+                      annotations: Map[String, JsValue] = Map(),
+                      parameterFile: Option[String] = None,
+                      annotationFile: Option[String] = None,
+                      feed: Option[String] = None,
+                      shared: Option[Boolean] = None,
+                      update: Boolean = false,
+                      expectedExitCode: Int = OK.intValue)(implicit wp: 
WskProps): RestResult = {
+
+    val (ns, triggerName) = this.getNamespaceActionName(name)
+    val path = Path(s"$basePath/namespaces/$ns/$noun/$triggerName")
+    val (params, annos) = getParamsAnnos(parameters, annotations, 
parameterFile, annotationFile, feed)
+    var bodyContent = JsObject("name" -> name.toJson, "namespace" -> 
s"$ns".toJson)
+
+    if (!update) {
+      val published = shared map { s =>
+        s
+      } getOrElse false
+      bodyContent = JsObject(
+        bodyContent.fields + ("publish" -> published.toJson,
+        "parameters" -> params, "annotations" -> annos))
+    } else {
+      bodyContent = shared map { s =>
+        JsObject(bodyContent.fields + ("publish" -> s.toJson))
+      } getOrElse bodyContent
+
+      val inputParams = convertMapIntoKeyValue(parameters)
+      if (inputParams.elements.size > 0) {
+        bodyContent = JsObject(bodyContent.fields + ("parameters" -> params))
+      }
+      val inputAnnos = convertMapIntoKeyValue(annotations)
+      if (inputAnnos.elements.size > 0) {
+        bodyContent = JsObject(bodyContent.fields + ("annotations" -> annos))
+      }
+    }
+
+    val respFuture =
+      if (update) createEntity(path, bodyContent.toString, Map("overwrite" -> 
"true"))
+      else createEntity(path, bodyContent.toString)
+    val resp = respFuture.futureValue
+    val result = new RestResult(resp.status, getRespData(resp))
+
+    val feedInput = feed map { f =>
+      f
+    } getOrElse null
+
+    if ((feed == null) || (result.statusCode != OK)) {
+      if (expectedExitCode != result.statusCode.intValue)
+        expectedExitCode shouldBe result.exitCode
+      result
+    } else {
+      // Invoke the feed
+      val (nsFeed, feedName) = this.getNamespaceActionName(feedInput)
+      val path = Path(s"$basePath/namespaces/$nsFeed/actions/$feedName")
+      val paramMap = Map("blocking" -> "true".toString, "result" -> 
"false".toString)
+      var params: Map[String, JsValue] = Map(
+        "lifecycleEvent" -> "CREATE".toJson,
+        "triggerName" -> s"/$ns/$triggerName".toJson,
+        "authKey" -> s"${getAuthKey(wp)}".toJson)
+      params = params ++ parameters
+      val resp = postEntityParam(path, params.toJson.toString(), 
paramMap).futureValue
+      val resultInvoke = new RestResult(resp.status, getRespData(resp))
+      expectedExitCode shouldBe resultInvoke.statusCode.intValue
+      if (resultInvoke.statusCode != OK) {
+        // Remove the trigger, because the feed failed to invoke.
+        this.delete(triggerName)
+      } else {
+        result
+      }
+    }
+  }
+
+  /**
+   * Fires trigger. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def fire(name: String,
+                    parameters: Map[String, JsValue] = Map(),
+                    parameterFile: Option[String] = None,
+                    expectedExitCode: Int = OK.intValue)(implicit wp: 
WskProps): RestResult = {
+    val path = Path(s"$basePath/namespaces/${wp.namespace}/$noun/$name")
+    val params = parameterFile map { l =>
+      val input = FileUtils.readFileToString(new File(l))
+      input.parseJson.convertTo[Map[String, JsValue]]
+    } getOrElse parameters
+    val resp =
+      if (params.size == 0) postEntity(path).futureValue else postEntity(path, 
params.toJson.toString()).futureValue
+    new RestResult(resp.status.intValue, getRespData(resp))
+  }
+}
+
+class WskRestRule
+    extends RunWskRestCmd
+    with ListOrGetFromCollectionRest
+    with DeleteFromCollectionRest
+    with WaitFor
+    with BaseRule {
+
+  override protected val noun = "rules"
+
+  /**
+   * Creates rule. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param trigger must be a simple name
+   * @param action must be a simple name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def create(name: String,
+                      trigger: String,
+                      action: String,
+                      annotations: Map[String, JsValue] = Map(),
+                      shared: Option[Boolean] = None,
+                      update: Boolean = false,
+                      expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: 
WskProps): RestResult = {
+    val path = Path(s"$basePath/namespaces/${wp.namespace}/$noun/$name")
+    val annos = convertMapIntoKeyValue(annotations)
+    val published = shared map { s =>
+      s
+    } getOrElse false
+
+    val bodyContent = JsObject(
+      "trigger" -> fullEntityName(trigger).toJson,
+      "action" -> fullEntityName(action).toJson,
+      "publish" -> published.toJson,
+      "name" -> name.toJson,
+      "status" -> "".toJson,
+      "annotations" -> annos)
+
+    val respFuture =
+      if (update) createEntity(path, bodyContent.toString, Map("overwrite" -> 
"true"))
+      else createEntity(path, bodyContent.toString)
+    val resp = respFuture.futureValue
+    new RestResult(resp.status, getRespData(resp))
+  }
+
+  /**
+   * Enables rule.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def enable(name: String, expectedExitCode: Int = 
SUCCESS_EXIT)(implicit wp: WskProps): RestResult = {
+    changeRuleState(name, "active")
+  }
+
+  /**
+   * Disables rule.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def disable(name: String, expectedExitCode: Int = 
SUCCESS_EXIT)(implicit wp: WskProps): RestResult = {
+    changeRuleState(name, "inactive")
+  }
+
+  /**
+   * Checks state of rule.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def state(name: String, expectedExitCode: Int = 
OK.intValue)(implicit wp: WskProps): RestResult = {
+    get(name, expectedExitCode = expectedExitCode)
+  }
+
+  def changeRuleState(ruleName: String, state: String = "active")(implicit wp: 
WskProps): RestResult = {
+    val enName = entityName(ruleName)
+    val path = Path(s"$basePath/namespaces/${wp.namespace}/$noun/$enName")
+    val bodyContent = JsObject("status" -> state.toJson)
+    val resp = postEntity(path, bodyContent.toString).futureValue
+    new RestResult(resp.status, getRespData(resp))
+  }
+}
+
+class WskRestActivation extends RunWskRestCmd with HasActivationRest with 
WaitFor with BaseActivation {
+
+  protected val noun = "activations"
+
+  /**
+   * Activation polling console.
+   *
+   * @param duration exits console after duration
+   * @param since (optional) time travels back to activation since given 
duration
+   */
+  override def console(duration: Duration, since: Option[Duration] = None, 
expectedExitCode: Int = SUCCESS_EXIT)(
+    implicit wp: WskProps): RestResult = {
+    var sinceTime = System.currentTimeMillis()
+    val utc = Instant.now(Clock.systemUTC()).toEpochMilli
+    sinceTime = since map { s =>
+      sinceTime - s.toMillis
+    } getOrElse sinceTime
+    val pollTimeout = duration.toSeconds
+    waitForActivationConsole(duration, Instant.ofEpochMilli(sinceTime))
+  }
+
+  /**
+   * Lists activations.
+   *
+   * @param filter (optional) if define, must be a simple entity name
+   * @param limit (optional) the maximum number of activation to return
+   * @param since (optional) only the activations since this timestamp are 
included
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  def listActivation(filter: Option[String] = None,
+                     limit: Option[Int] = None,
+                     since: Option[Instant] = None,
+                     docs: Boolean = true,
+                     expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: 
WskProps): RestResult = {
+    val entityPath = Path(s"${basePath}/namespaces/${wp.namespace}/$noun")
+    var paramMap = Map("skip" -> "0".toString, "docs" -> docs.toString) ++ {
+      limit map { l =>
+        Map("limit" -> l.toString)
+      } getOrElse Map("limit" -> "30".toString)
+    } ++ {
+      filter map { f =>
+        Map("limit" -> f.toString)
+      } getOrElse Map[String, String]()
+    } ++ {
+      since map { s =>
+        Map("limit" -> s.toEpochMilli().toString)
+      } getOrElse Map[String, String]()
+    }
+    val resp = getWhiskEntity(entityPath, paramMap).futureValue
+    new RestResult(resp.status, getRespData(resp))
+  }
+
+  /**
+   * Parses result of WskActivation.list to extract sequence of activation ids.
+   *
+   * @param rr run result, should be from WhiskActivation.list otherwise 
behavior is undefined
+   * @return sequence of activations
+   */
+  def idsActivation(rr: RestResult): Seq[String] = {
+    val list = rr.getBodyListJsObject()
+    var result = Seq[String]()
+    list.foreach((obj: JsObject) => result = result :+ 
(RestResult.getField(obj, "activationId").toString))
+    result
+  }
+
+  /**
+   * Gets activation logs by id.
+   *
+   * @param activationId the activation id
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  def activationLogs(activationId: String, expectedExitCode: Int = 
OK.intValue)(implicit wp: WskProps): RestResult = {
+    val path = 
Path(s"${basePath}/namespaces/${wp.namespace}/$noun/$activationId/logs")
+    val resp = getWhiskEntity(path).futureValue
+    var r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT) {
+      r.statusCode.intValue shouldBe expectedExitCode
+    }
+    r
+  }
+
+  /**
+   * Gets activation result by id.
+   *
+   * @param activationId the activation id
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  def activationResult(activationId: String, expectedExitCode: Int = 
OK.intValue)(implicit wp: WskProps): RestResult = {
+    val path = 
Path(s"${basePath}/namespaces/${wp.namespace}/$noun/$activationId/result")
+    val resp = getWhiskEntity(path).futureValue
+    var r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT) {
+      r.statusCode.intValue shouldBe expectedExitCode
+    }
+    r
+  }
+
+  /**
+   * Polls activations list for at least N activations. The activations
+   * are optionally filtered for the given entity. Will return as soon as
+   * N activations are found. If after retry budget is exhausted, N activations
+   * are still not present, will return a partial result. Hence caller must
+   * check length of the result and not assume it is >= N.
+   *
+   * @param N the number of activations desired
+   * @param entity the name of the entity to filter from activation list
+   * @param limit the maximum number of entities to list (if entity name is 
not unique use Some(0))
+   * @param since (optional) only the activations since this timestamp are 
included
+   * @param retries the maximum retries (total timeout is retries + 1 seconds)
+   * @return activation ids found, caller must check length of sequence
+   */
+  override def pollFor(N: Int,
+                       entity: Option[String],
+                       limit: Option[Int] = Some(30),
+                       since: Option[Instant] = None,
+                       retries: Int = 10,
+                       pollPeriod: Duration = 1.second)(implicit wp: 
WskProps): Seq[String] = {
+    Try {
+      retry({
+        val result = idsActivation(listActivation(filter = entity, limit = 
limit, since = since, docs = false))
+        if (result.length >= N) result else throw PartialResult(result)
+      }, retries, waitBeforeRetry = Some(pollPeriod))
+    } match {
+      case Success(ids)                => ids
+      case Failure(PartialResult(ids)) => ids
+      case _                           => Seq()
+    }
+  }
+
+  override def get(activationId: Option[String],
+                   expectedExitCode: Int = OK.intValue,
+                   fieldFilter: Option[String] = None,
+                   last: Option[Boolean] = None)(implicit wp: WskProps): 
RestResult = {
+    val path = activationId map { id =>
+      Path(s"${basePath}/namespaces/${wp.namespace}/$noun/$id")
+    } getOrElse Path("")
+    val resp = getWhiskEntity(path).futureValue
+    var r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT) {
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    }
+    r
+  }
+
+  /**
+   * Polls for an activation matching the given id. If found
+   * return Right(activation) else Left(result of calling REST API).
+   *
+   * @return either Left(error message) or Right(activation as JsObject)
+   */
+  override def waitForActivation(activationId: String,
+                                 initialWait: Duration = 1 second,
+                                 pollPeriod: Duration = 1 second,
+                                 totalWait: Duration = 30 seconds)(implicit 
wp: WskProps): Either[String, JsObject] = {
+    val activation = waitfor(() => {
+      val result = get(Some(activationId), expectedExitCode = 
DONTCARE_EXIT)(wp)
+      if (result.statusCode == NotFound) {
+        null
+      } else result
+    }, initialWait, pollPeriod, totalWait)
+    Try {
+      assert(activation.statusCode == OK)
+      assert(activation.getField("activationId") != "")
+      activation.respBody
+    } map {
+      Right(_)
+    } getOrElse Left(s"Cannot find activation id from '$activation'")
+
+  }
+
+  def waitForActivationConsole(totalWait: Duration = 30 seconds, sinceTime: 
Instant)(
+    implicit wp: WskProps): RestResult = {
+    var result = new RestResult(NotFound, null)
+    Thread.sleep(totalWait.toMillis)
+    listActivation(since = Some(sinceTime))(wp)
+  }
+
+  override def logs(activationId: Option[String] = None,
+                    expectedExitCode: Int = OK.intValue,
+                    last: Option[Boolean] = None)(implicit wp: WskProps): 
RestResult = {
+    val path = activationId map { id =>
+      Path(s"${basePath}/namespaces/${wp.namespace}/$noun/$id/logs")
+    } getOrElse Path("")
+    val resp = getWhiskEntity(path).futureValue
+    var r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT) {
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    }
+    r
+  }
+
+  override def result(activationId: Option[String] = None,
+                      expectedExitCode: Int = OK.intValue,
+                      last: Option[Boolean] = None)(implicit wp: WskProps): 
RestResult = {
+    val path = activationId map { id =>
+      Path(s"${basePath}/namespaces/${wp.namespace}/$noun/$id/result")
+    } getOrElse Path("")
+    val resp = getWhiskEntity(path).futureValue
+    var r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT) {
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    }
+    r
+  }
+
+  /** Used in polling for activations to record partial results from retry 
poll. */
+  private case class PartialResult(ids: Seq[String]) extends Throwable
+}
+
+class WskRestNamespace extends RunWskRestCmd with BaseNamespace {
+
+  protected val noun = "namespaces"
+
+  /**
+   * Lists available namespaces for whisk properties.
+   *
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def list(expectedExitCode: Int = OK.intValue, nameSort: 
Option[Boolean] = None)(
+    implicit wp: WskProps): RestResult = {
+    val entPath = Path(s"$basePath/namespaces")
+    val resp = getWhiskEntity(entPath).futureValue
+    val result = if (resp == None) new RestResult(NotFound, null) else new 
RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT)
+      if (result.statusCode.intValue != expectedExitCode)
+        result.exitCode shouldBe expectedExitCode
+    result
+  }
+
+  /**
+   * Gets entities in namespace.
+   *
+   * @param namespace (optional) if specified must be  fully qualified 
namespace
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def get(namespace: Option[String] = None,
+                   expectedExitCode: Int = OK.intValue,
+                   nameSort: Option[Boolean] = None)(implicit wp: WskProps): 
RestResult = {
+    val (ns, _) = namespace map { this.getNamespaceActionName(_) } getOrElse 
(wp.namespace, "")
+    val entPath = Path(s"$basePath/namespaces/$ns/")
+    val resp = getWhiskEntity(entPath).futureValue
+    val r = new RestResult(resp.status, getRespData(resp))
+    if (expectedExitCode != DONTCARE_EXIT) {
+      if (r.statusCode.intValue != expectedExitCode)
+        r.statusCode.intValue shouldBe expectedExitCode
+    }
+    r
+  }
+
+  /**
+   * Looks up namespace for whisk props.
+   *
+   * @param wskprops instance of WskProps with an auth key to lookup
+   * @return namespace as string
+   */
+  override def whois()(implicit wskprops: WskProps): String = {
+    val ns = list().getBodyListString
+    if (ns.size > 0) ns(0).toString() else ""
+  }
+}
+
+class WskRestPackage
+    extends RunWskRestCmd
+    with ListOrGetFromCollectionRest
+    with DeleteFromCollectionRest
+    with BasePackage {
+
+  override protected val noun = "packages"
+
+  /**
+   * Creates package. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def create(name: String,
+                      parameters: Map[String, JsValue] = Map(),
+                      annotations: Map[String, JsValue] = Map(),
+                      parameterFile: Option[String] = None,
+                      annotationFile: Option[String] = None,
+                      shared: Option[Boolean] = None,
+                      update: Boolean = false,
+                      expectedExitCode: Int = OK.intValue)(implicit wp: 
WskProps): RestResult = {
+    val path = Path(s"$basePath/namespaces/${wp.namespace}/$noun/$name")
+    var bodyContent = JsObject("namespace" -> s"${wp.namespace}".toJson, 
"name" -> name.toJson)
+
+    val (params, annos) = this.getParamsAnnos(parameters, annotations, 
parameterFile, annotationFile)
 
 Review comment:
   We pick up either parameters or parameterFile as the parameters to create 
the action, and pick up either annotations or annotationFile as the annotations 
to create the action. 
   When it comes to update the action, we only care about the parameters and 
annotations, not parameterFile or annotationFile any more. That is why we see 
the differences.

----------------------------------------------------------------
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

Reply via email to