Skip to content

Commit 30a3e64

Browse files
committed
Config runtime
Sometimes, admin may want to reinitalize the runtime config depend on the real requirements, e.g. increase some prewarm containers
1 parent 7113b73 commit 30a3e64

File tree

19 files changed

+555
-90
lines changed

19 files changed

+555
-90
lines changed

ansible/group_vars/all

+2
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ controller:
105105
authentication:
106106
spi: "{{ controller_authentication_spi | default('') }}"
107107
loglevel: "{{ controller_loglevel | default(whisk_loglevel) | default('INFO') }}"
108+
username: "{{ controller_username | default('controller.user') }}"
109+
password: "{{ controller_password | default('controller.pass') }}"
108110
entitlement:
109111
spi: "{{ controller_entitlement_spi | default('') }}"
110112
protocol: "{{ controller_protocol | default('https') }}"

ansible/roles/controller/tasks/deploy.yml

+3
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@
203203
"CONFIG_whisk_db_activationsFilterDdoc": "{{ db_whisk_activations_filter_ddoc | default() }}"
204204
"CONFIG_whisk_userEvents_enabled": "{{ user_events | default(false) | lower }}"
205205

206+
"CONFIG_whisk_credentials_controller_username": "{{ controller.username }}"
207+
"CONFIG_whisk_credentials_controller_password": "{{ controller.password }}"
208+
206209
"LIMITS_ACTIONS_INVOKES_PERMINUTE": "{{ limits.invocationsPerMinute }}"
207210
"LIMITS_ACTIONS_INVOKES_CONCURRENT": "{{ limits.concurrentInvocations }}"
208211
"LIMITS_TRIGGERS_FIRES_PERMINUTE": "{{ limits.firesPerMinute }}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.openwhisk.common
19+
20+
case class ControllerCredentials(username: String, password: String)

common/scala/src/main/scala/org/apache/openwhisk/core/WhiskConfig.scala

+2
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,6 @@ object ConfigKeys {
269269
val apacheClientConfig = "whisk.apache-client"
270270

271271
val parameterStorage = "whisk.parameter-storage"
272+
273+
val controllerCredentials = "whisk.credentials.controller"
272274
}

common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala

+27
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,30 @@ object EventMessage extends DefaultJsonProtocol {
429429

430430
def parse(msg: String) = Try(format.read(msg.parseJson))
431431
}
432+
433+
case class RuntimeMessage(runtime: String) extends Message {
434+
override def serialize = RuntimeMessage.serdes.write(this).compactPrint
435+
}
436+
437+
object RuntimeMessage extends DefaultJsonProtocol {
438+
def parse(msg: String) = Try(serdes.read(msg.parseJson))
439+
implicit val serdes = jsonFormat(RuntimeMessage.apply _, "runtime")
440+
}
441+
442+
case class PrewarmContainerData(kind: String, memory: Long, var number: Int) extends Message {
443+
override def serialize: String = PrewarmContainerData.serdes.write(this).compactPrint
444+
}
445+
446+
object PrewarmContainerData extends DefaultJsonProtocol {
447+
implicit val serdes = jsonFormat(PrewarmContainerData.apply _, "kind", "memory", "number")
448+
}
449+
450+
case class PrewarmContainerDataList(items: List[PrewarmContainerData])
451+
452+
object PrewarmContainerDataProtocol extends DefaultJsonProtocol {
453+
implicit val prewarmContainerDataFormat = jsonFormat(PrewarmContainerData.apply _, "kind", "memory", "number")
454+
implicit object prewarmContainerDataListJsonFormat extends RootJsonFormat[PrewarmContainerDataList] {
455+
def read(value: JsValue) = PrewarmContainerDataList(value.convertTo[List[PrewarmContainerData]])
456+
def write(f: PrewarmContainerDataList) = ???
457+
}
458+
}

common/scala/src/main/scala/org/apache/openwhisk/core/entity/ExecManifest.scala

+14
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ protected[core] object ExecManifest {
5555
mf
5656
}
5757

