blob: 3e88e52e3c38b5c7246e5e394980f02906fe28c9 [file] [log] [blame]
/*
* 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.limits
import java.io.File
import java.io.PrintWriter
import scala.concurrent.duration.DurationInt
import scala.language.postfixOps
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import common.TestHelpers
import common.TestUtils
import common.TestUtils.TOO_LARGE
import common.WhiskProperties
import common.Wsk
import common.WskProps
import common.WskTestHelpers
import spray.json._
import spray.json.DefaultJsonProtocol._
import whisk.core.entity.ActivationEntityLimit
import whisk.core.entity.ActivationResponse
import whisk.core.entity.Exec
import whisk.core.entity.LogLimit
import whisk.core.entity.size._
import whisk.core.entity.size.SizeString
import whisk.http.Messages
@RunWith(classOf[JUnitRunner])
class ActionLimitsTests extends TestHelpers with WskTestHelpers {
implicit val wskprops = WskProps()
val wsk = new Wsk()
val defaultDosAction = TestUtils.getTestActionFilename("timeout.js")
val allowedActionDuration = 10 seconds
val testActionsDir = WhiskProperties.getFileRelativeToWhiskHome("tests/dat/actions")
val actionCodeLimit = Exec.sizeLimit
behavior of "Action limits"
/**
* Test a long running action that exceeds the maximum execution time allowed for action
* by setting the action limit explicitly and attempting to run the action for an additional second.
*/
it should "error with a proper warning if the action exceeds its time limits" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val name = "TestActionCausingTimeout"
assetHelper.withCleaner(wsk.action, name, confirmDelete = true) {
(action, _) => action.create(name, Some(defaultDosAction), timeout = Some(allowedActionDuration))
}
val run = wsk.action.invoke(name, Map("payload" -> allowedActionDuration.plus(1 second).toMillis.toJson))
withActivation(wsk.activation, run) {
_.response.result.get.fields("error") shouldBe {
Messages.timedoutActivation(allowedActionDuration, false).toJson
}
}
}
/**
* Test an action that does not exceed the allowed execution timeout of an action.
*/
it should "succeed on an action staying within its time limits" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val name = "TestActionCausingNoTimeout"
assetHelper.withCleaner(wsk.action, name, confirmDelete = true) {
(action, _) => action.create(name, Some(defaultDosAction), timeout = Some(allowedActionDuration))
}
val run = wsk.action.invoke(name, Map("payload" -> allowedActionDuration.minus(1 second).toMillis.toJson))
withActivation(wsk.activation, run) {
_.response.result.get.toString should include(
"""[OK] message terminated successfully""")
}
}
it should "succeed but truncate logs, if log size exceeds its limit" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val allowedSize = 0 megabytes
val name = "TestActionCausingExceededLogs"
assetHelper.withCleaner(wsk.action, name, confirmDelete = true) {
val actionName = TestUtils.getTestActionFilename("dosLogs.js")
(action, _) => action.create(name, Some(actionName), logsize = Some(allowedSize))
}
val writing = allowedSize + 1.megabytes
val characters = writing.toBytes / 2 // a character takes 2 bytes
val run = wsk.action.invoke(name, Map("payload" -> characters.toJson))
withActivation(wsk.activation, run) { response =>
val lines = response.logs.get
lines.last shouldBe LogLimit(allowedSize).truncatedLogMessage
// dropping 39 characters (timestamp + streamname)
lines.dropRight(1).map(_.drop(39)).mkString.sizeInBytes should be <= allowedSize
}
}
it should "succeed but truncate result, if result exceeds its limit" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val name = "TestActionCausingExcessiveResult"
assetHelper.withCleaner(wsk.action, name) {
val actionName = TestUtils.getTestActionFilename("sizedResult.js")
(action, _) => action.create(name, Some(actionName))
}
val allowedSize = ActivationEntityLimit.MAX_ACTIVATION_ENTITY_LIMIT.toBytes
val run = wsk.action.invoke(name, Map("size" -> (allowedSize + 1).toJson, "char" -> "a".toJson))
withActivation(wsk.activation, run) { activation =>
val response = activation.response
response.success shouldBe false
response.status shouldBe ActivationResponse.messageForCode(ActivationResponse.ContainerError)
val msg = response.result.get.fields(ActivationResponse.ERROR_FIELD).convertTo[String]
val expected = Messages.truncatedResponse((allowedSize + 10).B, allowedSize.B)
withClue(s"is: ${msg.take(expected.length)}\nexpected: $expected") {
msg.startsWith(expected) shouldBe true
}
msg.endsWith("a") shouldBe true
}
}
it should "succeed with one log line" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val name = "TestActionCausingExceededLogs"
assetHelper.withCleaner(wsk.action, name, confirmDelete = true) {
val actionName = TestUtils.getTestActionFilename("dosLogs.js")
(action, _) => action.create(name, Some(actionName))
}
val run = wsk.action.invoke(name)
withActivation(wsk.activation, run) { response =>
val logs = response.logs.get
withClue(logs) { logs.size shouldBe 1 }
logs.head should include("0123456789abcdef")
response.response.status shouldBe "success"
response.response.result shouldBe Some(JsObject(
"msg" -> 1.toJson))
}
}
it should "fail on creating an action with exec which is too big" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val name = "TestActionCausingExecTooBig"
val actionCode = new File(s"$testActionsDir${File.separator}$name.js")
actionCode.createNewFile()
val pw = new PrintWriter(actionCode)
pw.write("a" * (actionCodeLimit.toBytes + 1).toInt)
pw.close
assetHelper.withCleaner(wsk.action, name, confirmDelete = false) {
(action, _) =>
action.create(name, Some(actionCode.getAbsolutePath), expectedExitCode = TOO_LARGE)
}
actionCode.delete
}
}