@@ -46,124 +46,127 @@ import whisk.core.entity.Exec
4646 * container as blocking method calls of this interface.
4747 */
4848trait ActionContainer {
49- def init (value : JsValue ): (Int , Option [JsObject ])
50- def run (value : JsValue ): (Int , Option [JsObject ])
49+ def init (value : JsValue ): (Int , Option [JsObject ])
50+ def run (value : JsValue ): (Int , Option [JsObject ])
5151}
5252
5353trait ActionProxyContainerTestUtils extends FlatSpec with Matchers {
54- import ActionContainer .{ filterSentinel , sentinel }
55-
56- def initPayload (code : String , main : String = " main" ) = {
57- JsObject (" value" -> JsObject (
58- " code" -> { if (code != null ) JsString (code) else JsNull },
59- " main" -> JsString (main),
60- " binary" -> JsBoolean (Exec .isBinaryCode(code))))
54+ import ActionContainer .{filterSentinel , sentinel }
55+
56+ def initPayload (code : String , main : String = " main" ) = {
57+ JsObject (
58+ " value" -> JsObject (
59+ " code" -> { if (code != null ) JsString (code) else JsNull },
60+ " main" -> JsString (main),
61+ " binary" -> JsBoolean (Exec .isBinaryCode(code))))
62+ }
63+
64+ def runPayload (args : JsValue , other : Option [JsObject ] = None ) = {
65+ JsObject (Map (" value" -> args) ++ (other map { _.fields } getOrElse Map ()))
66+ }
67+
68+ def checkStreams (out : String , err : String , additionalCheck : (String , String ) => Unit , sentinelCount : Int = 1 ) = {
69+ withClue(" expected number of stdout sentinels" ) {
70+ sentinelCount shouldBe StringUtils .countMatches(out, sentinel)
6171 }
62-
63- def runPayload (args : JsValue , other : Option [JsObject ] = None ) = {
64- JsObject (Map (" value" -> args) ++ (other map { _.fields } getOrElse Map ()))
72+ withClue(" expected number of stderr sentinels" ) {
73+ sentinelCount shouldBe StringUtils .countMatches(err, sentinel)
6574 }
6675
67- def checkStreams (out : String , err : String , additionalCheck : (String , String ) => Unit , sentinelCount : Int = 1 ) = {
68- withClue(" expected number of stdout sentinels" ) {
69- sentinelCount shouldBe StringUtils .countMatches(out, sentinel)
70- }
71- withClue(" expected number of stderr sentinels" ) {
72- sentinelCount shouldBe StringUtils .countMatches(err, sentinel)
73- }
74-
75- val (o, e) = (filterSentinel(out), filterSentinel(err))
76- o should not include (sentinel)
77- e should not include (sentinel)
78- additionalCheck(o, e)
79- }
76+ val (o, e) = (filterSentinel(out), filterSentinel(err))
77+ o should not include (sentinel)
78+ e should not include (sentinel)
79+ additionalCheck(o, e)
80+ }
8081}
8182
8283object ActionContainer {
83- private lazy val dockerBin : String = {
84- List (" /usr/bin/docker" , " /usr/local/bin/docker" ).find { bin =>
85- new File (bin).isFile()
86- }.getOrElse(??? ) // This fails if the docker binary couldn't be located.
84+ private lazy val dockerBin : String = {
85+ List (" /usr/bin/docker" , " /usr/local/bin/docker" )
86+ .find { bin =>
87+ new File (bin).isFile()
88+ }
89+ .getOrElse(??? ) // This fails if the docker binary couldn't be located.
90+ }
91+
92+ private lazy val dockerCmd : String = {
93+ val hostStr = if (WhiskProperties .onMacOSX()) {
94+ s " --host tcp:// ${WhiskProperties .getMainDockerEndpoint()} "
95+ } else {
96+ " "
8797 }
88-
89- private lazy val dockerCmd : String = {
90- val hostStr = if (WhiskProperties .onMacOSX()) {
91- s " --host tcp:// ${WhiskProperties .getMainDockerEndpoint()} "
92- } else {
93- " "
94- }
95- s " $dockerBin $hostStr"
98+ s " $dockerBin $hostStr"
99+ }
100+
101+ private def docker (command : String ): String = s " $dockerCmd $command"
102+
103+ // Runs a process asynchronously. Returns a future with (exitCode,stdout,stderr)
104+ private def proc (cmd : String ): Future [(Int , String , String )] = Future {
105+ blocking {
106+ val out = new ByteArrayOutputStream
107+ val err = new ByteArrayOutputStream
108+ val outW = new PrintWriter (out)
109+ val errW = new PrintWriter (err)
110+ val v = cmd ! (ProcessLogger (outW.println, errW.println))
111+ outW.close()
112+ errW.close()
113+ (v, out.toString, err.toString)
96114 }
97-
98- private def docker (command : String ): String = s " $dockerCmd $command"
99-
100- // Runs a process asynchronously. Returns a future with (exitCode,stdout,stderr)
101- private def proc (cmd : String ): Future [(Int , String , String )] = Future {
102- blocking {
103- val out = new ByteArrayOutputStream
104- val err = new ByteArrayOutputStream
105- val outW = new PrintWriter (out)
106- val errW = new PrintWriter (err)
107- val v = cmd ! (ProcessLogger (outW.println, errW.println))
108- outW.close()
109- errW.close()
110- (v, out.toString, err.toString)
111- }
115+ }
116+
117+ // Tying it all together, we have a method that runs docker, waits for
118+ // completion for some time then returns the exit code, the output stream
119+ // and the error stream.
120+ private def awaitDocker (cmd : String , t : Duration ): (Int , String , String ) = {
121+ Await .result(proc(docker(cmd)), t)
122+ }
123+
124+ // Filters out the sentinel markers inserted by the container (see relevant private code in Invoker.scala)
125+ val sentinel = " XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX"
126+ def filterSentinel (str : String ) = str.replaceAll(sentinel, " " ).trim
127+
128+ def withContainer (imageName : String , environment : Map [String , String ] = Map .empty)(code : ActionContainer => Unit )(
129+ implicit actorSystem : ActorSystem ): (String , String ) = {
130+ val rand = { val r = Random .nextInt; if (r < 0 ) - r else r }
131+ val name = imageName.toLowerCase.replaceAll(""" [^a-z]""" , " " ) + rand
132+ val envArgs = environment.toSeq.map {
133+ case (k, v) => s " -e ${k}= ${v}"
134+ } mkString (" " )
135+
136+ // We create the container...
137+ val runOut = awaitDocker(s " run --name $name $envArgs -d $imageName" , 10 seconds)
138+ assert(runOut._1 == 0 , " 'docker run' did not exit with 0: " + runOut)
139+
140+ // ...find out its IP address...
141+ val ipOut = awaitDocker(s """ inspect --format '{{.NetworkSettings.IPAddress}}' $name""" , 10 seconds)
142+ assert(ipOut._1 == 0 , " 'docker inspect did not exit with 0" )
143+ val ip = ipOut._2.replaceAll(""" [^0-9.]""" , " " )
144+
145+ // ...we create an instance of the mock container interface...
146+ val mock = new ActionContainer {
147+ def init (value : JsValue ) = syncPost(ip, 8080 , " /init" , value)
148+ def run (value : JsValue ) = syncPost(ip, 8080 , " /run" , value)
112149 }
113150
114- // Tying it all together, we have a method that runs docker, waits for
115- // completion for some time then returns the exit code, the output stream
116- // and the error stream.
117- private def awaitDocker (cmd : String , t : Duration ): (Int , String , String ) = {
118- Await .result(proc(docker(cmd)), t)
151+ try {
152+ // ...and finally run the code with it.
153+ code(mock)
154+ // I'm told this is good for the logs.
155+ Thread .sleep(100 )
156+ val (_, out, err) = awaitDocker(s " logs $name" , 10 seconds)
157+ (out, err)
158+ } finally {
159+ awaitDocker(s " kill $name" , 10 seconds)
160+ awaitDocker(s " rm $name" , 10 seconds)
119161 }
162+ }
120163
121- // Filters out the sentinel markers inserted by the container (see relevant private code in Invoker.scala)
122- val sentinel = " XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX"
123- def filterSentinel (str : String ) = str.replaceAll(sentinel, " " ).trim
124-
125- def withContainer (imageName : String , environment : Map [String , String ] = Map .empty)(
126- code : ActionContainer => Unit )(implicit actorSystem : ActorSystem ): (String , String ) = {
127- val rand = { val r = Random .nextInt; if (r < 0 ) - r else r }
128- val name = imageName.toLowerCase.replaceAll(""" [^a-z]""" , " " ) + rand
129- val envArgs = environment.toSeq.map {
130- case (k, v) => s " -e ${k}= ${v}"
131- } mkString (" " )
132-
133- // We create the container...
134- val runOut = awaitDocker(s " run --name $name $envArgs -d $imageName" , 10 seconds)
135- assert(runOut._1 == 0 , " 'docker run' did not exit with 0: " + runOut)
136-
137- // ...find out its IP address...
138- val ipOut = awaitDocker(s """ inspect --format '{{.NetworkSettings.IPAddress}}' $name""" , 10 seconds)
139- assert(ipOut._1 == 0 , " 'docker inspect did not exit with 0" )
140- val ip = ipOut._2.replaceAll(""" [^0-9.]""" , " " )
141-
142- // ...we create an instance of the mock container interface...
143- val mock = new ActionContainer {
144- def init (value : JsValue ) = syncPost(ip, 8080 , " /init" , value)
145- def run (value : JsValue ) = syncPost(ip, 8080 , " /run" , value)
146- }
147-
148- try {
149- // ...and finally run the code with it.
150- code(mock)
151- // I'm told this is good for the logs.
152- Thread .sleep(100 )
153- val (_, out, err) = awaitDocker(s " logs $name" , 10 seconds)
154- (out, err)
155- } finally {
156- awaitDocker(s " kill $name" , 10 seconds)
157- awaitDocker(s " rm $name" , 10 seconds)
158- }
159- }
160-
161- private def syncPost (host : String , port : Int , endPoint : String , content : JsValue ): (Int , Option [JsObject ]) = {
162- whisk.core.containerpool.docker.HttpUtils .post(host, port, endPoint, content)
163- }
164+ private def syncPost (host : String , port : Int , endPoint : String , content : JsValue ): (Int , Option [JsObject ]) = {
165+ whisk.core.containerpool.docker.HttpUtils .post(host, port, endPoint, content)
166+ }
164167
165- private class ActionContainerImpl () extends ActionContainer {
166- override def init (value : JsValue ) = ???
167- override def run (value : JsValue ) = ???
168- }
168+ private class ActionContainerImpl () extends ActionContainer {
169+ override def init (value : JsValue ) = ???
170+ override def run (value : JsValue ) = ???
171+ }
169172}
0 commit comments