Fix checkstyle
[ovsdb.git] / utils / ovsdb-it-utils / src / main / java / org / opendaylight / ovsdb / utils / ovsdb / it / utils / DockerOvs.java
1 /*
2  * Copyright © 2016, 2017 Red Hat, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.ovsdb.utils.ovsdb.it.utils;
9
10 import static org.junit.Assert.assertNotNull;
11 import static org.junit.Assert.fail;
12 import static org.ops4j.pax.exam.CoreOptions.propagateSystemProperties;
13
14 import com.esotericsoftware.yamlbeans.YamlException;
15 import com.esotericsoftware.yamlbeans.YamlReader;
16 import java.io.File;
17 import java.io.FileInputStream;
18 import java.io.FileNotFoundException;
19 import java.io.FileOutputStream;
20 import java.io.IOException;
21 import java.io.InputStreamReader;
22 import java.io.OutputStreamWriter;
23 import java.io.Reader;
24 import java.io.Writer;
25 import java.net.InetSocketAddress;
26 import java.net.URL;
27 import java.nio.ByteBuffer;
28 import java.nio.channels.ClosedByInterruptException;
29 import java.nio.channels.SocketChannel;
30 import java.nio.charset.Charset;
31 import java.nio.charset.CharsetDecoder;
32 import java.nio.charset.StandardCharsets;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Properties;
39 import java.util.concurrent.atomic.AtomicInteger;
40 import org.ops4j.pax.exam.Option;
41 import org.osgi.framework.Bundle;
42 import org.osgi.framework.FrameworkUtil;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47  * Run OVS(s) using docker-compose for use in integration tests.
48  * For example,
49  * <pre>
50  * try(DockerOvs ovs = new DockerOvs()) {
51  *      ConnectionInfo connectionInfo = SouthboundUtils.getConnectionInfo(
52  *                               ovs.getOvsdbAddress(0), ovs.getOvsdbPort(0));
53  *      ...
54  *       nodeInfo.disconnect();
55  *
56  * } catch (Exception e) {
57  * ...
58  * </pre>
59  * <b>
60  * Nota bene, DockerOvs will check whether or not docker-compose command requires "sudo"
61  * to run. However, if it does require sudo, it must be configured to not prompt for a
62  * password ("NOPASSWD: ALL" is the sudoers file).
63  * </b>
64  * DockerOvs loads its docker-compose yaml files from inside the ovsdb-it-utils bundle
65  * at the path META-INF/docker-compose-files/. Currently, a single yaml file is used,
66  * "docker-ovs-2.5.1.yml." DockerOvs does support docker-compose files that
67  * launch more than one docker image, more on this later. DockerOvs will wait for OVS
68  * to accept OVSDB connections.
69  * Any docker-compose file must have a port mapping.
70  *
71  * <p>
72  * The following explains how system properties are used to configure DockerOvs
73  * <pre>
74  *  private static String ENV_USAGE =
75  *  "-Ddocker.run - explicitly configure whether or not DockerOvs should run docker-compose\n" +
76  *  "-Dovsdbserver.ipaddress - specify IP address of ovsdb server - implies -Ddocker.run=false\n" +
77  *  "-Dovsdbserver.port - specify the port of the ovsdb server - required with -Dovsdbserver.ipaddress\n" +
78  *  "-Ddocker.compose.file - docker compose file in META-INF/docker-compose-files/.
79  *      If not specified, default file is used\n" +
80  *  "-Dovsdb.userspace.enabled - true when Ovs is running in user space (usually the case with docker)\n" +
81  *  "-Dovsdb.controller.address - IP address of the controller (usually the docker0 interface with docker)\n" +
82  *  "To auto-run Ovs and connect actively:\n" +
83  *  " -Dovsdb.controller.address=x.x.x.x -Dovsdb.userspace.enabled=yes [-Ddocker.compose.file=ffff]\n" +
84  *  "To auto-run Ovs and connect passively:\n" +
85  *  " -Dovsdbserver.connection=passive -Dovsdb.controller.address=x.x.x.x
86  *    -Dovsdb.userspace.enabled=yes [-Ddocker.compose.file=ffff]\n" +
87  *  "To actively connect to a running Ovs:\n" +
88  *  " -Dovsdbserver.ipaddress=x.x.x.x -Dovsdbserver.port=6641 -Dovsdb.controller.address=y.y.y.y\n" +
89  *  "To passively connect to a running Ovs:\n" +
90  *  " -Dovsdbserver.connection=passive -Ddocker.run=false\n";
91  * </pre>
92  * <strong>Note:</strong> Ommiting the environment variable ovsdb.controller.address will cause DockerOvs to use a
93  * predefined docker network called "odl". This network's subnet must be 172.99.0.0/16 and its gateway must be defined
94  * as 172.99.0.254. If the "odl" network is not present, DockerOvs will create it for you.
95  * When DockerOvs does not run docker-compose getOvsdbAddress and getOvsdbPort return the address and port specified in
96  * the system properties.
97  */
98 public final class DockerOvs implements AutoCloseable {
99     private static String ENV_USAGE = "Usage:\n"
100         + "-Ddocker.run - explicitly configure whether or not DockerOvs should run docker-compose\n"
101         + "-Dovsdbserver.ipaddress - specify IP address of ovsdb server - implies -Ddocker.run=false\n"
102         + "-Dovsdbserver.port - specify the port of the ovsdb server - required with -Dovsdbserver.ipaddress\n"
103         + "-Ddocker.compose.file - docker compose file in META-INF/docker-compose-files/. "
104         + "If not specified, default file is used\n"
105         + "-Dovsdb.userspace.enabled - true when Ovs is running in user space (usually the case with docker)\n"
106         + "-Dovsdb.controller.address - IP address of the controller (usually the docker0 interface with docker)\n"
107         + "To auto-run Ovs and connect actively:\n"
108         + " -Dovsdb.controller.address=x.x.x.x -Dovsdb.userspace.enabled=yes <-Ddocker.compose.file=ffff>\n"
109         + "To auto-run Ovs and connect passively:\n"
110         + " -Dovsdbserver.connection=passive -Dovsdb.controller.address=x.x.x.x -Dovsdb.userspace.enabled=yes "
111         + "<-Ddocker.compose.file=ffff>\n"
112         + "To actively connect to a running Ovs:\n"
113         + " -Dovsdbserver.ipaddress=x.x.x.x -Dovsdbserver.port=6641 -Dovsdb.controller.address=y.y.y.y\n"
114         + "To passively connect to a running Ovs:\n" + " -Dovsdbserver.connection=passive -Ddocker.run=false\n";
115
116     private static final Logger LOG = LoggerFactory.getLogger(DockerOvs.class);
117     private static final String DEFAULT_DOCKER_FILE = "ovs-2.5.0-hwvtep.yml";
118     private static final String DOCKER_FILE_PATH = "META-INF/docker-compose-files/";
119     private static final int COMPOSE_FILE_IDX = 3;
120     private static final int COMPOSE_FILE_IDX_NO_SUDO = 2;
121     private static final String DEFAULT_OVSDB_HOST = "127.0.0.1";
122     private static final String ODL_NET_GATEWAY = "172.99.0.254";
123
124     private String[] psCmd = {"sudo", "docker-compose", "-f", null, "ps"};
125     private String[] psCmdNoSudo = {"docker-compose", "-f", null, "ps"};
126     private String[] upCmd = {"sudo", "docker-compose", "-f", null, "up", "-d", "--force-recreate"};
127     private String[] downCmd = {"sudo", "docker-compose", "-f", null, "stop"};
128     private String[] execCmd = {"sudo", "docker-compose", "-f", null, "exec", null};
129
130     private final String[] dockerPsCmdNoSudo = {"docker", "ps"};
131     private final String[] dockerPsCmd = {"sudo", "docker", "ps"};
132     private String[] netInspectCmd = {"sudo", "docker", "network", "inspect", "odl"};
133     private String[] netCreateCmd = {"sudo", "docker", "network", "create",
134                                      "--subnet=172.99.0.0/16", "--gateway=172.99.0.254", "odl"};
135
136     private File tmpDockerComposeFile;
137     boolean isRunning;
138     private String envServerAddress;
139     private String envServerPort;
140     private String envDockerComposeFile;
141     private String envDockerWaitForPing;
142     private boolean runDocker;
143     private boolean createOdlNetwork;
144
145     private static final class DockerComposeServiceInfo {
146         String name;
147         String port;
148     }
149
150     private final Map<String, DockerComposeServiceInfo> dockerComposeServices = new HashMap<>();
151
152     /**
153      * Get the array of system properties as pax exam Option objects for use in pax exam
154      * unit tests with Configuration annotation.
155      * @return List of Option objects
156      */
157     public static Option[] getSysPropOptions() {
158         return new Option[] {
159                 propagateSystemProperties(ItConstants.SERVER_IPADDRESS,
160                                             ItConstants.SERVER_PORT,
161                                             ItConstants.CONNECTION_TYPE,
162                                             ItConstants.CONTROLLER_IPADDRESS,
163                                             ItConstants.USERSPACE_ENABLED,
164                                             ItConstants.DOCKER_COMPOSE_FILE_NAME,
165                                             ItConstants.DOCKER_RUN,
166                                             ItConstants.DOCKER_WAIT_FOR_PING_SECS,
167                                             ItConstants.DOCKER_VENV_WS)
168         };
169     }
170
171     /**
172      * Bring up all docker images in the default docker-compose file.
173      * @throws IOException if something goes wrong on the IO end
174      * @throws InterruptedException If this thread is interrupted
175      */
176     public DockerOvs() throws IOException, InterruptedException {
177         this(DEFAULT_DOCKER_FILE);
178     }
179
180     /**
181      * Bring up all docker images in the provided docker-compose file under "META-INF/docker-compose-files/".
182      * @param yamlFileName Just the file name
183      * @throws IOException if something goes wrong on the IO end
184      * @throws InterruptedException If this thread is interrupted
185      */
186     public DockerOvs(String yamlFileName) throws IOException, InterruptedException {
187         configureFromEnv();
188
189         if (!runDocker) {
190             LOG.info("DockerOvs.DockerOvs: Not running docker, -D{} specified", ItConstants.SERVER_IPADDRESS);
191             return;
192         }
193
194         tmpDockerComposeFile = createTempDockerComposeFile(yamlFileName);
195         buildDockerCommands();
196         parseDockerComposeYaml();
197
198         isRunning = false;
199         networkUp();
200         //We run this for A LONG TIME since on the first run docker must download the
201         //image from docker hub. In experience it takes significantly less than this
202         //even when downloading the image. Once the image is downloaded this command
203         //runs like that <snaps fingers>
204         ProcUtils.runProcess(60000, upCmd);
205         isRunning = true;
206         int waitSeconds = Integer.parseInt(envDockerWaitForPing);
207         waitForOvsdbServers(waitSeconds * 1000L);
208     }
209
210     private void setupEnvForDockerCompose(String venvWs) {
211         String dockerCompose = venvWs + "/venv/bin/docker-compose";
212
213         psCmd = new String[]{"sudo", dockerCompose, "-f", null, "ps"};
214         psCmdNoSudo = new String[]{dockerCompose, "-f", null, "ps"};
215         upCmd = new String[]{"sudo", dockerCompose, "-f", null, "up", "-d", "--force-recreate"};
216         downCmd = new String[]{"sudo", dockerCompose, "-f", null, "stop"};
217         execCmd = new String[]{"sudo", dockerCompose, "-f", null, "exec", null};
218     }
219
220     /**
221      * Pull required configuration from System.getProperties() and validate we have what we need.
222      * Note: Note that there is some minor complexity in how this class is configured using System
223      * properties. This stems from the fact that we want to preserve the meaning of these properties
224      * prior to the introduction of this class. See the ENV_USAGE variable for details.
225      */
226     private void configureFromEnv() {
227         Properties env = System.getProperties();
228         envServerAddress = env.getProperty(ItConstants.SERVER_IPADDRESS);
229         envServerPort = env.getProperty(ItConstants.SERVER_PORT);
230         envDockerWaitForPing = env.getProperty(ItConstants.DOCKER_WAIT_FOR_PING_SECS, "10");
231         createOdlNetwork = env.getProperty(ItConstants.CONTROLLER_IPADDRESS) == null;
232         String envRunDocker = env.getProperty(ItConstants.DOCKER_RUN);
233         String dockerFile = env.getProperty(ItConstants.DOCKER_COMPOSE_FILE_NAME);
234         String venvWs = env.getProperty(ItConstants.DOCKER_VENV_WS);
235         envDockerComposeFile = DOCKER_FILE_PATH + (null == dockerFile ? DEFAULT_DOCKER_FILE : dockerFile);
236
237         //Are we running docker? If we specified docker.run, that's the answer. Otherwise, if there is a server
238         //address we assume docker is already running
239         runDocker = envRunDocker != null ? Boolean.parseBoolean(envRunDocker) : envServerAddress == null;
240
241         //Should we run in a virtual environment? Needed for jenkins and docker-compose
242         if (venvWs != null && !venvWs.isEmpty()) {
243             LOG.info("Setting up virtual environment for docker compose");
244             setupEnvForDockerCompose(venvWs);
245         }
246
247         if (runDocker) {
248             return;
249         }
250
251         String connType = env.getProperty(ItConstants.CONNECTION_TYPE, ItConstants.CONNECTION_TYPE_ACTIVE);
252         if (connType.equals(ItConstants.CONNECTION_TYPE_PASSIVE)) {
253             return;
254         }
255
256         //At this point we know we're not running docker and the conn type is active - make sure we have what we need
257         //If we have a server address than we require a port too as those
258         //are returned in getOvsdbPort() and getOvsdbAddress()
259         assertNotNull("Attempt to connect to previous running ovs but missing -Dovsdbserver.ipaddress\n"
260                                                                                     + ENV_USAGE, envServerAddress);
261         assertNotNull("Attempt to connect to previous running ovs but missing -Dovsdbserver.port\n"
262                 + ENV_USAGE, envServerPort);
263     }
264
265     /**
266      * Verify and build the docker and docker-compose commands we will be running. This function adds the docker-compose
267      * file to the command lines and also checks (and adjusts the command line) as to whether sudo is required. This is
268      * done by attempting to run commands without and then with sudo
269      * @throws IOException if something goes wrong on the IO end
270      * @throws InterruptedException If this thread is interrupted
271      */
272     private void buildDockerCommands() throws IOException, InterruptedException {
273         psCmd[COMPOSE_FILE_IDX] = tmpDockerComposeFile.toString();
274         psCmdNoSudo[COMPOSE_FILE_IDX_NO_SUDO] = tmpDockerComposeFile.toString();
275         upCmd[COMPOSE_FILE_IDX] = tmpDockerComposeFile.toString();
276         downCmd[COMPOSE_FILE_IDX] = tmpDockerComposeFile.toString();
277         execCmd[COMPOSE_FILE_IDX] = tmpDockerComposeFile.toString();
278
279         if (0 == ProcUtils.tryProcess(null, 5000, psCmdNoSudo)) {
280             LOG.info("DockerOvs.buildDockerCommands docker-compose does not require sudo");
281             upCmd = removeFirstEl(upCmd);
282             downCmd = removeFirstEl(downCmd);
283             execCmd = removeFirstEl(execCmd);
284         } else if (0 == ProcUtils.tryProcess(null, 5000, psCmd)) {
285             LOG.info("DockerOvs.buildDockerCommands docker-compose requires sudo");
286         } else {
287             fail("docker-compose does not seem to work with or without sudo");
288         }
289
290         if (0 == ProcUtils.tryProcess(null, 5000, dockerPsCmdNoSudo)) {
291             LOG.info("DockerOvs.buildDockerCommands docker does not require sudo");
292             netCreateCmd = removeFirstEl(netCreateCmd);
293             netInspectCmd = removeFirstEl(netInspectCmd);
294         } else if (0 == ProcUtils.tryProcess(null, 5000, dockerPsCmd)) {
295             LOG.info("DockerOvs.buildDockerCommands docker requires sudo");
296         } else {
297             fail("docker does not seem to work with or without sudo");
298         }
299     }
300
301     private String[] removeFirstEl(String[] arr) {
302         return Arrays.copyOfRange(arr, 1, arr.length);
303     }
304
305     private void networkUp() throws IOException, InterruptedException {
306         if (usingExternalDocker() || !createOdlNetwork) {
307             return;
308         }
309
310         if (0 != ProcUtils.tryProcess(null, 5000, netInspectCmd)) {
311             ProcUtils.runProcess(5000, netCreateCmd);
312         }
313
314         System.getProperties().setProperty(ItConstants.CONTROLLER_IPADDRESS, ODL_NET_GATEWAY);
315     }
316
317     /**
318      * Are we using some other OVS, not a docker we spin up?.
319      *
320      * @return true if we are *not* running a docker image to test against
321      */
322     public boolean usingExternalDocker() {
323         return !runDocker;
324     }
325
326     /**
327      * Get the IP address of the n'th OVS.
328      * @param ovsNumber which OVS?
329      * @return IP string
330      */
331     public String getOvsdbAddress(int ovsNumber) {
332         if (!runDocker) {
333             return envServerAddress;
334         }
335         return DEFAULT_OVSDB_HOST;
336     }
337
338     /**
339      * Get the port of the n'th OVS.
340      * @param ovsNumber which OVS?
341      * @return Port as a string
342      */
343     public String getOvsdbPort(int ovsNumber) {
344         if (!runDocker) {
345             return envServerPort;
346         }
347         return dockerComposeServices.get(getOvsNumString(ovsNumber)).port;
348     }
349
350     public String getOvsdbPort(String ovsName) {
351         if (!runDocker) {
352             return envServerPort;
353         }
354         return dockerComposeServices.get(ovsName).port;
355     }
356
357     /**
358      * How many OVS nodes are there.
359      * @return number of running OVS nodes
360      */
361     public int getNumOvsNodes() {
362         return dockerComposeServices.size();
363     }
364
365     private String getOvsNumString(int numOvs) {
366         if (numOvs == 0) {
367             return "ovs1";
368         } else {
369             return "ovs" + numOvs;
370         }
371     }
372
373     public String[] getExecCmdPrefix(int numOvs) {
374         String[] res = new String[execCmd.length];
375         System.arraycopy(execCmd, 0, res, 0, execCmd.length);
376         res[res.length - 1] = dockerComposeServices.get(getOvsNumString(numOvs)).name;
377         return res;
378     }
379
380     public void runInContainer(int waitFor, int numOvs, String ... cmdWords) throws IOException, InterruptedException {
381         String[] pfx = getExecCmdPrefix(numOvs);
382         String[] cmd = new String[pfx.length + cmdWords.length];
383         System.arraycopy(pfx, 0, cmd, 0, pfx.length);
384         System.arraycopy(cmdWords, 0, cmd, pfx.length, cmdWords.length);
385         ProcUtils.runProcess(waitFor, cmd);
386     }
387
388     public int runInContainer(int reserved, int waitFor, int numOvs, String ... cmdWords)
389             throws IOException, InterruptedException {
390         String[] pfx = getExecCmdPrefix(numOvs);
391         String[] cmd = new String[pfx.length + cmdWords.length];
392         System.arraycopy(pfx, 0, cmd, 0, pfx.length);
393         System.arraycopy(cmdWords, 0, cmd, pfx.length, cmdWords.length);
394         return ProcUtils.runProcess(reserved, waitFor, null, cmd);
395     }
396
397     public int runInContainer(int reserved, int waitFor, StringBuilder capturedStdout, int numOvs, String ... cmdWords)
398             throws IOException, InterruptedException {
399         String[] pfx = getExecCmdPrefix(numOvs);
400         String[] cmd = new String[pfx.length + cmdWords.length];
401         System.arraycopy(pfx, 0, cmd, 0, pfx.length);
402         System.arraycopy(cmdWords, 0, cmd, pfx.length, cmdWords.length);
403         return ProcUtils.runProcess(reserved, waitFor, capturedStdout, cmd);
404     }
405
406     public void tryInContainer(String logText, int waitFor, int numOvs, String ... cmdWords)
407             throws IOException, InterruptedException {
408         String[] pfx = getExecCmdPrefix(numOvs);
409         String[] cmd = new String[pfx.length + cmdWords.length];
410         System.arraycopy(pfx, 0, cmd, 0, pfx.length);
411         System.arraycopy(cmdWords, 0, cmd, pfx.length, cmdWords.length);
412         ProcUtils.tryProcess(logText, waitFor, cmd);
413     }
414
415     /**
416      * Parse the docker-compose yaml file to extract the port mappings.
417      * @return a list of the external ports
418      */
419     private List<String> parseDockerComposeYaml() {
420         List<String> ports = new ArrayList<>();
421
422         YamlReader yamlReader = null;
423         Map root = null;
424         try {
425             yamlReader = new YamlReader(new InputStreamReader(new FileInputStream(tmpDockerComposeFile),
426                     StandardCharsets.UTF_8));
427             root = (Map) yamlReader.read();
428         } catch (FileNotFoundException e) {
429             LOG.warn("DockerOvs.parseDockerComposeYaml error reading yaml file", e);
430             return ports;
431         } catch (YamlException e) {
432             LOG.warn("DockerOvs.parseDockerComposeYaml error parsing yaml file", e);
433             return ports;
434         }
435
436         if (null == root) {
437             return ports;
438         }
439
440         String composeVersion = (String)root.get("version");
441         if (null != composeVersion && composeVersion.equals("2")) {
442             root = (Map) root.get("services");
443         }
444
445         for (Object entry : root.entrySet()) {
446             String key = ((Map.Entry<String,Map>)entry).getKey();
447             Map map = ((Map.Entry<String,Map>)entry).getValue();
448
449             DockerComposeServiceInfo svc = new DockerComposeServiceInfo();
450             svc.name = key;
451
452             List portMappings = (List) map.get("ports");
453             if (null == portMappings) {
454                 continue;
455             }
456             for (Object portMapping : portMappings) {
457                 String portMappingStr = (String) portMapping;
458                 int delim = portMappingStr.indexOf(":");
459                 if (delim == -1) {
460                     continue;
461                 }
462                 String port = portMappingStr.substring(0, delim);
463                 ports.add(port);
464                 svc.port = port;
465             }
466             //TODO: think this through. What if there is no port?
467             dockerComposeServices.put(svc.name, svc);
468         }
469
470         return ports;
471     }
472
473     /**
474      * Shut everything down.
475      */
476     @Override
477     public void close() throws IOException, InterruptedException {
478         if (isRunning) {
479             ProcUtils.runProcess(10000, downCmd);
480             isRunning = false;
481         }
482
483         if (!tmpDockerComposeFile.delete()) {
484             LOG.warn("Failed to delete {}", tmpDockerComposeFile);
485         }
486     }
487
488     /**
489      * A thread that waits until it can "ping" a running OVS -  tests basic reachability
490      * and readiness. The "ping" here is actually a list_dbs method and the response is
491      * checked to make sure the Open_Vswitch DB is present. Note that this thread will
492      * run until it succeeds unless its interrupt() method is called.
493      */
494     private static class OvsdbPing extends Thread {
495         private final String host;
496         private final int port;
497         private final AtomicInteger result;
498         public CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
499         ByteBuffer listDbsRequest;
500
501         /**
502          * Construct a new OvsdbPing object.
503          * @param ovsNumber which OVS is this?
504          * @param result an AtomicInteger that is incremented upon a successful "ping"
505          */
506         OvsdbPing(AtomicInteger result, String host, int port) {
507             this.host = host;
508             this.port = port;
509             this.result = result;
510             listDbsRequest = ByteBuffer.wrap(
511                 ("{\"method\": \"list_dbs\", \"params\": [], \"id\": " + port + "}").getBytes(StandardCharsets.UTF_8));
512             listDbsRequest.mark();
513         }
514
515         @Override
516         public void run() {
517             while (!doPing()) {
518                 try {
519                     Thread.sleep(1000);
520                 } catch (InterruptedException e) {
521                     LOG.warn("OvsdbPing interrupted", e);
522                     return;
523                 }
524             }
525         }
526
527         /**
528          * Attempt a "ping" of the OVSDB connection.
529          * @return true if the ping was successful OR IF THIS THREAD WAS INTERRUPTED
530          */
531         private boolean doPing() {
532             try (SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port))) {
533                 socketChannel.write(listDbsRequest);
534                 listDbsRequest.reset();
535
536                 ByteBuffer buf = ByteBuffer.allocateDirect(512);
537                 socketChannel.read(buf);
538                 buf.flip();
539                 String response = decoder.decode(buf).toString();
540
541                 if (response.contains("Open_vSwitch")) {
542                     LOG.info("OvsdbPing connection validated");
543                     result.incrementAndGet();
544                     return true;
545                 }
546             } catch (ClosedByInterruptException e) {
547                 LOG.warn("OvsdbPing interrupted", e);
548                 //return true here because we're done, ne'er to return again.
549                 return true;
550             } catch (IOException e) {
551                 LOG.info("OvsdbPing exception while attempting connect", e);
552             }
553             return false;
554         }
555     }
556
557     /**
558      * Wait for all Ovs's to accept and respond to OVSDB requests.
559      * @param waitFor How long to wait
560      * @throws IOException if something goes wrong on the IO end
561      * @throws InterruptedException If this thread is interrupted
562      */
563     private void waitForOvsdbServers(long waitFor) throws IOException, InterruptedException {
564         AtomicInteger numRunningOvs = new AtomicInteger(0);
565
566         int numOvs = dockerComposeServices.size();
567         if (0 == numOvs) {
568             return;
569         }
570
571         OvsdbPing[] pingers = new OvsdbPing[numOvs];
572         if (numOvs == 1) {
573             pingers[0] = new OvsdbPing(numRunningOvs, getOvsdbAddress(0), Integer.parseInt(getOvsdbPort(0)));
574             pingers[0].start();
575         } else {
576             for (int i = 0; i < numOvs; i++) {
577                 pingers[i] = new OvsdbPing(numRunningOvs, getOvsdbAddress(i + 1),
578                         Integer.parseInt(getOvsdbPort(i + 1)));
579                 pingers[i].start();
580             }
581         }
582
583         long startTime = System.currentTimeMillis();
584         while (System.currentTimeMillis() - startTime < waitFor) {
585             if (numRunningOvs.get() >= numOvs) {
586                 LOG.info("DockerOvs.waitForOvsdbServers all OVS instances running");
587                 break;
588             }
589             Thread.sleep(1000);
590         }
591         LOG.info("DockerOvs.waitForOvsdbServers - finished waiting in {}", System.currentTimeMillis() - startTime);
592
593         for (OvsdbPing pinger : pingers) {
594             pinger.interrupt();
595         }
596     }
597
598     /**
599      * Since the docker-compose file is a resource in the bundle and docker-compose needs it.
600      * in the file system, we copy it over - ugly but necessary.
601      * @param yamlFileName File name
602      * @return A File object for the newly created temporary yaml file.
603      */
604     private File createTempDockerComposeFile(String yamlFileName) {
605         Bundle bundle = FrameworkUtil.getBundle(this.getClass());
606         assertNotNull("DockerOvs: bundle is null", bundle);
607         URL url = bundle.getResource(envDockerComposeFile);
608         assertNotNull("DockerOvs: URL is null", url);
609
610         File tmpFile = null;
611         try {
612             tmpFile = File.createTempFile("ovsdb-it-tmp-", null);
613
614             try (Reader in = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8);
615                     Writer out = new OutputStreamWriter(new FileOutputStream(tmpFile), StandardCharsets.UTF_8)) {
616                 char[] buf = new char[1024];
617                 int read;
618                 while (-1 != (read = in.read(buf))) {
619                     out.write(buf, 0, read);
620                 }
621             }
622
623         } catch (IOException e) {
624             fail(e.toString());
625         }
626
627         return tmpFile;
628     }
629
630     /**
631      * Useful for debugging. Dump some interesting config
632      * @throws IOException If something goes wrong with reading the process output
633      * @throws InterruptedException because there's some sleeping in here
634      */
635     public void logState(int dockerInstance, String logText) throws IOException, InterruptedException {
636         tryInContainer(logText, 5000, dockerInstance, "ip", "addr");
637         tryInContainer(logText, 5000, dockerInstance, "ovs-vsctl", "show");
638         tryInContainer(logText, 5000, dockerInstance, "ovs-ofctl", "-OOpenFlow13", "show", "br-int");
639         tryInContainer(logText, 5000, dockerInstance, "ovs-ofctl", "-OOpenFlow13", "dump-flows", "br-int");
640         tryInContainer(logText, 5000, dockerInstance, "ip", "netns", "list");
641     }
642 }