-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add administrative interface to invoker and controller to reconfigure runtimes #4790
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 => | ||
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. 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) { | ||
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. 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 => | ||
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)) { | ||
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. 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) { | ||
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 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)) | ||
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? 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) | ||
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 | ||
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 Tests
run failed due tolack /conf/ under the build machine
On the other hand,
couchdb credentials is passed via environment variable
, so controller/invoker credentials can be passed via environment variable as well.