58+
/**
59+
* Reads runtimes manifest from runtime string
60+
*
61+
* @param runtime
62+
* @return the manifest if initialized successfully, or an failure
63+
*/
64+
protected[core] def initialize(runtime: String): Try[Runtimes] = {
65+
val rmc = loadConfigOrThrow[RuntimeManifestConfig](ConfigKeys.runtimes)
66+
val mf = Try(runtime.parseJson.asJsObject).flatMap(runtimes(_, rmc))
67+
var manifest: Option[Runtimes] = None
68+
mf.foreach(m => manifest = Some(m))
69+
mf
70+
}
71+
5872
/**
5973
* Gets existing runtime manifests.
6074
*

core/controller/src/main/scala/org/apache/openwhisk/core/controller/Controller.scala

+64-4
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import akka.Done
2121
import akka.actor.{ActorSystem, CoordinatedShutdown}
2222
import akka.event.Logging.InfoLevel
2323
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
24+
import akka.http.scaladsl.model.{StatusCodes, Uri}
2425
import akka.http.scaladsl.model.StatusCodes._
25-
import akka.http.scaladsl.model.Uri
26+
import akka.http.scaladsl.model.headers.BasicHttpCredentials
2627
import akka.http.scaladsl.server.Route
2728
import akka.stream.ActorMaterializer
2829
import kamon.Kamon
@@ -31,8 +32,15 @@ import pureconfig.generic.auto._
3132
import spray.json.DefaultJsonProtocol._
3233
import spray.json._
3334
import org.apache.openwhisk.common.Https.HttpsConfig
34-
import org.apache.openwhisk.common.{AkkaLogging, ConfigMXBean, Logging, LoggingMarkers, TransactionId}
35-
import org.apache.openwhisk.core.WhiskConfig
35+
import org.apache.openwhisk.common.{
36+
AkkaLogging,
37+
ConfigMXBean,
38+
ControllerCredentials,
39+
Logging,
40+
LoggingMarkers,
41+
TransactionId
42+
}
43+
import org.apache.openwhisk.core.{ConfigKeys, WhiskConfig}
3644
import org.apache.openwhisk.core.connector.MessagingProvider
3745
import org.apache.openwhisk.core.containerpool.logging.LogStoreProvider
3846
import org.apache.openwhisk.core.database.{ActivationStoreProvider, CacheChangeNotification, RemoteCacheInvalidation}
@@ -97,7 +105,7 @@ class Controller(val instance: ControllerInstanceId,
97105
(pathEndOrSingleSlash & get) {
98106
complete(info)
99107
}
100-
} ~ apiV1.routes ~ swagger.swaggerRoutes ~ internalInvokerHealth
108+
} ~ apiV1.routes ~ swagger.swaggerRoutes ~ internalInvokerHealth ~ configRuntime
101109
}
102110

103111
// initialize datastores
@@ -176,6 +184,58 @@ class Controller(val instance: ControllerInstanceId,
176184
LogLimit.config,
177185
runtimes,
178186
List(apiV1.basepath()))
187+
188+
private val controllerCredentials = loadConfigOrThrow[ControllerCredentials](ConfigKeys.controllerCredentials)
189+
190+
/**
191+
* config runtime
192+
*/
193+
private val configRuntime = {
194+
implicit val executionContext = actorSystem.dispatcher
195+
(path("config" / "runtime") & post) {
196+
extractCredentials {
197+
case Some(BasicHttpCredentials(username, password)) =>
198+
if (username == controllerCredentials.username && password == controllerCredentials.password) {
199+
entity(as[String]) { runtime =>
200+
val execManifest = ExecManifest.initialize(runtime)
201+
if (execManifest.isFailure) {
202+
logging.info(this, s"received invalid runtimes manifest")
203+
complete(StatusCodes.BadRequest)
204+
} else {
205+
parameter('limit.?) { limit =>
206+
limit match {
207+
case Some(targetValue) =>
208+
val pattern = """\d+:\d"""
209+
if (targetValue.matches(pattern)) {
210+
val invokerArray = targetValue.split(":")
211+
val beginIndex = invokerArray(0).toInt
212+
val finishIndex = invokerArray(1).toInt
213+
if (finishIndex < beginIndex) {
214+
complete(StatusCodes.BadRequest, "finishIndex can't be less than beginIndex")
215+
} else {
216+
val targetInvokers = (beginIndex to finishIndex).toList
217+
loadBalancer.sendRuntimeToInvokers(runtime, Some(targetInvokers))
218+
logging.info(this, "config runtime request is already sent to target invokers")
219+
complete(StatusCodes.Accepted)
220+
}
221+
} else {
222+
complete(StatusCodes.BadRequest, "limit value can't match [beginIndex:finishIndex]")
223+
}
224+
case None =>
225+
loadBalancer.sendRuntimeToInvokers(runtime, None)
226+
logging.info(this, "config runtime request is already sent to all managed invokers")
227+
complete(StatusCodes.Accepted)
228+
}
229+
}
230+
}
231+
}
232+
} else {
233+
complete(StatusCodes.Unauthorized, "username or password is wrong")
234+
}
235+
case _ => complete(StatusCodes.Unauthorized)
236+
}
237+
}
238+
}
179239
}
180240

