You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by ma...@apache.org on 2017/09/11 08:14:24 UTC

[incubator-openwhisk] branch master updated: Add tests to check high-availability of the controller. (#2661)

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 66c9dbc  Add tests to check high-availability of the controller. (#2661)
66c9dbc is described below

commit 66c9dbc094234c6851731c2f858ea9e5977b5409
Author: Christian Bickel <gi...@cbickel.de>
AuthorDate: Mon Sep 11 10:14:21 2017 +0200

    Add tests to check high-availability of the controller. (#2661)
---
 tests/src/test/scala/ha/ShootComponentsTests.scala | 147 +++++++++++++++++++++
 1 file changed, 147 insertions(+)

diff --git a/tests/src/test/scala/ha/ShootComponentsTests.scala b/tests/src/test/scala/ha/ShootComponentsTests.scala
new file mode 100644
index 0000000..4b28ded
--- /dev/null
+++ b/tests/src/test/scala/ha/ShootComponentsTests.scala
@@ -0,0 +1,147 @@
+/*
+ * 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 ha
+
+import java.io.File
+import java.time.Instant
+
+import scala.concurrent.Future
+import scala.concurrent.duration.DurationInt
+import scala.util.Try
+
+import org.junit.runner.RunWith
+import org.scalatest.FlatSpec
+import org.scalatest.Matchers
+import org.scalatest.concurrent.ScalaFutures
+import org.scalatest.junit.JUnitRunner
+
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.model.HttpRequest
+import akka.http.scaladsl.model.StatusCodes
+import akka.http.scaladsl.unmarshalling.Unmarshal
+import akka.stream.ActorMaterializer
+import common.TestUtils
+import common.WhiskProperties
+import common.Wsk
+import common.WskActorSystem
+import common.WskProps
+import common.WskTestHelpers
+import whisk.core.WhiskConfig
+import whisk.utils.retry
+
+@RunWith(classOf[JUnitRunner])
+class ShootComponentsTests extends FlatSpec with Matchers with WskTestHelpers with ScalaFutures with WskActorSystem {
+
+  implicit val wskprops = WskProps()
+  val wsk = new Wsk
+  val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
+
+  implicit val materializer = ActorMaterializer()
+  implicit val testConfig = PatienceConfig(1.minute)
+
+  val controller0DockerHost = WhiskProperties.getBaseControllerHost() + ":" + WhiskProperties.getProperty(
+    WhiskConfig.dockerPort)
+
+  def restartComponent(host: String, component: String) = {
+    def file(path: String) = Try(new File(path)).filter(_.exists).map(_.getAbsolutePath).toOption
+    val docker = (file("/usr/bin/docker") orElse file("/usr/local/bin/docker")).getOrElse("docker")
+
+    val cmd = Seq(docker, "--host", host, "restart", component)
+    println(s"Running command: ${cmd.mkString(" ")}")
+
+    TestUtils.runCmd(0, new File("."), cmd: _*)
+  }
+
+  def ping(host: String, port: Int) = {
+    val response = Try { Http().singleRequest(HttpRequest(uri = s"http://$host:$port/ping")).futureValue }.toOption
+
+    response.map { res =>
+      (res.status, Unmarshal(res).to[String].futureValue)
+    }
+  }
+
+  def isControllerAlive(instance: Int): Boolean = {
+    require(instance >= 0 && instance < 2, "Controller instance not known.")
+
+    val host = WhiskProperties.getProperty("controller.hosts").split(",")(instance)
+    val port = WhiskProperties.getControllerBasePort + instance
+
+    val res = ping(host, port)
+    res == Some((StatusCodes.OK, "pong"))
+  }
+
+  def doRequests(amount: Int, actionName: String): Seq[(Int, Int)] = {
+    (0 until amount).map { i =>
+      val start = Instant.now
+
+      // Do POSTs and GETs
+      val invokeExit = Future { wsk.action.invoke(actionName, expectedExitCode = TestUtils.DONTCARE_EXIT).exitCode }
+      val getExit = Future { wsk.action.get(actionName, expectedExitCode = TestUtils.DONTCARE_EXIT).exitCode }
+
+      println(s"Done rerquests with responses: invoke: ${invokeExit.futureValue} and get: ${getExit.futureValue}")
+
+      // Do at most one action invocation per second to avoid getting 429s. (60 req/min - limit)
+      val wait = 1000 - (Instant.now.toEpochMilli - start.toEpochMilli)
+      Thread.sleep(if (wait < 0) 0L else if (wait > 1000) 1000L else wait)
+      (invokeExit.futureValue, getExit.futureValue)
+    }
+  }
+
+  behavior of "Controllers hot standby"
+
+  it should "use controller1 if controller0 goes down" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+    if (WhiskProperties.getProperty(WhiskConfig.controllerInstances).toInt >= 2) {
+      val actionName = "shootcontroller"
+
+      assetHelper.withCleaner(wsk.action, actionName) { (action, _) =>
+        action.create(actionName, defaultAction)
+      }
+
+      // Produce some load on the system for 100 seconds (each second one request). Kill the controller after 4 requests
+      val totalRequests = 100
+
+      val requestsBeforeRestart = doRequests(4, actionName)
+
+      // Kill the controller
+      restartComponent(controller0DockerHost, "controller0")
+      // Wait until down
+      retry({
+        isControllerAlive(0) shouldBe false
+      }, 100, Some(100.milliseconds))
+      // Check that second controller is still up
+      isControllerAlive(1) shouldBe true
+
+      val requestsAfterRestart = doRequests(totalRequests - 4, actionName)
+
+      val requests = requestsBeforeRestart ++ requestsAfterRestart
+
+      val unsuccessfulInvokes = requests.map(_._1).count(_ != TestUtils.SUCCESS_EXIT)
+      // Allow 3 failures for the 90 seconds
+      unsuccessfulInvokes should be <= 3
+
+      val unsuccessfulGets = requests.map(_._2).count(_ != TestUtils.SUCCESS_EXIT)
+      // Only allow 1 failure in GET requests, because they are idempotent and they should be passed to the next controller if one crashes
+      unsuccessfulGets shouldBe 0
+
+      // Check that both controllers are up
+      // controller0
+      isControllerAlive(0) shouldBe true
+      //controller1
+      isControllerAlive(1) shouldBe true
+    }
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].