| /* |
| * Copyright 2015-2016 IBM Corporation |
| * |
| * Licensed 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 whisk.core.entity.test |
| |
| import java.time.Clock |
| import java.time.Instant |
| import scala.concurrent.Await |
| import scala.util.Try |
| import org.junit.runner.RunWith |
| import org.scalatest.BeforeAndAfter |
| import org.scalatest.FlatSpec |
| import org.scalatest.Matchers |
| import org.scalatest.junit.JUnitRunner |
| import spray.json.JsObject |
| import whisk.core.WhiskConfig |
| import whisk.core.database.test.DbUtils |
| import whisk.core.entity.ActivationId |
| import whisk.core.entity.AuthKey |
| import whisk.core.entity.Binding |
| import whisk.core.entity.EntityName |
| import whisk.core.entity.Exec |
| import whisk.core.entity.EntityPath |
| import whisk.core.entity.Subject |
| import whisk.core.entity.WhiskAction |
| import whisk.core.entity.WhiskActivation |
| import whisk.core.entity.WhiskAuth |
| import whisk.core.entity.WhiskEntity |
| import whisk.core.entity.WhiskEntityQueries.listAllInNamespace |
| import whisk.core.entity.WhiskEntityQueries.listEntitiesInNamespace |
| import whisk.core.entity.WhiskEntityQueries.listCollectionInAnyNamespace |
| import whisk.core.entity.WhiskEntityQueries.listCollectionByName |
| import whisk.core.entity.WhiskEntityQueries.listCollectionInNamespace |
| import whisk.core.entity.WhiskEntityStore |
| import whisk.core.entity.WhiskPackage |
| import whisk.core.entity.WhiskRule |
| import whisk.core.entity.WhiskTrigger |
| import org.scalatest.BeforeAndAfterAll |
| import scala.language.postfixOps |
| import akka.event.Logging.InfoLevel |
| |
| import common.WskActorSystem |
| import whisk.core.entity.FullyQualifiedEntityName |
| |
| @RunWith(classOf[JUnitRunner]) |
| class ViewTests extends FlatSpec |
| with BeforeAndAfter |
| with BeforeAndAfterAll |
| with Matchers |
| with DbUtils |
| with WskActorSystem { |
| |
| def aname = MakeName.next("viewtests") |
| def afullname(namespace: EntityPath) = FullyQualifiedEntityName(namespace, aname) |
| |
| object MakeName { |
| @volatile var counter = 1 |
| def next(prefix: String = "test")(): EntityName = { |
| counter = counter + 1 |
| EntityName(s"${prefix}_name$counter") |
| } |
| } |
| |
| val creds1 = WhiskAuth(Subject("s12345"), AuthKey()) |
| val namespace1 = EntityPath(creds1.subject.asString) |
| |
| val creds2 = WhiskAuth(Subject("t12345"), AuthKey()) |
| val namespace2 = EntityPath(creds2.subject.asString) |
| |
| val config = new WhiskConfig(WhiskEntityStore.requiredProperties) |
| val datastore = WhiskEntityStore.datastore(config) |
| datastore.setVerbosity(InfoLevel) |
| |
| after { |
| cleanup() |
| } |
| |
| override def afterAll() { |
| println("Shutting down store connections") |
| datastore.shutdown() |
| super.afterAll() |
| } |
| |
| behavior of "Datastore View" |
| |
| def getAllInNamespace(ns: EntityPath)(implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val result = Await.result(listAllInNamespace(datastore, ns, false), dbOpTimeout).values.toList flatMap { t => t } |
| val expected = entities filter { _.namespace.root.toPath == ns } |
| result.length should be(expected.length) |
| expected forall { e => result contains e.summaryAsJson } should be(true) |
| } |
| |
| def getEntitiesInNamespace(ns: EntityPath)(implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val map = Await.result(listEntitiesInNamespace(datastore, ns, false), dbOpTimeout) |
| val result = map.values.toList flatMap { t => t } |
| val expected = entities filter { !_.isInstanceOf[WhiskActivation] } filter { _.namespace.root.toPath == ns } |
| map.get(WhiskActivation.collectionName) should be(None) |
| result.length should be(expected.length) |
| expected forall { e => result contains e.summaryAsJson } should be(true) |
| } |
| |
| def getKindInNamespace(ns: EntityPath, kind: String, f: (WhiskEntity) => Boolean)(implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val result = Await.result(listCollectionInNamespace(datastore, kind, ns, 0, 0, convert = None) map { _.left.get map { e => e } }, dbOpTimeout) |
| val expected = entities filter { e => f(e) && e.namespace.root.toPath == ns } |
| result.length should be(expected.length) |
| expected forall { e => result contains e.summaryAsJson } should be(true) |
| } |
| |
| def getPublicPackages(implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val result = Await.result(listCollectionInAnyNamespace(datastore, "packages", 0, 0, true, convert = None) map { _.left.get map { e => e } }, dbOpTimeout) |
| val expected = entities filter { case (e: WhiskPackage) => e.publish && e.binding.isEmpty case _ => false } |
| result.length should be >= (expected.length) |
| expected forall { e => result contains e.summaryAsJson } should be(true) |
| } |
| |
| def getKindInNamespaceWithDoc[T](ns: EntityPath, kind: String, f: (WhiskEntity) => Boolean, convert: Option[JsObject => Try[T]])(implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val result = Await.result(listCollectionInNamespace(datastore, kind, ns, 0, 0, convert = convert) map { _.right.get }, dbOpTimeout) |
| val expected = entities filter { e => f(e) && e.namespace.root.toPath == ns } |
| result.length should be(expected.length) |
| expected forall { e => result contains e } should be(true) |
| } |
| |
| def getKindInNamespaceByName(ns: EntityPath, kind: String, name: EntityName, f: (WhiskEntity) => Boolean)(implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val result = Await.result(listCollectionByName(datastore, kind, ns, name, 0, 0, convert = None) map { _.left.get map { e => e } }, dbOpTimeout) |
| val expected = entities filter { e => f(e) && e.namespace.root.toPath == ns } |
| result.length should be(expected.length) |
| expected forall { e => result contains e.summaryAsJson } should be(true) |
| } |
| |
| def getKindInPackage(ns: EntityPath, kind: String, f: (WhiskEntity) => Boolean)(implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val result = Await.result(listCollectionInNamespace(datastore, kind, ns, 0, 0, convert = None) map { _.left.get map { e => e } }, dbOpTimeout) |
| val expected = entities filter { e => f(e) && e.namespace == ns } |
| result.length should be(expected.length) |
| expected forall { e => result contains e.summaryAsJson } should be(true) |
| } |
| |
| def getKindInNamespaceByNameSortedByDate( |
| ns: EntityPath, kind: String, name: EntityName, skip: Int, count: Int, start: Option[Instant], end: Option[Instant], f: (WhiskEntity) => Boolean)( |
| implicit entities: Seq[WhiskEntity]) = { |
| implicit val tid = transid() |
| val result = Await.result(listCollectionByName(datastore, kind, ns, name, skip, count, start, end, convert = None) map { _.left.get map { e => e } }, dbOpTimeout) |
| val expected = entities filter { e => f(e) && e.namespace.root.toPath == ns } sortBy { case (e: WhiskActivation) => e.start.toEpochMilli; case _ => 0 } map { _.summaryAsJson } |
| result.length should be(expected.length) |
| result should be(expected reverse) |
| } |
| |
| it should "query whisk view by namespace, collection and entity name" in { |
| implicit val tid = transid() |
| val exec = Exec.bb("image") |
| val pkgname1 = namespace1.addPath(aname) |
| val pkgname2 = namespace2.addPath(aname) |
| val actionName = aname |
| def now = Instant.now(Clock.systemUTC()) |
| |
| // creates 17 entities in each namespace as follows: |
| // - 2 actions in each namespace in the default namespace |
| // - 2 actions in the same package within a namespace |
| // - 1 action in two different packages in the same namespace |
| // - 1 action in package with prescribed name |
| // - 2 triggers in each namespace |
| // - 2 rules in each namespace |
| // - 2 packages in each namespace |
| // - 2 package bindings in each namespace |
| // - 2 activations in each namespace (some may have prescribed action name to query by name) |
| implicit val entities = Seq( |
| WhiskAction(namespace1, aname, exec), |
| WhiskAction(namespace1, aname, exec), |
| WhiskAction(namespace1.addPath(aname), aname, exec), |
| WhiskAction(namespace1.addPath(aname), aname, exec), |
| WhiskAction(pkgname1, aname, exec), |
| WhiskAction(pkgname1, aname, exec), |
| WhiskAction(pkgname1, actionName, exec), |
| WhiskTrigger(namespace1, aname), |
| WhiskTrigger(namespace1, aname), |
| WhiskRule(namespace1, aname, trigger = afullname(namespace1), action = afullname(namespace1)), |
| WhiskRule(namespace1, aname, trigger = afullname(namespace1), action = afullname(namespace1)), |
| WhiskPackage(namespace1, aname), |
| WhiskPackage(namespace1, aname), |
| WhiskPackage(namespace1, aname, Some(Binding(namespace2.root, aname))), |
| WhiskPackage(namespace1, aname, Some(Binding(namespace2.root, aname))), |
| WhiskActivation(namespace1, aname, Subject(), ActivationId(), start = now, end = now), |
| WhiskActivation(namespace1, aname, Subject(), ActivationId(), start = now, end = now), |
| |
| WhiskAction(namespace2, aname, exec), |
| WhiskAction(namespace2, aname, exec), |
| WhiskAction(namespace2.addPath(aname), aname, exec), |
| WhiskAction(namespace2.addPath(aname), aname, exec), |
| WhiskAction(pkgname2, aname, exec), |
| WhiskAction(pkgname2, aname, exec), |
| WhiskTrigger(namespace2, aname), |
| WhiskTrigger(namespace2, aname), |
| WhiskRule(namespace2, aname, trigger = afullname(namespace2), action = afullname(namespace2)), |
| WhiskRule(namespace2, aname, trigger = afullname(namespace2), action = afullname(namespace2)), |
| WhiskPackage(namespace2, aname), |
| WhiskPackage(namespace2, aname), |
| WhiskPackage(namespace2, aname, Some(Binding(namespace1.root, aname))), |
| WhiskPackage(namespace2, aname, Some(Binding(namespace1.root, aname))), |
| WhiskActivation(namespace2, aname, Subject(), ActivationId(), start = now, end = now), |
| WhiskActivation(namespace2, actionName, Subject(), ActivationId(), start = now, end = now), |
| WhiskActivation(namespace2, actionName, Subject(), ActivationId(), start = now, end = now)) |
| |
| entities foreach { put(datastore, _) } |
| waitOnView(datastore, namespace1, entities.length / 2) |
| waitOnView(datastore, namespace2, entities.length / 2) |
| |
| getAllInNamespace(namespace1) |
| getKindInNamespace(namespace1, "actions", { case (e: WhiskAction) => true case (_) => false }) |
| getKindInNamespace(namespace1, "triggers", { case (e: WhiskTrigger) => true case (_) => false }) |
| getKindInNamespace(namespace1, "rules", { case (e: WhiskRule) => true case (_) => false }) |
| getKindInNamespace(namespace1, "packages", { case (e: WhiskPackage) => true case (_) => false }) |
| getKindInNamespace(namespace1, "activations", { case (e: WhiskActivation) => true case (_) => false }) |
| getKindInPackage(pkgname1, "actions", { case (e: WhiskAction) => true case (_) => false }) |
| getKindInNamespaceByName(pkgname1, "actions", actionName, { case (e: WhiskAction) => (e.name == actionName) case (_) => false }) |
| getEntitiesInNamespace(namespace1) |
| |
| getAllInNamespace(namespace2) |
| getKindInNamespace(namespace2, "actions", { case (e: WhiskAction) => true case (_) => false }) |
| getKindInNamespace(namespace2, "triggers", { case (e: WhiskTrigger) => true case (_) => false }) |
| getKindInNamespace(namespace2, "rules", { case (e: WhiskRule) => true case (_) => false }) |
| getKindInNamespace(namespace2, "packages", { case (e: WhiskPackage) => true case (_) => false }) |
| getKindInNamespace(namespace2, "activations", { case (e: WhiskActivation) => true case (_) => false }) |
| getKindInPackage(pkgname2, "actions", { case (e: WhiskAction) => true case (_) => false }) |
| getKindInNamespaceByName(namespace2, "activations", actionName, { case (e: WhiskActivation) => (e.name == actionName) case (_) => false }) |
| getEntitiesInNamespace(namespace2) |
| } |
| |
| it should "query whisk view for activations sorted by date" in { |
| implicit val tid = transid() |
| val actionName = aname |
| val now = Instant.now(Clock.systemUTC()) |
| val others = Seq( |
| WhiskAction(namespace1, aname, Exec.js("??")), |
| WhiskAction(namespace1, aname, Exec.js("??"))) |
| implicit val entities = Seq( |
| WhiskActivation(namespace1, actionName, Subject(), ActivationId(), start = now, end = now), |
| WhiskActivation(namespace1, actionName, Subject(), ActivationId(), start = now.plusSeconds(20), end = now.plusSeconds(20)), |
| WhiskActivation(namespace1, actionName, Subject(), ActivationId(), start = now.plusSeconds(10), end = now.plusSeconds(20)), |
| WhiskActivation(namespace1, actionName, Subject(), ActivationId(), start = now.plusSeconds(40), end = now.plusSeconds(20)), |
| WhiskActivation(namespace1, actionName, Subject(), ActivationId(), start = now.plusSeconds(30), end = now.plusSeconds(20))) |
| |
| others foreach { put(datastore, _) } |
| entities foreach { put(datastore, _) } |
| waitOnView(datastore, namespace1, others.length + entities.length) |
| |
| getKindInNamespaceByNameSortedByDate(namespace1, "activations", actionName, 0, 5, None, None, { |
| case (e: WhiskActivation) => e.name == actionName |
| case (_) => false |
| }) |
| |
| val since = entities(1).start |
| val upto = entities(4).start |
| getKindInNamespaceByNameSortedByDate(namespace1, "activations", actionName, 0, 5, Some(since), Some(upto), { |
| case (e: WhiskActivation) => e.name == actionName && (e.start.equals(since) || e.start.equals(upto) || (e.start.isAfter(since) && e.start.isBefore(upto))) |
| case (_) => false |
| }) |
| } |
| |
| it should "query whisk and retrieve full documents" in { |
| implicit val tid = transid() |
| val actionName = aname |
| val now = Instant.now(Clock.systemUTC()) |
| implicit val entities = Seq( |
| WhiskAction(namespace1, aname, Exec.js("??")), |
| WhiskAction(namespace1, aname, Exec.js("??"))) |
| |
| entities foreach { put(datastore, _) } |
| waitOnView(datastore, namespace1, entities.length) |
| getKindInNamespaceWithDoc[WhiskAction](namespace1, "actions", { |
| case (e: WhiskAction) => true case (_) => false |
| }, Some { |
| case (o: JsObject) => Try { WhiskAction.serdes.read(o) } |
| }) |
| } |
| |
| it should "query whisk for public packages in all namespaces" in { |
| implicit val tid = transid() |
| implicit val entities = Seq( |
| WhiskPackage(namespace1, aname, publish = true), |
| WhiskPackage(namespace1, aname, publish = false), |
| WhiskPackage(namespace1, aname, Some(Binding(namespace2.root, aname)), publish = true), |
| WhiskPackage(namespace1, aname, Some(Binding(namespace2.root, aname))), |
| |
| WhiskPackage(namespace2, aname, publish = true), |
| WhiskPackage(namespace2, aname, publish = false), |
| WhiskPackage(namespace2, aname, Some(Binding(namespace1.root, aname)), publish = true), |
| WhiskPackage(namespace2, aname, Some(Binding(namespace1.root, aname)))) |
| |
| entities foreach { put(datastore, _) } |
| waitOnView(datastore, namespace1, entities.length / 2) |
| waitOnView(datastore, namespace2, entities.length / 2) |
| getPublicPackages(entities) |
| } |
| } |