181241
/**

core/controller/src/main/scala/org/apache/openwhisk/core/loadBalancer/LoadBalancer.scala

+8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ trait LoadBalancer {
6060
def publish(action: ExecutableWhiskActionMetaData, msg: ActivationMessage)(
6161
implicit transid: TransactionId): Future[Future[Either[ActivationId, WhiskActivation]]]
6262

63+
/**
64+
* send runtime to invokers
65+
*
66+
* @param runtime
67+
* @param targetInvokers
68+
*/
69+
def sendRuntimeToInvokers(runtime: String, targetInvokers: Option[List[Int]]): Unit = {}
70+
6371
/**
6472
* Returns a message indicating the health of the containers and/or container pool in general.
6573
*

core/controller/src/main/scala/org/apache/openwhisk/core/loadBalancer/ShardingContainerPoolBalancer.scala

+17
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import org.apache.openwhisk.spi.SpiLoader
4343
import scala.annotation.tailrec
4444
import scala.concurrent.Future
4545
import scala.concurrent.duration.FiniteDuration
46+
import scala.util.{Failure, Success}
4647

4748
/**
4849
* A loadbalancer that schedules workload based on a hashing-algorithm.
@@ -316,6 +317,22 @@ class ShardingContainerPoolBalancer(
316317
}
317318
}
318319

320+
/** send runtime to invokers*/
321+
override def sendRuntimeToInvokers(runtime: String, targetInvokers: Option[List[Int]]): Unit = {
322+
val runtimeMessage = RuntimeMessage(runtime)
323+
schedulingState.managedInvokers.filter { manageInvoker =>
324+
targetInvokers.getOrElse(schedulingState.managedInvokers.map(_.id.instance)).contains(manageInvoker.id.instance)
325+
} foreach { invokerHealth =>
326+
val topic = s"invoker${invokerHealth.id.toInt}"
327+
messageProducer.send(topic, runtimeMessage).andThen {
328+
case Success(_) =>
329+
logging.info(this, s"Successfully posted runtime to topic $topic")
330+
case Failure(_) =>
331+
logging.error(this, s"Failed posted runtime to topic $topic")
332+
}
333+
}
334+
}
335+
319336
override val invokerPool =
320337
invokerPoolFactory.createInvokerPool(
321338
actorSystem,

core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerPool.scala

+52-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ package org.apache.openwhisk.core.containerpool
2020
import akka.actor.{Actor, ActorRef, ActorRefFactory, Props}
2121
import org.apache.openwhisk.common.MetricEmitter
2222
import org.apache.openwhisk.common.{AkkaLogging, LoggingMarkers, TransactionId}
23-
import org.apache.openwhisk.core.connector.MessageFeed
23+
import org.apache.openwhisk.core.connector.{MessageFeed, PrewarmContainerData}
2424
import org.apache.openwhisk.core.entity._
2525
import org.apache.openwhisk.core.entity.size._
26+
2627
import scala.annotation.tailrec
2728
import scala.collection.immutable
29+
import scala.collection.mutable.ListBuffer
2830
import scala.concurrent.duration._
2931
import scala.util.Try
3032

@@ -34,6 +36,9 @@ case object Free extends WorkerState
3436

3537
case class WorkerData(data: ContainerData, state: WorkerState)
3638

39+
case class PreWarmConfigList(list: List[PrewarmingConfig])
40+
object PrewarmQuery
41+
3742
case object EmitMetrics
3843

3944
/**
@@ -70,6 +75,7 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
7075
var busyPool = immutable.Map.empty[ActorRef, ContainerData]
7176
var prewarmedPool = immutable.Map.empty[ActorRef, ContainerData]
7277
var prewarmStartingPool = immutable.Map.empty[ActorRef, (String, ByteSize)]
78+
var latestPrewarmConfig = prewarmConfig
7379
// If all memory slots are occupied and if there is currently no container to be removed, than the actions will be
7480
// buffered here to keep order of computation.
7581
// Otherwise actions with small memory-limits could block actions with large memory limits.
@@ -279,6 +285,23 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
279285
case RescheduleJob =>
280286
freePool = freePool - sender()
281287
busyPool = busyPool - sender()
288+
case prewarmConfigList: PreWarmConfigList =>
289+
latestPrewarmConfig = prewarmConfigList.list
290+
// Delete prewarmedPool firstly
291+
prewarmedPool foreach { element =>
292+
val actor = element._1
293+
actor ! Remove
294+
prewarmedPool = prewarmedPool - actor
295+
}
296+
prewarmConfigList.list foreach { config =>
297+
logging.info(this, s"add pre-warming ${config.count} ${config.exec.kind} ${config.memoryLimit.toString}")(
298+
TransactionId.invokerWarmup)
299+
(1 to config.count).foreach { _ =>
300+
prewarmContainer(config.exec, config.memoryLimit)
301+
}
302+
}
303+
case PrewarmQuery =>
304+
sender() ! getPrewarmContainer()
282305
case EmitMetrics =>
283306
emitMetrics()
284307
}
@@ -304,7 +327,7 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
304327

305328
/** Install prewarm containers up to the configured requirements for each kind/memory combination. */
306329
def backfillPrewarms(init: Boolean) = {
307-
prewarmConfig.foreach { config =>
330+
latestPrewarmConfig.foreach { config =>
308331
val kind = config.exec.kind
309332
val memory = config.memoryLimit
310333
val currentCount = prewarmedPool.count {
@@ -375,6 +398,33 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
375398
busyPool = busyPool - toDelete
376399
}
377400

401+
/**
402+
* get the prewarm container
403+
* @return
404+
*/
405+
def getPrewarmContainer(): ListBuffer[PrewarmContainerData] = {
406+
val containerDataList = prewarmedPool.values.toList.map { data =>
407+
data.asInstanceOf[PreWarmedData]
408+
}
409+
410+
var resultList: ListBuffer[PrewarmContainerData] = new ListBuffer[PrewarmContainerData]()
411+
containerDataList.foreach { prewarmData =>
412+
val isInclude = resultList.filter { resultData =>
413+
prewarmData.kind == resultData.kind && prewarmData.memoryLimit.toMB == resultData.memory
414+
}.size > 0
415+
416+
if (isInclude) {
417+
var resultData = resultList.filter { resultData =>
418+
prewarmData.kind == resultData.kind && prewarmData.memoryLimit.toMB == resultData.memory
419+
}.head
420+
resultData.number += 1
421+
} else {
422+
resultList += PrewarmContainerData(prewarmData.kind, prewarmData.memoryLimit.toMB, 1)
423+
}
424+
}
425+
resultList
426+
}
427+
378428
/**
379429
* Calculate if there is enough free memory within a given pool.
380430
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.openwhisk.core.invoker
19+
20+
import akka.actor.ActorSystem
21+
import akka.http.scaladsl.server.Route
22+
import org.apache.openwhisk.common.{Logging, TransactionId}
23+
24+
import org.apache.openwhisk.http.BasicRasService
25+
26+
import scala.concurrent.ExecutionContext
27+
28+
/**
29+
* Implements web server to handle certain REST API calls.
30+
*/
31+
class DefaultInvokerServer(val invoker: InvokerCore)(implicit val ec: ExecutionContext,
32+
val actorSystem: ActorSystem,
33+
val logger: Logging)
34+
extends BasicRasService {
35+
36+
override def routes(implicit transid: TransactionId): Route = {
37+
super.routes ~ {
38+
(path("getRuntime") & get) {
39+
invoker.getRuntime()
40+
}
41+
}
42+
}
43+
}
44+
45+
object DefaultInvokerServer extends InvokerServerProvider {
46+
override def instance(
47+
invoker: InvokerCore)(implicit ec: ExecutionContext, actorSystem: ActorSystem, logger: Logging): BasicRasService =
48+
new DefaultInvokerServer(invoker)
49+
}

0 commit comments

Comments
 (0)