-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add administrative interface to invoker and controller to reconfigure runtimes #4790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,8 +21,9 @@ import akka.Done | |
| import akka.actor.{ActorSystem, CoordinatedShutdown} | ||
| import akka.event.Logging.InfoLevel | ||
| import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ | ||
| import akka.http.scaladsl.model.{StatusCodes, Uri} | ||
| import akka.http.scaladsl.model.StatusCodes._ | ||
| import akka.http.scaladsl.model.Uri | ||
| import akka.http.scaladsl.model.headers.BasicHttpCredentials | ||
| import akka.http.scaladsl.server.Route | ||
| import akka.stream.ActorMaterializer | ||
| import kamon.Kamon | ||
|
|
@@ -32,7 +33,7 @@ import spray.json.DefaultJsonProtocol._ | |
| import spray.json._ | ||
| import org.apache.openwhisk.common.Https.HttpsConfig | ||
| import org.apache.openwhisk.common.{AkkaLogging, ConfigMXBean, Logging, LoggingMarkers, TransactionId} | ||
| import org.apache.openwhisk.core.WhiskConfig | ||
| import org.apache.openwhisk.core.{ConfigKeys, WhiskConfig} | ||
| import org.apache.openwhisk.core.connector.MessagingProvider | ||
| import org.apache.openwhisk.core.containerpool.logging.LogStoreProvider | ||
| import org.apache.openwhisk.core.database.{ActivationStoreProvider, CacheChangeNotification, RemoteCacheInvalidation} | ||
|
|
@@ -97,7 +98,7 @@ class Controller(val instance: ControllerInstanceId, | |
| (pathEndOrSingleSlash & get) { | ||
| complete(info) | ||
| } | ||
| } ~ apiV1.routes ~ swagger.swaggerRoutes ~ internalInvokerHealth | ||
| } ~ apiV1.routes ~ swagger.swaggerRoutes ~ internalInvokerHealth ~ configRuntime | ||
| } | ||
|
|
||
| // initialize datastores | ||
|
|
@@ -176,6 +177,59 @@ class Controller(val instance: ControllerInstanceId, | |
| LogLimit.config, | ||
| runtimes, | ||
| List(apiV1.basepath())) | ||
|
|
||
| private val controllerUsername = loadConfigOrThrow[String](ConfigKeys.whiskControllerUsername) | ||
| private val controllerPassword = loadConfigOrThrow[String](ConfigKeys.whiskControllerPassword) | ||
|
|
||
| /** | ||
| * config runtime | ||
| */ | ||
| private val configRuntime = { | ||
| implicit val executionContext = actorSystem.dispatcher | ||
| (path("config" / "runtime") & post) { | ||
| extractCredentials { | ||
| case Some(BasicHttpCredentials(username, password)) => | ||
| if (username == controllerUsername && password == controllerPassword) { | ||
| entity(as[String]) { runtime => | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually, entity(as[Runtimes]) may be better, but if we apply this, need to change Fortunately,we can reuse openwhisk itself's initialize method, just convert the runtime string to Runtimes. So here, i think pass runtime string would be ok.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can do |
||
| val execManifest = ExecManifest.initialize(whiskConfig, Some(runtime)) | ||
| if (execManifest.isFailure) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scala nit: you can use a case match here instead.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just follow other codes using |
||
| logging.info(this, s"received invalid runtimes manifest") | ||
| complete(StatusCodes.BadRequest) | ||
| } else { | ||
| parameter('limit.?) { limit => | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Support passed limit invokers, e.g. |
||
| limit match { | ||
| case Some(targetValue) => | ||
| val pattern = """\d+:\d""" | ||
| if (targetValue.matches(pattern)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scala nit: you can rewrite this either if/else and nested clauses using case matching on regex.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm.. can you show a example? my codes like below |
||
| val invokerArray = targetValue.split(":") | ||
| val beginIndex = invokerArray(0).toInt | ||
| val finishIndex = invokerArray(1).toInt | ||
| if (finishIndex < beginIndex) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be great to support
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, already support |
||
| complete(StatusCodes.BadRequest, "finishIndex can't be less than beginIndex") | ||
| } else { | ||
| val targetInvokers = (beginIndex to finishIndex).toList | ||
| loadBalancer.sendRuntimeToInvokers(runtime, Some(targetInvokers)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a later validation to check that the invoker indexing is in range?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, validate it in ShardingContainerPoolLoadbalacer.scala |
||
| logging.info(this, "config runtime request is already sent to target invokers") | ||
| complete(StatusCodes.Accepted) | ||
| } | ||
| } else { | ||
| complete(StatusCodes.BadRequest, "limit value can't match [beginIndex:finishIndex]") | ||
| } | ||
| case None => | ||
| loadBalancer.sendRuntimeToInvokers(runtime, None) | ||
| logging.info(this, "config runtime request is already sent to all managed invokers") | ||
| complete(StatusCodes.Accepted) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed to |
||
| } | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| complete(StatusCodes.Unauthorized, "username or password is wrong") | ||
| } | ||
| case _ => complete(StatusCodes.Unauthorized) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,18 +19,22 @@ package org.apache.openwhisk.core.containerpool | |
|
|
||
| import akka.actor.{Actor, ActorRef, ActorRefFactory, Props} | ||
| import org.apache.openwhisk.common.{Logging, LoggingMarkers, MetricEmitter, TransactionId} | ||
| import org.apache.openwhisk.core.connector.MessageFeed | ||
| import org.apache.openwhisk.core.connector.{MessageFeed, PrewarmContainerData} | ||
| import org.apache.openwhisk.core.entity.ExecManifest.ReactivePrewarmingConfig | ||
| import org.apache.openwhisk.core.entity._ | ||
| import org.apache.openwhisk.core.entity.size._ | ||
|
|
||
| import scala.annotation.tailrec | ||
| import scala.collection.immutable | ||
| import scala.collection.mutable.ListBuffer | ||
| import scala.concurrent.duration._ | ||
| import scala.util.{Random, Try} | ||
|
|
||
| case class ColdStartKey(kind: String, memory: ByteSize) | ||
|
|
||
| case class PreWarmConfigList(list: List[PrewarmingConfig]) | ||
| object PrewarmQuery | ||
|
|
||
| case object EmitMetrics | ||
|
|
||
| case object AdjustPrewarmedContainer | ||
|
|
@@ -68,6 +72,7 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
| var busyPool = immutable.Map.empty[ActorRef, ContainerData] | ||
| var prewarmedPool = immutable.Map.empty[ActorRef, PreWarmedData] | ||
| var prewarmStartingPool = immutable.Map.empty[ActorRef, (String, ByteSize)] | ||
| var latestPrewarmConfig = prewarmConfig | ||
| // If all memory slots are occupied and if there is currently no container to be removed, than the actions will be | ||
| // buffered here to keep order of computation. | ||
| // Otherwise actions with small memory-limits could block actions with large memory limits. | ||
|
|
@@ -297,6 +302,34 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
| case RescheduleJob => | ||
| freePool = freePool - sender() | ||
| busyPool = busyPool - sender() | ||
| case prewarmConfigList: PreWarmConfigList => | ||
| logging.info(this, "update prewarm configuration request is send to invoker") | ||
| val passedPrewarmConfig = prewarmConfigList.list | ||
| var newPrewarmConfig: List[PrewarmingConfig] = List.empty | ||
| latestPrewarmConfig foreach { config => | ||
| newPrewarmConfig = newPrewarmConfig :+ passedPrewarmConfig | ||
| .find(passedConfig => | ||
| passedConfig.exec.kind == config.exec.kind && passedConfig.memoryLimit == config.memoryLimit) | ||
| .getOrElse(config) | ||
| } | ||
| latestPrewarmConfig = newPrewarmConfig | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Support just change specify runtime config as well, e.g. |
||
| // Delete prewarmedPool firstly | ||
| prewarmedPool foreach { element => | ||
| val actor = element._1 | ||
| actor ! Remove | ||
| prewarmedPool = prewarmedPool - actor | ||
| } | ||
| latestPrewarmConfig foreach { config => | ||
| logging.info( | ||
| this, | ||
| s"add pre-warming ${config.initialCount} ${config.exec.kind} ${config.memoryLimit.toString}")( | ||
| TransactionId.invokerWarmup) | ||
| (1 to config.initialCount).foreach { _ => | ||
| prewarmContainer(config.exec, config.memoryLimit, config.reactive.map(_.ttl)) | ||
| } | ||
| } | ||
| case PrewarmQuery => | ||
| sender() ! getPrewarmContainer() | ||
| case EmitMetrics => | ||
| emitMetrics() | ||
|
|
||
|
|
@@ -327,7 +360,7 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
| def adjustPrewarmedContainer(init: Boolean, scheduled: Boolean): Unit = { | ||
| if (scheduled) { | ||
| //on scheduled time, remove expired prewarms | ||
| ContainerPool.removeExpired(poolConfig, prewarmConfig, prewarmedPool).foreach { p => | ||
| ContainerPool.removeExpired(poolConfig, latestPrewarmConfig, prewarmedPool).foreach { p => | ||
| prewarmedPool = prewarmedPool - p | ||
| p ! Remove | ||
| } | ||
|
|
@@ -340,7 +373,7 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
| } | ||
| //fill in missing prewarms (replaces any deletes) | ||
| ContainerPool | ||
| .increasePrewarms(init, scheduled, coldStartCount, prewarmConfig, prewarmedPool, prewarmStartingPool) | ||
| .increasePrewarms(init, scheduled, coldStartCount, latestPrewarmConfig, prewarmedPool, prewarmStartingPool) | ||
| .foreach { c => | ||
| val config = c._1 | ||
| val currentCount = c._2._1 | ||
|
|
@@ -380,7 +413,7 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
|
|
||
| /** this is only for cold start statistics of prewarm configs, e.g. not blackbox or other configs. */ | ||
| def incrementColdStartCount(kind: String, memoryLimit: ByteSize): Unit = { | ||
| prewarmConfig | ||
| latestPrewarmConfig | ||
| .filter { config => | ||
| kind == config.exec.kind && memoryLimit == config.memoryLimit | ||
| } | ||
|
|
@@ -421,7 +454,9 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
|
|
||
| //get the appropriate ttl from prewarm configs | ||
| val ttl = | ||
| prewarmConfig.find(pc => pc.memoryLimit == memory && pc.exec.kind == kind).flatMap(_.reactive.map(_.ttl)) | ||
| latestPrewarmConfig | ||
| .find(pc => pc.memoryLimit == memory && pc.exec.kind == kind) | ||
| .flatMap(_.reactive.map(_.ttl)) | ||
| prewarmContainer(action.exec, memory, ttl) | ||
| (ref, data) | ||
| } | ||
|
|
@@ -434,6 +469,31 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
| busyPool = busyPool - toDelete | ||
| } | ||
|
|
||
| /** | ||
| * get the prewarm container | ||
| * @return | ||
| */ | ||
| def getPrewarmContainer(): ListBuffer[PrewarmContainerData] = { | ||
| val containerDataList = prewarmedPool.values.toList | ||
|
|
||
| var resultList: ListBuffer[PrewarmContainerData] = new ListBuffer[PrewarmContainerData]() | ||
| containerDataList.foreach { prewarmData => | ||
| val isInclude = resultList.filter { resultData => | ||
| prewarmData.kind == resultData.kind && prewarmData.memoryLimit.toMB == resultData.memory | ||
| }.size > 0 | ||
|
|
||
| if (isInclude) { | ||
| var resultData = resultList.filter { resultData => | ||
| prewarmData.kind == resultData.kind && prewarmData.memoryLimit.toMB == resultData.memory | ||
| }.head | ||
| resultData.number += 1 | ||
| } else { | ||
| resultList += PrewarmContainerData(prewarmData.kind, prewarmData.memoryLimit.toMB, 1) | ||
| } | ||
| } | ||
| resultList | ||
| } | ||
|
|
||
| /** | ||
| * Calculate if there is enough free memory within a given pool. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If generate controller/invoker credentials to container's /conf/,
the Standalone Testsrun failed due tolack /conf/ under the build machineOn the other hand,
couchdb credentials is passed via environment variable, so controller/invoker credentials can be passed via environment variable as well.