Serialize `updated` value of entity document in response (#4646)
* Add updated field on WhiskAction json format
* Update custom serdes
* Add updated field at case class
* Fix package api test
* Modified test case name
* Add annotation
* Add singleton method to get current timestamp
* Modify test case
* Update annotation
* Add updated field on package entity
* Add updated field on trigger entity
* Serialize updated field in rules entity
* Fix scheme tests
* Update apiv1swagger.json
* Refactor test code
* Fix test case
* Fix typo
* Refactor test code
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
index 887adc3..1b9fa4e 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
@@ -19,6 +19,7 @@
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.nio.charset.StandardCharsets.UTF_8
+import java.time.Instant
import java.util.Base64
import akka.http.scaladsl.model.ContentTypes
@@ -122,7 +123,8 @@
* @param limits the limits to impose on the action
* @param version the semantic version
* @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the action
+ * @param annotations the set of annotations to attribute to the action
+ * @param updated the timestamp when the action is updated
* @throws IllegalArgumentException if any argument is undefined
*/
@throws[IllegalArgumentException]
@@ -133,7 +135,8 @@
limits: ActionLimits = ActionLimits(),
version: SemVer = SemVer(),
publish: Boolean = false,
- annotations: Parameters = Parameters())
+ annotations: Parameters = Parameters(),
+ override val updated: Instant = WhiskEntity.currentMillis())
extends WhiskActionLike(name) {
require(exec != null, "exec undefined")
@@ -200,6 +203,7 @@
version: SemVer = SemVer(),
publish: Boolean = false,
annotations: Parameters = Parameters(),
+ override val updated: Instant = WhiskEntity.currentMillis(),
binding: Option[EntityPath] = None)
extends WhiskActionLikeMetaData(name) {
@@ -264,7 +268,7 @@
* @param limits the limits to impose on the action
* @param version the semantic version
* @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the action
+ * @param annotations the set of annotations to attribute to the action
* @param binding the path of the package binding if any
* @throws IllegalArgumentException if any argument is undefined
*/
@@ -330,7 +334,7 @@
require(limits != null, "limits undefined")
def toWhiskAction =
- WhiskActionMetaData(namespace, name, exec, parameters, limits, version, publish, annotations)
+ WhiskActionMetaData(namespace, name, exec, parameters, limits, version, publish, annotations, updated)
.revision[WhiskActionMetaData](rev)
/**
@@ -342,11 +346,13 @@
}
object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[WhiskAction] with DefaultJsonProtocol {
+ import WhiskActivation.instantSerdes
val execFieldName = "exec"
val requireWhiskAuthHeader = "x-require-whisk-auth"
override val collectionName = "actions"
+ override val cacheEnabled = true
override implicit val serdes = jsonFormat(
WhiskAction.apply,
@@ -357,9 +363,8 @@
"limits",
"version",
"publish",
- "annotations")
-
- override val cacheEnabled = true
+ "annotations",
+ "updated")
// overriden to store attached code
override def put[A >: WhiskAction](db: ArtifactStore[A], doc: WhiskAction, old: Option[WhiskAction])(
@@ -547,7 +552,10 @@
with WhiskEntityQueries[WhiskActionMetaData]
with DefaultJsonProtocol {
+ import WhiskActivation.instantSerdes
+
override val collectionName = "actions"
+ override val cacheEnabled = true
override implicit val serdes = jsonFormat(
WhiskActionMetaData.apply,
@@ -559,10 +567,9 @@
"version",
"publish",
"annotations",
+ "updated",
"binding")
- 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.
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala
index baa6462..0fd81d3 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala
@@ -19,10 +19,9 @@
import java.time.Clock
import java.time.Instant
+import java.time.temporal.ChronoUnit
-import scala.Stream
import scala.util.Try
-
import spray.json._
import org.apache.openwhisk.core.database.DocumentUnreadable
import org.apache.openwhisk.core.database.DocumentTypeMismatchException
@@ -37,7 +36,7 @@
* @param namespace the namespace for the entity as an abstract field
* @param version the semantic version as an abstract field
* @param publish true to share the entity and false to keep it private as an abstract field
- * @param annotation the set of annotations to attribute to the entity
+ * @param annotations the set of annotations to attribute to the entity
*
* @throws IllegalArgumentException if any argument is undefined
*/
@@ -49,7 +48,7 @@
val version: SemVer
val publish: Boolean
val annotations: Parameters
- val updated = Instant.now(Clock.systemUTC())
+ val updated = WhiskEntity.currentMillis()
/**
* The name of the entity qualified with its namespace and version for
@@ -112,6 +111,15 @@
def qualifiedName(namespace: EntityPath, activationId: ActivationId) = {
s"$namespace${EntityPath.PATHSEP}$activationId"
}
+
+ /**
+ * Get Instant object with a millisecond precision
+ * timestamp of whisk entity is stored in milliseconds in the db
+ */
+ def currentMillis() = {
+ Instant.now(Clock.systemUTC()).truncatedTo(ChronoUnit.MILLIS)
+ }
+
}
object WhiskDocumentReader extends DocumentReader {
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
index c3c5050..c606867 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
@@ -17,11 +17,12 @@
package org.apache.openwhisk.core.entity
+import java.time.Instant
+
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.language.postfixOps
import scala.util.Try
-
import spray.json.DefaultJsonProtocol
import spray.json.DefaultJsonProtocol._
import spray.json._
@@ -60,7 +61,8 @@
* @param parameters the set of parameters to bind to the action environment
* @param version the semantic version
* @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the package
+ * @param annotations the set of annotations to attribute to the package
+ * @param updated the timestamp when the package is updated
* @throws IllegalArgumentException if any argument is undefined
*/
@throws[IllegalArgumentException]
@@ -70,7 +72,8 @@
parameters: Parameters = Parameters(),
version: SemVer = SemVer(),
publish: Boolean = false,
- annotations: Parameters = Parameters())
+ annotations: Parameters = Parameters(),
+ override val updated: Instant = WhiskEntity.currentMillis())
extends WhiskEntity(name, "package") {
require(binding != null || (binding map { _ != null } getOrElse true), "binding undefined")
@@ -159,6 +162,8 @@
with WhiskEntityQueries[WhiskPackage]
with DefaultJsonProtocol {
+ import WhiskActivation.instantSerdes
+
val bindingFieldName = "binding"
override val collectionName = "packages"
@@ -197,7 +202,7 @@
override def write(b: Option[Binding]) = Binding.optionalBindingSerializer.write(b)
override def read(js: JsValue) = Binding.optionalBindingDeserializer.read(js)
}
- jsonFormat7(WhiskPackage.apply)
+ jsonFormat8(WhiskPackage.apply)
}
override val cacheEnabled = true
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala
index 6a7e929..259a612 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala
@@ -17,10 +17,11 @@
package org.apache.openwhisk.core.entity
+import java.time.Instant
+
import scala.util.Failure
import scala.util.Success
import scala.util.Try
-
import spray.json.DefaultJsonProtocol
import spray.json.DeserializationException
import spray.json.JsObject
@@ -65,7 +66,8 @@
* @param action the action name to invoke invoke when trigger is fired
* @param version the semantic version
* @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the rule
+ * @param annotations the set of annotations to attribute to the rule
+ * @param updated the timestamp when the rule is updated
* @throws IllegalArgumentException if any argument is undefined
*/
@throws[IllegalArgumentException]
@@ -75,10 +77,12 @@
action: FullyQualifiedEntityName,
version: SemVer = SemVer(),
publish: Boolean = false,
- annotations: Parameters = Parameters())
+ annotations: Parameters = Parameters(),
+ override val updated: Instant = WhiskEntity.currentMillis())
extends WhiskEntity(name, "rule") {
- def withStatus(s: Status) = WhiskRuleResponse(namespace, name, s, trigger, action, version, publish, annotations)
+ def withStatus(s: Status) =
+ WhiskRuleResponse(namespace, name, s, trigger, action, version, publish, annotations, updated)
def toJson = WhiskRule.serdes.write(this).asJsObject
}
@@ -95,7 +99,7 @@
* @param action the action name to invoke invoke when trigger is fired
* @param version the semantic version
* @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the rule
+ * @param annotations the set of annotations to attribute to the rule
*/
case class WhiskRuleResponse(namespace: EntityPath,
name: EntityName,
@@ -104,7 +108,8 @@
action: FullyQualifiedEntityName,
version: SemVer = SemVer(),
publish: Boolean = false,
- annotations: Parameters = Parameters()) {
+ annotations: Parameters = Parameters(),
+ updated: Instant) {
def toWhiskRule = WhiskRule(namespace, name, trigger, action, version, publish, annotations)
}
@@ -195,11 +200,12 @@
}
object WhiskRule extends DocumentFactory[WhiskRule] with WhiskEntityQueries[WhiskRule] with DefaultJsonProtocol {
+ import WhiskActivation.instantSerdes
override val collectionName = "rules"
private implicit val fqnSerdes = FullyQualifiedEntityName.serdes
- private val caseClassSerdes = jsonFormat7(WhiskRule.apply)
+ private val caseClassSerdes = jsonFormat8(WhiskRule.apply)
override implicit val serdes = new RootJsonFormat[WhiskRule] {
def write(r: WhiskRule) = caseClassSerdes.write(r)
@@ -233,8 +239,9 @@
}
object WhiskRuleResponse extends DefaultJsonProtocol {
+ import WhiskActivation.instantSerdes
private implicit val fqnSerdes = FullyQualifiedEntityName.serdes
- implicit val serdes = jsonFormat8(WhiskRuleResponse.apply)
+ implicit val serdes = jsonFormat9(WhiskRuleResponse.apply)
}
object WhiskRulePut extends DefaultJsonProtocol {
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala
index d6668a9..0470980 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala
@@ -17,6 +17,8 @@
package org.apache.openwhisk.core.entity
+import java.time.Instant
+
import spray.json.DefaultJsonProtocol
import org.apache.openwhisk.core.database.DocumentFactory
import spray.json._
@@ -54,8 +56,9 @@
* @param limits the limits to impose on the trigger
* @param version the semantic version
* @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the trigger
+ * @param annotations the set of annotations to attribute to the trigger
* @param rules the map of the rules that are associated with this trigger. Key is the rulename and value is the ReducedRule
+ * @param updated the timestamp when the trigger is updated
* @throws IllegalArgumentException if any argument is undefined
*/
@throws[IllegalArgumentException]
@@ -66,7 +69,8 @@
version: SemVer = SemVer(),
publish: Boolean = false,
annotations: Parameters = Parameters(),
- rules: Option[Map[FullyQualifiedEntityName, ReducedRule]] = None)
+ rules: Option[Map[FullyQualifiedEntityName, ReducedRule]] = None,
+ override val updated: Instant = WhiskEntity.currentMillis())
extends WhiskEntity(name, "trigger") {
require(limits != null, "limits undefined")
@@ -108,11 +112,12 @@
extends DocumentFactory[WhiskTrigger]
with WhiskEntityQueries[WhiskTrigger]
with DefaultJsonProtocol {
+ import WhiskActivation.instantSerdes
override val collectionName = "triggers"
private implicit val fqnSerdesAsDocId = FullyQualifiedEntityName.serdesAsDocId
- override implicit val serdes = jsonFormat8(WhiskTrigger.apply)
+ override implicit val serdes = jsonFormat9(WhiskTrigger.apply)
override val cacheEnabled = true
}
diff --git a/core/controller/src/main/resources/apiv1swagger.json b/core/controller/src/main/resources/apiv1swagger.json
index 4a3aca7..b9c1060 100644
--- a/core/controller/src/main/resources/apiv1swagger.json
+++ b/core/controller/src/main/resources/apiv1swagger.json
@@ -1998,6 +1998,10 @@
"deactivating"
]
},
+ "updated": {
+ "type": "integer",
+ "description": "Time when the rule was updated"
+ },
"trigger": {
"$ref": "#/definitions/PathName"
},
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
index 102ca36..b46a3a4 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
@@ -194,6 +194,7 @@
implicit val tid = transid()
val action = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b"))
put(entityStore, action)
+
Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
@@ -201,20 +202,42 @@
}
}
- it should "get action by name in explicit namespace" in {
+ it should "get action with updated field" in {
implicit val tid = transid()
+
val action = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b"))
put(entityStore, action)
- Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(creds)) ~> check {
+
+ // `updated` field should be compared with a document in DB
+ val a = get(entityStore, action.docid, WhiskAction)
+
+ Get(s"/$namespace/${collection.path}/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
- val response = responseAs[WhiskAction]
- response should be(action)
+ val responseJson = responseAs[JsObject]
+ responseJson.fields("updated").convertTo[Long] should be(a.updated.toEpochMilli)
}
- // it should "reject get action by name in explicit namespace not owned by subject" in
- val auser = WhiskAuthHelpers.newIdentity()
- Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(auser)) ~> check {
- status should be(Forbidden)
+ Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val responseJson = responseAs[JsObject]
+ responseJson.fields("updated").convertTo[Long] should be(a.updated.toEpochMilli)
+ }
+ }
+
+ it should "ignore updated field when updating action" in {
+ implicit val tid = transid()
+
+ val action = WhiskAction(namespace, aname(), jsDefault(""))
+ val dummyUpdated = WhiskEntity.currentMillis().toEpochMilli
+
+ val content = JsObject(
+ "exec" -> JsObject("code" -> "".toJson, "kind" -> action.exec.kind.toJson),
+ "updated" -> dummyUpdated.toJson)
+
+ Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val response = responseAs[WhiskAction]
+ response.updated.toEpochMilli should be > dummyUpdated
}
}
@@ -324,7 +347,7 @@
Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(expectedWhiskAction)
+ checkWhiskEntityResponse(response, expectedWhiskAction)
}
Get(s"$collectionPath/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check {
@@ -332,21 +355,21 @@
val responseJson = responseAs[JsObject]
responseJson.fields("exec").asJsObject.fields should not(contain key "code")
val response = responseAs[WhiskActionMetaData]
- response should be(expectedWhiskActionMetaData)
+ checkWhiskEntityResponse(response, expectedWhiskActionMetaData)
}
Seq(s"$collectionPath/${action.name}", s"$collectionPath/${action.name}?code=true").foreach { path =>
Get(path) ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(expectedWhiskAction)
+ checkWhiskEntityResponse(response, expectedWhiskAction)
}
}
Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(expectedWhiskAction)
+ checkWhiskEntityResponse(response, expectedWhiskAction)
}
}
}
@@ -545,7 +568,8 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -566,7 +590,8 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -589,7 +614,8 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -617,7 +643,8 @@
Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -635,7 +662,8 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -706,7 +734,8 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -746,7 +775,8 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -760,6 +790,7 @@
}
it should "put and then get an action from cache" in {
+ implicit val tid = transid()
val javaAction =
WhiskAction(namespace, aname(), javaDefault("ZHViZWU=", Some("hello")), annotations = Parameters("exec", "java"))
val nodeAction = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b"))
@@ -781,7 +812,8 @@
Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -800,7 +832,8 @@
Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -818,7 +851,8 @@
Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be {
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -827,8 +861,7 @@
action.limits,
action.version.upPatch,
action.publish,
- action.annotations ++ systemAnnotations(kind))
- }
+ action.annotations ++ systemAnnotations(kind)))
}
stream.toString should include(s"entity exists, will try to update '$action'")
stream.toString should include(s"invalidating ${CacheKey(action)}")
@@ -839,7 +872,8 @@
Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -893,7 +927,8 @@
Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -913,7 +948,8 @@
Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -932,7 +968,8 @@
Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -976,7 +1013,8 @@
Put(s"$collectionPath/$name", content) ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -996,7 +1034,8 @@
Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -1016,7 +1055,8 @@
Delete(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -1065,7 +1105,8 @@
Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -1124,7 +1165,7 @@
Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(expectedAction)
+ checkWhiskEntityResponse(response, expectedAction)
}
}
@@ -1172,7 +1213,8 @@
Put(s"$collectionPath/$name?overwrite=true", content) ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -1190,7 +1232,8 @@
Delete(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -1237,7 +1280,8 @@
Put(s"$collectionPath/${actionOldSchema.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check {
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
actionOldSchema.namespace,
actionOldSchema.name,
@@ -1261,7 +1305,8 @@
Delete(s"$collectionPath/${actionOldSchema.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskAction]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
actionOldSchema.namespace,
actionOldSchema.name,
@@ -1301,7 +1346,10 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be {
+
+ response.updated should not be action.updated
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
@@ -1313,8 +1361,7 @@
content.limits.get.logs.get,
content.limits.get.concurrency.get),
version = action.version.upPatch,
- annotations = action.annotations ++ systemAnnotations(NODEJS10, create = false))
- }
+ annotations = action.annotations ++ systemAnnotations(NODEJS10, create = false)))
}
}
@@ -1327,15 +1374,15 @@
deleteAction(action.docid)
status should be(OK)
val response = responseAs[WhiskAction]
- response should be {
+ checkWhiskEntityResponse(
+ response,
WhiskAction(
action.namespace,
action.name,
action.exec,
content.parameters.get,
version = action.version.upPatch,
- annotations = action.annotations ++ systemAnnotations(NODEJS10, false))
- }
+ annotations = action.annotations ++ systemAnnotations(NODEJS10, false)))
}
}
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala
index fca9faf..9817bef 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala
@@ -85,6 +85,18 @@
}
}
+ def checkWhiskEntityResponse(response: WhiskEntity, expected: WhiskEntity): Unit = {
+ // Used to ignore `updated` field because timestamp is not known before inserting into the DB
+ // If you use this method, test case that checks timestamp must be added
+ val r = response match {
+ case whiskAction: WhiskAction => whiskAction.copy(updated = expected.updated)
+ case whiskActionMetaData: WhiskActionMetaData => whiskActionMetaData.copy(updated = expected.updated)
+ case whiskTrigger: WhiskTrigger => whiskTrigger.copy(updated = expected.updated)
+ case whiskPackage: WhiskPackage => whiskPackage.copy(updated = expected.updated)
+ }
+ r should be(expected)
+ }
+
def systemAnnotations(kind: String, create: Boolean = true): Parameters = {
val base = if (create && FeatureFlags.requireApiKeyAnnotation) {
Parameters(Annotations.ProvideApiKeyAnnotationName, JsFalse)
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala
index d1033b0..a645e87 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala
@@ -162,7 +162,8 @@
action.limits,
action.version,
action.publish,
- action.annotations ++ systemAnnotations(NODEJS10)))
+ action.annotations ++ systemAnnotations(NODEJS10),
+ updated = response.updated))
}
}
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala
index 5d1ecca..5ea915d 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala
@@ -297,6 +297,21 @@
}
}
+ it should "get package with updated field" in {
+ implicit val tid = transid()
+ val provider = WhiskPackage(namespace, aname(), None)
+ put(entityStore, provider)
+
+ // `updated` field should be compared with a document in DB
+ val pkg = get(entityStore, provider.docid, WhiskPackage)
+
+ Get(s"$collectionPath/${provider.name}") ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val response = responseAs[WhiskPackageWithActions]
+ response should be(provider copy (updated = pkg.updated) withActions ())
+ }
+ }
+
it should "get package reference for private package in same namespace" in {
implicit val tid = transid()
val provider = WhiskPackage(namespace, aname(), None, Parameters("a", "A") ++ Parameters("b", "B"))
@@ -422,7 +437,7 @@
deletePackage(provider.docid)
status should be(OK)
val response = responseAs[WhiskPackage]
- response should be(provider)
+ checkWhiskEntityResponse(response, provider)
}
}
@@ -492,7 +507,7 @@
deletePackage(reference.docid)
status should be(OK)
val response = responseAs[WhiskPackage]
- response should be(reference)
+ checkWhiskEntityResponse(response, reference)
}
}
@@ -522,13 +537,13 @@
deletePackage(reference.docid)
status should be(OK)
val response = responseAs[WhiskPackage]
- response should be {
+ checkWhiskEntityResponse(
+ response,
WhiskPackage(
reference.namespace,
reference.name,
provider.bind,
- annotations = bindingAnnotation(provider.bind.get))
- }
+ annotations = bindingAnnotation(provider.bind.get)))
}
}
@@ -632,7 +647,8 @@
Put(s"$collectionPath/${provider.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check {
deletePackage(provider.docid)
val response = responseAs[WhiskPackage]
- response should be(
+ checkWhiskEntityResponse(
+ response,
WhiskPackage(namespace, provider.name, None, version = provider.version.upPatch, publish = true))
}
}
@@ -657,15 +673,15 @@
deletePackage(reference.docid)
status should be(OK)
val response = responseAs[WhiskPackage]
- response should be {
+ checkWhiskEntityResponse(
+ response,
WhiskPackage(
reference.namespace,
reference.name,
reference.binding,
version = reference.version.upPatch,
publish = true,
- annotations = reference.annotations ++ Parameters("a", "b"))
- }
+ annotations = reference.annotations ++ Parameters("a", "b")))
}
}
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala
index d497593..9aee5da 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala
@@ -17,6 +17,8 @@
package org.apache.openwhisk.core.controller.test
+import java.time.Instant
+
import scala.language.postfixOps
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@@ -27,7 +29,7 @@
import spray.json._
import org.apache.openwhisk.core.controller.WhiskRulesApi
import org.apache.openwhisk.core.entitlement.Collection
-import org.apache.openwhisk.core.entity._
+import org.apache.openwhisk.core.entity.{WhiskRuleResponse, _}
import org.apache.openwhisk.core.entity.test.OldWhiskTrigger
import org.apache.openwhisk.http.ErrorResponse
@@ -61,6 +63,11 @@
val activeStatus = s"""{"status":"${Status.ACTIVE}"}""".parseJson.asJsObject
val inactiveStatus = s"""{"status":"${Status.INACTIVE}"}""".parseJson.asJsObject
val parametersLimit = Parameters.sizeLimit
+ val dummyInstant = Instant.now()
+
+ def checkResponse(response: WhiskRuleResponse, expected: WhiskRuleResponse) =
+ // ignore `updated` field because another test covers it
+ response should be(expected copy (updated = response.updated))
//// GET /rules
it should "list rules by default/explicit namespace" in {
@@ -164,14 +171,14 @@
Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.withStatus(Status.INACTIVE))
}
// it should "get trigger by name in explicit namespace owned by subject" in
Get(s"/$namespace/${collection.path}/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.withStatus(Status.INACTIVE))
}
// it should "reject get trigger by name in explicit namespace not owned by subject" in
@@ -207,7 +214,32 @@
Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.ACTIVE))
+ checkResponse(response, rule.withStatus(Status.ACTIVE))
+ }
+ }
+
+ it should "get rule with updated field" in {
+ implicit val tid = transid()
+
+ val rule = WhiskRule(
+ namespace,
+ EntityName("get_active_rule"),
+ afullname(namespace, "get_active_rule trigger"),
+ afullname(namespace, "an action"))
+ val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name, rules = Some {
+ Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.ACTIVE))
+ })
+
+ put(entityStore, trigger)
+ put(entityStore, rule)
+
+ // `updated` field should be compared with a document in DB
+ val r = get(entityStore, rule.docid, WhiskRule)
+
+ Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val response = responseAs[WhiskRuleResponse]
+ response should be(rule.withStatus(Status.ACTIVE) copy (updated = r.updated))
}
}
@@ -227,7 +259,7 @@
Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.withStatus(Status.INACTIVE))
}
}
@@ -264,7 +296,7 @@
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.withStatus(Status.INACTIVE))
}
}
@@ -292,7 +324,7 @@
status should be(OK)
t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe None
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.withStatus(Status.INACTIVE))
}
}
@@ -310,7 +342,7 @@
Delete(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.withStatus(Status.INACTIVE))
}
}
@@ -329,7 +361,7 @@
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.withStatus(Status.INACTIVE))
}
}
@@ -356,7 +388,7 @@
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.ACTIVE))
+ checkResponse(response, rule.withStatus(Status.ACTIVE))
t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
}
}
@@ -385,7 +417,7 @@
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.ACTIVE))
+ checkResponse(response, rule.withStatus(Status.ACTIVE))
t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
}
}
@@ -436,7 +468,7 @@
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.ACTIVE))
+ checkResponse(response, rule.withStatus(Status.ACTIVE))
t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
}
}
@@ -468,7 +500,7 @@
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.ACTIVE))
+ checkResponse(response, rule.withStatus(Status.ACTIVE))
t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
}
}
@@ -592,14 +624,16 @@
t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
val response = responseAs[WhiskRuleResponse]
- response should be(
+ checkResponse(
+ response,
WhiskRuleResponse(
namespace,
rule.name,
Status.INACTIVE,
trigger.fullyQualifiedName(false),
action.fullyQualifiedName(false),
- version = SemVer().upPatch))
+ version = SemVer().upPatch,
+ updated = dummyInstant))
}
}
@@ -623,14 +657,16 @@
status should be(OK)
t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
val response = responseAs[WhiskRuleResponse]
- response should be(
+ checkResponse(
+ response,
WhiskRuleResponse(
namespace,
rule.name,
Status.INACTIVE,
trigger.fullyQualifiedName(false),
action.fullyQualifiedName(false),
- version = SemVer().upPatch))
+ version = SemVer().upPatch,
+ updated = dummyInstant))
}
}
@@ -654,14 +690,16 @@
status should be(OK)
t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
val response = responseAs[WhiskRuleResponse]
- response should be(
+ checkResponse(
+ response,
WhiskRuleResponse(
namespace,
rule.name,
Status.INACTIVE,
trigger.fullyQualifiedName(false),
action.fullyQualifiedName(false),
- version = SemVer().upPatch))
+ version = SemVer().upPatch,
+ updated = dummyInstant))
}
}
@@ -685,14 +723,16 @@
status should be(OK)
t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe a[Some[_]]
val response = responseAs[WhiskRuleResponse]
- response should be(
+ checkResponse(
+ response,
WhiskRuleResponse(
namespace,
rule.name,
Status.INACTIVE,
trigger.fullyQualifiedName(false),
action.fullyQualifiedName(false),
- version = SemVer().upPatch))
+ version = SemVer().upPatch,
+ updated = dummyInstant))
}
}
@@ -713,14 +753,16 @@
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(
+ checkResponse(
+ response,
WhiskRuleResponse(
namespace,
rule.name,
Status.INACTIVE,
trigger.fullyQualifiedName(false),
action.fullyQualifiedName(false),
- version = SemVer().upPatch))
+ version = SemVer().upPatch,
+ updated = dummyInstant))
}
}
@@ -795,14 +837,16 @@
status should be(OK)
t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
val response = responseAs[WhiskRuleResponse]
- response should be(
+ checkResponse(
+ response,
WhiskRuleResponse(
namespace,
rule.name,
Status.ACTIVE,
trigger.fullyQualifiedName(false),
action.fullyQualifiedName(false),
- version = SemVer().upPatch))
+ version = SemVer().upPatch,
+ updated = dummyInstant))
}
}
@@ -948,7 +992,7 @@
Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
status should be(OK)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.toWhiskRule.withStatus(Status.INACTIVE))
+ checkResponse(response, rule.toWhiskRule.withStatus(Status.INACTIVE))
}
}
@@ -972,7 +1016,7 @@
status should be(OK)
t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
val response = responseAs[WhiskRuleResponse]
- response should be(rule.withStatus(Status.ACTIVE))
+ checkResponse(response, rule.withStatus(Status.ACTIVE))
}
}
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala
index 91b22a2..d87751b 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala
@@ -67,6 +67,7 @@
def aname() = MakeName.next("triggers_tests")
def afullname(namespace: EntityPath, name: String) = FullyQualifiedEntityName(namespace, EntityName(name))
val parametersLimit = Parameters.sizeLimit
+ val dummyInstant = Instant.now()
//// GET /triggers
it should "list triggers by default/explicit namespace" in {
@@ -183,6 +184,21 @@
}
}
+ it should "get trigger with updated field" in {
+ implicit val tid = transid()
+ val trigger = WhiskTrigger(namespace, aname(), Parameters("x", "b"))
+ put(entityStore, trigger)
+
+ // `updated` field should be compared with a document in DB
+ val t = get(entityStore, trigger.docid, WhiskTrigger)
+
+ Get(s"$collectionPath/${trigger.name}") ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val response = responseAs[WhiskTrigger]
+ response should be(trigger copy (updated = t.updated))
+ }
+ }
+
it should "report Conflict if the name was of a different type" in {
implicit val tid = transid()
val rule = WhiskRule(
@@ -217,7 +233,7 @@
deleteTrigger(trigger.docid)
status should be(OK)
val response = responseAs[WhiskTrigger]
- response should be(trigger.withoutRules)
+ checkWhiskEntityResponse(response, trigger.withoutRules)
}
}
@@ -229,7 +245,7 @@
deleteTrigger(trigger.docid)
status should be(OK)
val response = responseAs[WhiskTrigger]
- response should be(trigger.withoutRules)
+ checkWhiskEntityResponse(response, trigger.withoutRules)
}
}
@@ -323,11 +339,14 @@
deleteTrigger(trigger.docid)
status should be(OK)
val response = responseAs[WhiskTrigger]
- response should be(WhiskTrigger(
- trigger.namespace,
- trigger.name,
- trigger.parameters,
- version = trigger.version.upPatch).withoutRules)
+ checkWhiskEntityResponse(
+ response,
+ WhiskTrigger(
+ trigger.namespace,
+ trigger.name,
+ trigger.parameters,
+ version = trigger.version.upPatch,
+ updated = dummyInstant).withoutRules)
}
}
@@ -432,8 +451,7 @@
Get(s"$collectionPath/${trigger.name}") ~> Route.seal(routes(creds)) ~> check {
val response = responseAs[WhiskTrigger]
status should be(OK)
-
- response should be(trigger.toWhiskTrigger)
+ checkWhiskEntityResponse(response, trigger.toWhiskTrigger)
}
}
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
index 6f9a322..42fb0d0 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
@@ -431,7 +431,8 @@
"parameters" -> Parameters().toJson,
"version" -> SemVer().toJson,
"publish" -> JsFalse,
- "annotations" -> Parameters().toJson)
+ "annotations" -> Parameters().toJson,
+ "updated" -> pkg.updated.toEpochMilli.toJson)
}
it should "serialize and deserialize package binding" in {
@@ -443,7 +444,8 @@
"parameters" -> Parameters().toJson,
"version" -> SemVer().toJson,
"publish" -> JsFalse,
- "annotations" -> Parameters().toJson)
+ "annotations" -> Parameters().toJson,
+ "updated" -> pkg.updated.toEpochMilli.toJson)
//val legacyPkgAsJson = JsObject(pkgAsJson.fields + ("binding" -> JsObject("namespace" -> "x".toJson, "name" -> "y".toJson)))
WhiskPackage.serdes.write(pkg) shouldBe pkgAsJson
WhiskPackage.serdes.read(pkgAsJson) shouldBe pkg
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala
index e473070..3b10d76 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala
@@ -186,7 +186,8 @@
| "timeout": 60000,
| "memory": 256
| },
- | "namespace": "namespace"
+ | "namespace": "namespace",
+ | "updated": 1546268400000
|}""".stripMargin.parseJson
val action = WhiskDocumentReader.read(manifest[WhiskAction], json)
@@ -213,7 +214,8 @@
| "timeout": 60000,
| "memory": 256
| },
- | "namespace": "namespace"
+ | "namespace": "namespace",
+ | "updated": 1546268400000
|}""".stripMargin.parseJson
val action = WhiskDocumentReader.read(manifest[WhiskAction], json)
assertType(action.asInstanceOf[WhiskEntity], "action")