This is an automated email from the ASF dual-hosted git repository. markusthoemmes pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push: new 8fd78b7 Fix attachment name compatibility issue. (#3719) 8fd78b7 is described below commit 8fd78b788cdfe5f92bc898a8f99611a05704b3d5 Author: Chetan Mehrotra <chet...@apache.org> AuthorDate: Fri Jun 1 18:07:17 2018 +0530 Fix attachment name compatibility issue. (#3719) --- .../whisk/core/database/CouchDbRestStore.scala | 29 +++-- .../main/scala/whisk/core/entity/WhiskAction.scala | 4 +- .../test/AttachmentCompatibilityTests.scala | 134 +++++++++++++++++++++ 3 files changed, 156 insertions(+), 11 deletions(-) diff --git a/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala b/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala index ffa2aaf..8c401bd 100644 --- a/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala +++ b/common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala @@ -17,9 +17,6 @@ package whisk.core.database -import scala.concurrent.Await -import scala.concurrent.Future -import scala.concurrent.duration._ import akka.actor.ActorSystem import akka.event.Logging.ErrorLevel import akka.http.scaladsl.model._ @@ -28,14 +25,15 @@ import akka.stream.scaladsl._ import akka.util.ByteString import spray.json._ import whisk.common.{Logging, LoggingMarkers, MetricEmitter, TransactionId} -import whisk.core.entity.Attachments.Attached import whisk.core.database.StoreUtils._ -import whisk.core.entity.BulkEntityResult -import whisk.core.entity.DocInfo -import whisk.core.entity.DocumentReader -import whisk.core.entity.UUID +import whisk.core.entity.Attachments.Attached +import whisk.core.entity.{BulkEntityResult, DocInfo, DocumentReader, UUID} import whisk.http.Messages +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ +import scala.util.Try + /** * Basic client to put and delete artifacts in a data store. * @@ -474,12 +472,13 @@ class CouchDbRestStore[DocumentAbstraction <: DocumentSerializer](dbProtocol: St val (name, value) = fields.head value.asJsObject.getFields("content_type", "digest", "length") match { case Seq(JsString(contentTypeValue), JsString(digest), JsNumber(length)) => - val attachmentName = Uri.from(scheme = attachmentScheme, path = name).toString() val contentType = ContentType.parse(contentTypeValue) match { case Right(ct) => ct case Left(_) => ContentTypes.NoContentType //Should not happen } - attachmentHandler(doc, Attached(attachmentName, contentType, Some(length.intValue()), Some(digest))) + attachmentHandler( + doc, + Attached(getAttachmentName(name), contentType, Some(length.intValue()), Some(digest))) case x => throw DeserializationException("Attachment json does not have required fields" + x) @@ -489,6 +488,16 @@ class CouchDbRestStore[DocumentAbstraction <: DocumentSerializer](dbProtocol: St .getOrElse(doc) } + /** + * Determines if the attachment scheme confirms to new UUID based scheme or not + * and generates the name based on that + */ + private def getAttachmentName(name: String): String = { + Try(java.util.UUID.fromString(name)) + .map(_ => Uri.from(scheme = attachmentScheme, path = name).toString) + .getOrElse(name) + } + private def reportFailure[T, U](f: Future[T], onFailure: Throwable => U): Future[T] = { f.onFailure({ case _: ArtifactStoreException => // These failures are intentional and shouldn't trigger the catcher. diff --git a/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala index 2ecbd3e..a0011ff 100644 --- a/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala +++ b/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala @@ -391,7 +391,9 @@ object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[ def attachmentHandler(action: WhiskAction, attached: Attached): WhiskAction = { val eu = action.exec match { case exec @ CodeExecAsAttachment(_, Attached(attachmentName, _, _, _), _) => - require(attachmentName == attached.attachmentName) + require( + attachmentName == attached.attachmentName, + s"Attachment name '${attached.attachmentName}' does not match the expected name '$attachmentName'") exec.attach(attached) case exec => exec } diff --git a/tests/src/test/scala/whisk/core/database/test/AttachmentCompatibilityTests.scala b/tests/src/test/scala/whisk/core/database/test/AttachmentCompatibilityTests.scala new file mode 100644 index 0000000..6550cc0 --- /dev/null +++ b/tests/src/test/scala/whisk/core/database/test/AttachmentCompatibilityTests.scala @@ -0,0 +1,134 @@ +/* + * 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 whisk.core.database.test + +import java.io.ByteArrayInputStream +import java.util.Base64 + +import akka.http.scaladsl.model.{ContentType, StatusCodes} +import akka.stream.ActorMaterializer +import akka.stream.scaladsl.{Source, StreamConverters} +import akka.util.ByteString +import common.{StreamLogging, WskActorSystem} +import org.junit.runner.RunWith +import org.scalatest.concurrent.ScalaFutures +import org.scalatest.junit.JUnitRunner +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, FlatSpec, Matchers} +import pureconfig.loadConfigOrThrow +import spray.json.DefaultJsonProtocol +import whisk.common.TransactionId +import whisk.core.ConfigKeys +import whisk.core.database.{CouchDbConfig, CouchDbRestClient, NoDocumentException} +import whisk.core.entity.Attachments.Inline +import whisk.core.entity.test.ExecHelpers +import whisk.core.entity.{ + CodeExecAsAttachment, + DocInfo, + EntityName, + EntityPath, + WhiskAction, + WhiskEntity, + WhiskEntityStore +} + +import scala.concurrent.Future + +@RunWith(classOf[JUnitRunner]) +class AttachmentCompatibilityTests + extends FlatSpec + with Matchers + with ScalaFutures + with BeforeAndAfterEach + with BeforeAndAfterAll + with WskActorSystem + with ExecHelpers + with DbUtils + with DefaultJsonProtocol + with StreamLogging { + + //Bring in sync the timeout used by ScalaFutures and DBUtils + implicit override val patienceConfig: PatienceConfig = PatienceConfig(timeout = dbOpTimeout) + implicit val materializer = ActorMaterializer() + val config = loadConfigOrThrow[CouchDbConfig](ConfigKeys.couchdb) + val entityStore = WhiskEntityStore.datastore() + val client = + new CouchDbRestClient( + config.protocol, + config.host, + config.port, + config.username, + config.password, + config.databaseFor[WhiskEntity]) + + override def afterEach(): Unit = { + cleanup() + } + + override protected def withFixture(test: NoArgTest) = { + assume(isCouchStore(entityStore)) + super.withFixture(test) + } + + behavior of "Attachments" + + it should "read attachments created using old scheme" in { + implicit val tid: TransactionId = transid() + val namespace = EntityPath("attachment-compat-test1") + val exec = javaDefault("ZHViZWU=", Some("hello")) + val doc = + WhiskAction(namespace, EntityName("attachment_unique"), exec) + + doc.exec match { + case exec @ CodeExecAsAttachment(_, Inline(code), _) => + val attached = exec.manifest.attached.get + + val newDoc = doc.copy(exec = exec.copy(code = attached)) + newDoc.revision(doc.rev) + + val codeBytes = Base64.getDecoder().decode(code) + val stream = new ByteArrayInputStream(codeBytes) + val src = StreamConverters.fromInputStream(() => stream) + val info = entityStore.put(newDoc).futureValue + val info2 = attach(info, attached.attachmentName, attached.attachmentType, src).futureValue + docsToDelete += ((entityStore, info2)) + case _ => + fail("Exec must be code attachment") + } + + val doc2 = WhiskAction.get(entityStore, doc.docid).futureValue + doc2.exec shouldBe exec + } + + private def attach(doc: DocInfo, + name: String, + contentType: ContentType, + docStream: Source[ByteString, _]): Future[DocInfo] = { + client.putAttachment(doc.id.id, doc.rev.rev, name, contentType, docStream).map { + case Right(response) => + val id = response.fields("id").convertTo[String] + val rev = response.fields("rev").convertTo[String] + DocInfo ! (id, rev) + + case Left(StatusCodes.NotFound) => + throw NoDocumentException("Not found on 'readAttachment'.") + + case Left(code) => + throw new Exception("Unexpected http response code: " + code) + } + } +} -- To stop receiving notification emails like this one, please contact markusthoem...@apache.org.