Skip to content

Commit 489a2bc

Browse files
rabbahcsantanapr
authored andcommitted
Allow docker pull to be skipped for local docker actions. (#3052)
This commit adds a deployment flag which allows a docker action to be treated as a native image. A native image may eschew a docker pull. It is defined as one that has a prefix matching the docker prefix for managed images. Also added some missing tids.
1 parent 6720d2e commit 489a2bc

10 files changed

Lines changed: 63 additions & 40 deletions

File tree

ansible/environments/docker-machine/group_vars/all

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ config_root_dir: /Users/Shared/wskconf
33
whisk_logs_dir: /Users/Shared/wsklogs
44
docker_registry: ""
55
docker_dns: ""
6+
bypass_pull_for_local_images: true
67

78
# The whisk_api_localhost_name is used to configure nginx to permit vanity URLs for web actions.
89
# It is also used for the SSL certificate generation. For a local deployment, this is typically

ansible/environments/local/group_vars/all

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ config_root_dir: /tmp/wskconf
33
whisk_logs_dir: /tmp/wsklogs
44
docker_registry: ""
55
docker_dns: ""
6+
bypass_pull_for_local_images: true
67

78
db_prefix: whisk_local_
89

ansible/group_vars/all

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ whisk:
2929
# defaultImageTag: the default image tag
3030
# runtimes: set of language runtime families grouped by language (e.g., nodejs, python)
3131
# blackboxes: list of pre-populated docker action images as "name" with optional "prefix" and "tag"
32+
# bypassPullForLocalImages: optional, if true, allow images with a prefix that matches {{ docker.image.prefix }}
33+
# to skip docker pull in invoker even if the image is not part of the blackboxe set
3234
#
3335
runtimesManifest: "{{ runtimes_manifest | default(runtimesManifestDefault) }}"
3436

3537
runtimesManifestDefault:
38+
bypassPullForLocalImages: "{{ bypass_pull_for_local_images | default(false) }}"
3639
defaultImagePrefix: "openwhisk"
3740
defaultImageTag: "latest"
3841
runtimes:

common/scala/src/main/scala/whisk/common/TransactionId.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ object TransactionId {
215215
val loadbalancer = TransactionId(-120) // Loadbalancer thread
216216
val invokerHealth = TransactionId(-121) // Invoker supervision
217217
val controller = TransactionId(-130) // Controller startup
218+
val dbBatcher = TransactionId(-140) // Database batcher
218219

219220
def apply(tid: BigDecimal): TransactionId = {
220221
Try {

common/scala/src/main/scala/whisk/core/database/CouchDbRestStore.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class CouchDbRestStore[DocumentAbstraction <: DocumentSerializer](dbProtocol: St
7474
private val maxOpenDbRequests = system.settings.config.getInt("akka.http.host-connection-pool.max-connections") / 2
7575

7676
private val batcher: Batcher[JsObject, Either[ArtifactStoreException, DocInfo]] =
77-
new Batcher(500, maxOpenDbRequests)(put(_)(TransactionId.unknown))
77+
new Batcher(500, maxOpenDbRequests)(put(_)(TransactionId.dbBatcher))
7878

7979
override protected[database] def put(d: DocumentAbstraction)(implicit transid: TransactionId): Future[DocInfo] = {
8080
val asJson = d.toDocumentRecord

common/scala/src/main/scala/whisk/core/entity/Exec.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ protected[core] object Exec extends ArgNormalizer[Exec] with DefaultJsonProtocol
266266
s"if defined, 'code' must a string defined in 'exec' for '${Exec.BLACKBOX}' actions")
267267
case None => None
268268
}
269-
val native = execManifests.blackboxImages.contains(image)
269+
val native = execManifests.skipDockerPull(image)
270270
BlackBoxExec(image, code, optMainField, native)
271271

272272
case _ =>
@@ -384,8 +384,7 @@ protected[core] object ExecMetaDataBase extends ArgNormalizer[ExecMetaDataBase]
384384
throw new DeserializationException(
385385
s"'image' must be a string defined in 'exec' for '${Exec.BLACKBOX}' actions")
386386
}
387-
388-
val native = execManifests.blackboxImages.contains(image)
387+
val native = execManifests.skipDockerPull(image)
389388
BlackBoxExecMetaData(native)
390389

391390
case _ =>

common/scala/src/main/scala/whisk/core/entity/ExecManifest.scala

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package whisk.core.entity
1919

20-
import scala.util.{Failure, Success, Try}
20+
import scala.util.{Failure, Try}
2121
import spray.json._
2222
import spray.json.DefaultJsonProtocol._
2323
import whisk.core.WhiskConfig
@@ -41,15 +41,13 @@ protected[core] object ExecManifest {
4141
* singleton Runtime instance.
4242
*
4343
* @param config a valid configuration
44-
* @param reinit re-initialize singleton iff true
45-
* @return the manifest if initialized successfully, or if previously initialized
44+
* @param localDockerImagePrefix optional local docker prefix, permitting images matching prefix to bypass docker pull
45+
* @return the manifest if initialized successfully, or an failure
4646
*/
47-
protected[core] def initialize(config: WhiskConfig, reinit: Boolean = false): Try[Runtimes] = {
48-
if (manifest.isEmpty || reinit) {
49-
val mf = Try(config.runtimesManifest.parseJson.asJsObject).flatMap(runtimes(_))
50-
mf.foreach(m => manifest = Some(m))
51-
mf
52-
} else Success(manifest.get)
47+
protected[core] def initialize(config: WhiskConfig, localDockerImagePrefix: Option[String] = None): Try[Runtimes] = {
48+
val mf = Try(config.runtimesManifest.parseJson.asJsObject).flatMap(runtimes(_, localDockerImagePrefix))
49+
mf.foreach(m => manifest = Some(m))
50+
mf
5351
}
5452

5553
/**
@@ -71,26 +69,34 @@ protected[core] object ExecManifest {
7169
* @param config a configuration object as JSON
7270
* @return Runtimes instance
7371
*/
74-
protected[entity] def runtimes(config: JsObject): Try[Runtimes] = Try {
72+
protected[entity] def runtimes(config: JsObject, localDockerImagePrefix: Option[String] = None): Try[Runtimes] = Try {
7573
val prefix = config.fields.get("defaultImagePrefix").map(_.convertTo[String])
7674
val tag = config.fields.get("defaultImageTag").map(_.convertTo[String])
77-
val runtimes = config
78-
.fields("runtimes")
79-
.convertTo[Map[String, Set[RuntimeManifest]]]
80-
.map {
75+
76+
val runtimes = config.fields
77+
.get("runtimes")
78+
.map(_.convertTo[Map[String, Set[RuntimeManifest]]].map {
8179
case (name, versions) =>
8280
RuntimeFamily(name, versions.map { mf =>
8381
val img = ImageName(mf.image.name, mf.image.prefix.orElse(prefix), mf.image.tag.orElse(tag))
8482
mf.copy(image = img)
8583
})
86-
}
87-
.toSet
84+
}.toSet)
85+
8886
val blackbox = config.fields
8987
.get("blackboxes")
9088
.map(_.convertTo[Set[ImageName]].map { image =>
9189
ImageName(image.name, image.prefix.orElse(prefix), image.tag.orElse(tag))
9290
})
93-
Runtimes(runtimes, blackbox.getOrElse(Set.empty))
91+
92+
val bypassPullForLocalImages = config.fields
93+
.get("bypassPullForLocalImages")
94+
.map(_.convertTo[Boolean])
95+
.filter(identity)
96+
.flatMap(_ => localDockerImagePrefix)
97+
.map(_.trim)
98+
99+
Runtimes(runtimes.getOrElse(Set.empty), blackbox.getOrElse(Set.empty), bypassPullForLocalImages)
94100
}
95101

96102
/**
@@ -215,10 +221,17 @@ protected[core] object ExecManifest {
215221
*
216222
* @param set of supported runtime families
217223
*/
218-
protected[core] case class Runtimes(runtimes: Set[RuntimeFamily], blackboxImages: Set[ImageName]) {
224+
protected[core] case class Runtimes(runtimes: Set[RuntimeFamily],
225+
blackboxImages: Set[ImageName],
226+
bypassPullForLocalImages: Option[String]) {
219227

220228
val knownContainerRuntimes: Set[String] = runtimes.flatMap(_.versions.map(_.kind))
221229

230+
def skipDockerPull(image: ImageName): Boolean = {
231+
blackboxImages.contains(image) ||
232+
image.prefix.flatMap(p => bypassPullForLocalImages.map(_ == p)).getOrElse(false)
233+
}
234+
222235
def toJson: JsObject = {
223236
runtimes
224237
.map { family =>

core/invoker/src/main/scala/whisk/core/containerpool/ContainerPool.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import akka.actor.ActorRefFactory
2525
import akka.actor.Props
2626
import whisk.common.AkkaLogging
2727

28+
import whisk.common.TransactionId
2829
import whisk.core.entity.ByteSize
2930
import whisk.core.entity.CodeExec
3031
import whisk.core.entity.EntityName
@@ -72,7 +73,7 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
7273
var prewarmedPool = immutable.Map.empty[ActorRef, ContainerData]
7374

7475
prewarmConfig.foreach { config =>
75-
logging.info(this, s"pre-warming ${config.count} ${config.exec.kind} containers")
76+
logging.info(this, s"pre-warming ${config.count} ${config.exec.kind} containers")(TransactionId.invokerWarmup)
7677
(1 to config.count).foreach { _ =>
7778
prewarmContainer(config.exec, config.memoryLimit)
7879
}

core/invoker/src/main/scala/whisk/core/invoker/Invoker.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ object Invoker {
101101
abort("Bad configuration, cannot start.")
102102
}
103103

104-
val execManifest = ExecManifest.initialize(config)
104+
val execManifest = ExecManifest.initialize(config, localDockerImagePrefix = Some(config.dockerImagePrefix))
105105
if (execManifest.isFailure) {
106106
logger.error(this, s"Invalid runtimes manifest: ${execManifest.failed.get}")
107107
abort("Bad configuration, cannot start.")

tests/src/test/scala/whisk/core/entity/test/ExecManifestTests.scala

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,18 @@
1818
package whisk.core.entity.test
1919

2020
import java.io.{BufferedWriter, File, FileWriter}
21-
import java.util.NoSuchElementException
2221

23-
import scala.util.{Success}
22+
import common.{StreamLogging, WskActorSystem}
2423
import org.junit.runner.RunWith
25-
import org.scalatest.FlatSpec
26-
import org.scalatest.Matchers
24+
import org.scalatest.{FlatSpec, Matchers}
2725
import org.scalatest.junit.JUnitRunner
28-
import spray.json._
2926
import spray.json.DefaultJsonProtocol._
27+
import spray.json._
3028
import whisk.core.WhiskConfig
3129
import whisk.core.entity.ExecManifest
3230
import whisk.core.entity.ExecManifest._
33-
import common.StreamLogging
34-
import common.WskActorSystem
31+
32+
import scala.util.Success
3533

3634
@RunWith(classOf[JUnitRunner])
3735
class ExecManifestTests extends FlatSpec with WskActorSystem with StreamLogging with Matchers {
@@ -112,7 +110,10 @@ class ExecManifestTests extends FlatSpec with WskActorSystem with StreamLogging
112110

113111
val mf = JsObject("runtimes" -> JsObject(), "blackboxes" -> imgs.toJson)
114112
val runtimes = ExecManifest.runtimes(mf).get
113+
115114
runtimes.blackboxImages shouldBe imgs
115+
imgs.foreach(img => runtimes.skipDockerPull(img) shouldBe true)
116+
runtimes.skipDockerPull(ImageName("???", Some("bbb"))) shouldBe false
116117
}
117118

118119
it should "read a valid configuration with blackbox images, default prefix and tag" in {
@@ -137,6 +138,8 @@ class ExecManifestTests extends FlatSpec with WskActorSystem with StreamLogging
137138
ImageName("???", Some("pre"), Some("ttt")))
138139
}
139140

141+
runtimes.skipDockerPull(ImageName("???", Some("pre"), Some("test"))) shouldBe true
142+
runtimes.skipDockerPull(ImageName("???", Some("bbb"), Some("test"))) shouldBe false
140143
}
141144

142145
it should "reject runtimes with multiple defaults" in {
@@ -175,21 +178,22 @@ class ExecManifestTests extends FlatSpec with WskActorSystem with StreamLogging
175178
}
176179
}
177180

178-
it should "throw an error when configured manifest is a valid JSON, but with a missing key" in {
179-
val config_manifest = """{"nodejs":[{"kind":"nodejs:6","default":true,"image":{"name":"nodejs6action"}}]}"""
181+
it should "indicate image is local if it matches deployment docker prefix" in {
182+
val config_manifest = """{"bypassPullForLocalImages":true}"""
180183
val file = File.createTempFile("cxt", ".txt")
181184
file.deleteOnExit()
182185

183186
val bw = new BufferedWriter(new FileWriter(file))
184-
bw.write("runtimes.manifest=" + config_manifest + "\n")
187+
bw.write(WhiskConfig.runtimesManifest + s"=$config_manifest\n")
185188
bw.close()
186189

187-
val result = ExecManifest.initialize(new WhiskConfig(Map("runtimes.manifest" -> null), Set(), file), true)
188-
189-
result should be a 'failure
190+
val props = Map(WhiskConfig.runtimesManifest -> null)
191+
val manifest =
192+
ExecManifest.initialize(new WhiskConfig(props, Set(), file), localDockerImagePrefix = Some("localpre"))
193+
manifest should be a 'success
190194

191-
the[NoSuchElementException] thrownBy {
192-
result.get
193-
} should have message ("key not found: runtimes")
195+
manifest.get.skipDockerPull(ImageName(prefix = Some("x"), name = "y")) shouldBe false
196+
manifest.get.skipDockerPull(ImageName(prefix = Some("localpre"), name = "y")) shouldBe true
194197
}
198+
195199
}

0 commit comments

Comments
 (0)