2 * Copyright © 2016, 2017 Red Hat, Inc. and others. All rights reserved.
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
8 package org.opendaylight.ovsdb.utils.ovsdb.it.utils;
10 import static org.junit.Assert.assertNotNull;
11 import static org.junit.Assert.fail;
12 import static org.ops4j.pax.exam.CoreOptions.propagateSystemProperties;
14 import com.esotericsoftware.yamlbeans.YamlException;
15 import com.esotericsoftware.yamlbeans.YamlReader;
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;
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;
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;
47 * Run OVS(s) using docker-compose for use in integration tests.
50 * try(DockerOvs ovs = new DockerOvs()) {
51 * ConnectionInfo connectionInfo = SouthboundUtils.getConnectionInfo(
52 * ovs.getOvsdbAddress(0), ovs.getOvsdbPort(0));
54 * nodeInfo.disconnect();
56 * } catch (Exception e) {
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).
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.
72 * The following explains how system properties are used to configure DockerOvs
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";
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.
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";
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";
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};
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"};
136 private File tmpDockerComposeFile;
138 private String envServerAddress;
139 private String envServerPort;
140 private String envDockerComposeFile;
141 private String envDockerWaitForPing;
142 private boolean runDocker;
143 private boolean createOdlNetwork;
145 private static final class DockerComposeServiceInfo {
150 private final Map<String, DockerComposeServiceInfo> dockerComposeServices = new HashMap<>();
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
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)
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
176 public DockerOvs() throws IOException, InterruptedException {
177 this(DEFAULT_DOCKER_FILE);
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
186 public DockerOvs(String yamlFileName) throws IOException, InterruptedException {
190 LOG.info("DockerOvs.DockerOvs: Not running docker, -D{} specified", ItConstants.SERVER_IPADDRESS);
194 tmpDockerComposeFile = createTempDockerComposeFile(yamlFileName);
195 buildDockerCommands();
196 parseDockerComposeYaml();
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);
206 int waitSeconds = Integer.parseInt(envDockerWaitForPing);
207 waitForOvsdbServers(waitSeconds * 1000L);
210 private void setupEnvForDockerCompose(String venvWs) {
211 String dockerCompose = venvWs + "/venv/bin/docker-compose";
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};
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.
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);
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;
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);
251 String connType = env.getProperty(ItConstants.CONNECTION_TYPE, ItConstants.CONNECTION_TYPE_ACTIVE);
252 if (connType.equals(ItConstants.CONNECTION_TYPE_PASSIVE)) {
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);
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
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();
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");
287 fail("docker-compose does not seem to work with or without sudo");
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");
297 fail("docker does not seem to work with or without sudo");
301 private String[] removeFirstEl(String[] arr) {
302 return Arrays.copyOfRange(arr, 1, arr.length);
305 private void networkUp() throws IOException, InterruptedException {
306 if (usingExternalDocker() || !createOdlNetwork) {
310 if (0 != ProcUtils.tryProcess(null, 5000, netInspectCmd)) {
311 ProcUtils.runProcess(5000, netCreateCmd);
314 System.getProperties().setProperty(ItConstants.CONTROLLER_IPADDRESS, ODL_NET_GATEWAY);
318 * Are we using some other OVS, not a docker we spin up?.
320 * @return true if we are *not* running a docker image to test against
322 public boolean usingExternalDocker() {
327 * Get the IP address of the n'th OVS.
328 * @param ovsNumber which OVS?
331 public String getOvsdbAddress(int ovsNumber) {
333 return envServerAddress;
335 return DEFAULT_OVSDB_HOST;
339 * Get the port of the n'th OVS.
340 * @param ovsNumber which OVS?
341 * @return Port as a string
343 public String getOvsdbPort(int ovsNumber) {
345 return envServerPort;
347 return dockerComposeServices.get(getOvsNumString(ovsNumber)).port;
350 public String getOvsdbPort(String ovsName) {
352 return envServerPort;
354 return dockerComposeServices.get(ovsName).port;
358 * How many OVS nodes are there.
359 * @return number of running OVS nodes
361 public int getNumOvsNodes() {
362 return dockerComposeServices.size();
365 private String getOvsNumString(int numOvs) {
369 return "ovs" + numOvs;
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;
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);
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);
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);
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);
416 * Parse the docker-compose yaml file to extract the port mappings.
417 * @return a list of the external ports
419 private List<String> parseDockerComposeYaml() {
420 List<String> ports = new ArrayList<>();
422 YamlReader yamlReader = null;
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);
431 } catch (YamlException e) {
432 LOG.warn("DockerOvs.parseDockerComposeYaml error parsing yaml file", e);
440 String composeVersion = (String)root.get("version");
441 if (null != composeVersion && composeVersion.equals("2")) {
442 root = (Map) root.get("services");
445 for (Object entry : root.entrySet()) {
446 String key = ((Map.Entry<String,Map>)entry).getKey();
447 Map map = ((Map.Entry<String,Map>)entry).getValue();
449 DockerComposeServiceInfo svc = new DockerComposeServiceInfo();
452 List portMappings = (List) map.get("ports");
453 if (null == portMappings) {
456 for (Object portMapping : portMappings) {
457 String portMappingStr = (String) portMapping;
458 int delim = portMappingStr.indexOf(":");
462 String port = portMappingStr.substring(0, delim);
466 //TODO: think this through. What if there is no port?
467 dockerComposeServices.put(svc.name, svc);
474 * Shut everything down.
477 public void close() throws IOException, InterruptedException {
479 ProcUtils.runProcess(10000, downCmd);
483 if (!tmpDockerComposeFile.delete()) {
484 LOG.warn("Failed to delete {}", tmpDockerComposeFile);
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.
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;
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"
506 OvsdbPing(AtomicInteger result, String host, int port) {
509 this.result = result;
510 listDbsRequest = ByteBuffer.wrap(
511 ("{\"method\": \"list_dbs\", \"params\": [], \"id\": " + port + "}").getBytes(StandardCharsets.UTF_8));
512 listDbsRequest.mark();
520 } catch (InterruptedException e) {
521 LOG.warn("OvsdbPing interrupted", e);
528 * Attempt a "ping" of the OVSDB connection.
529 * @return true if the ping was successful OR IF THIS THREAD WAS INTERRUPTED
531 private boolean doPing() {
532 try (SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port))) {
533 socketChannel.write(listDbsRequest);
534 listDbsRequest.reset();
536 ByteBuffer buf = ByteBuffer.allocateDirect(512);
537 socketChannel.read(buf);
539 String response = decoder.decode(buf).toString();
541 if (response.contains("Open_vSwitch")) {
542 LOG.info("OvsdbPing connection validated");
543 result.incrementAndGet();
546 } catch (ClosedByInterruptException e) {
547 LOG.warn("OvsdbPing interrupted", e);
548 //return true here because we're done, ne'er to return again.
550 } catch (IOException e) {
551 LOG.info("OvsdbPing exception while attempting connect", e);
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
563 private void waitForOvsdbServers(long waitFor) throws IOException, InterruptedException {
564 AtomicInteger numRunningOvs = new AtomicInteger(0);
566 int numOvs = dockerComposeServices.size();
571 OvsdbPing[] pingers = new OvsdbPing[numOvs];
573 pingers[0] = new OvsdbPing(numRunningOvs, getOvsdbAddress(0), Integer.parseInt(getOvsdbPort(0)));
576 for (int i = 0; i < numOvs; i++) {
577 pingers[i] = new OvsdbPing(numRunningOvs, getOvsdbAddress(i + 1),
578 Integer.parseInt(getOvsdbPort(i + 1)));
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");
591 LOG.info("DockerOvs.waitForOvsdbServers - finished waiting in {}", System.currentTimeMillis() - startTime);
593 for (OvsdbPing pinger : pingers) {
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.
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);
612 tmpFile = File.createTempFile("ovsdb-it-tmp-", null);
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];
618 while (-1 != (read = in.read(buf))) {
619 out.write(buf, 0, read);
623 } catch (IOException e) {
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
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");