aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src
diff options
context:
space:
mode:
authorHarald Musum <musum@yahoo-inc.com>2016-12-09 08:53:59 +0100
committerGitHub <noreply@github.com>2016-12-09 08:53:59 +0100
commit7c314a1c3b624e2f1db5b2d3c53d5cbd1e38d1e5 (patch)
treec0c086c000b86316ea257e7970596e0f4a425f68 /node-admin/src
parent3d70076fe4ea5756467701068d189610f4ffaa0c (diff)
parent3be8cac786d2ddec96158c62a67019415e43d66e (diff)
Merge pull request #1278 from yahoo/freva/update-node-admin-readme
Freva/update node admin readme
Diffstat (limited to 'node-admin/src')
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/LocalZoneUtils.java142
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/RunVespaLocal.java149
2 files changed, 191 insertions, 100 deletions
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/LocalZoneUtils.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/LocalZoneUtils.java
index 0f86c9e753f..9d5726d7071 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/LocalZoneUtils.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/LocalZoneUtils.java
@@ -1,6 +1,7 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.docker;
+import com.yahoo.net.HostName;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.hosted.dockerapi.Container;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
@@ -10,6 +11,7 @@ import com.yahoo.vespa.hosted.dockerapi.DockerImpl;
import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
import com.yahoo.vespa.hosted.node.admin.util.Environment;
+import com.yahoo.vespa.hosted.node.maintenance.Maintainer;
import com.yahoo.vespa.hosted.provision.Node;
import java.io.IOException;
@@ -20,22 +22,31 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
+import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
* @author freva
*/
public class LocalZoneUtils {
+ public static final int CONFIG_SERVER_WEB_SERVICE_PORT = 4080;
public static final String CONFIG_SERVER_HOSTNAME = "config-server";
public static final ContainerName CONFIG_SERVER_CONTAINER_NAME = new ContainerName(CONFIG_SERVER_HOSTNAME);
- public static final int CONFIG_SERVER_WEB_SERVICE_PORT = 4080;
+ public static final String NODE_ADMIN_HOSTNAME = HostName.getLocalhost();
+ public static final ContainerName NODE_ADMIN_CONTAINER_NAME = new ContainerName("node-admin");
public static final DockerImage VESPA_LOCAL_IMAGE = new DockerImage("vespa-local:latest");
private static final ConfigServerHttpRequestExecutor requestExecutor = ConfigServerHttpRequestExecutor.create(
@@ -45,10 +56,10 @@ public class LocalZoneUtils {
private static final String APPLICATION_NAME = "default";
private static final Path PROJECT_ROOT = Paths.get("").toAbsolutePath();
- public static boolean startConfigServerIfNeeded(Docker docker, Environment environment) throws UnknownHostException {
+ public static void startConfigServerIfNeeded(Docker docker, Environment environment) throws UnknownHostException {
Optional<Container> container = docker.getContainer(CONFIG_SERVER_HOSTNAME);
if (container.isPresent()) {
- if (container.get().isRunning) return true;
+ if (container.get().isRunning) return;
else docker.deleteContainer(CONFIG_SERVER_CONTAINER_NAME);
}
@@ -66,21 +77,52 @@ public class LocalZoneUtils {
.create();
docker.startContainer(CONFIG_SERVER_CONTAINER_NAME);
+ }
- int maxRetries = 2000;
- for (int i = 1; i < maxRetries; i++) {
- try {
- if (i % 100 == 0) System.out.println("Check if config server is up, try " + i + " of " + maxRetries);
-
- URL url = new URL("http://" + CONFIG_SERVER_HOSTNAME + ":" + CONFIG_SERVER_WEB_SERVICE_PORT +
- "/state/v1/health");
- Thread.sleep(100);
- HttpURLConnection http = (HttpURLConnection) url.openConnection();
- if (http.getResponseCode() == 200) return true;
- } catch (IOException | InterruptedException ignored) { }
+ public static void startNodeAdminIfNeeded(Docker docker, Environment environment, Path pathToContainerStorage) {
+ Optional<Container> container = docker.getContainer(NODE_ADMIN_HOSTNAME);
+ if (container.isPresent()) {
+ if (container.get().isRunning) return;
+ else docker.deleteContainer(NODE_ADMIN_CONTAINER_NAME);
}
- return false;
+ Docker.CreateContainerCommand createCmd = docker.createContainerCommand(VESPA_LOCAL_IMAGE,
+ NODE_ADMIN_CONTAINER_NAME, NODE_ADMIN_HOSTNAME)
+ .withNetworkMode("host")
+ .withVolume("/proc", "/host/proc")
+ .withVolume("/var/run/docker.sock", "/host/var/run/docker.sock")
+ .withVolume(pathToContainerStorage.toString(), "/host" + pathToContainerStorage.toString())
+ .withEnvironment("ENVIRONMENT", environment.getEnvironment())
+ .withEnvironment("REGION", environment.getRegion())
+ .withEnvironment("CONFIG_SERVER_ADDRESS", CONFIG_SERVER_HOSTNAME)
+ .withEnvironment("JPDA_PORT", "localhost:5555")
+ .withEnvironment("JPDA_SUSPEND", "n")
+ .withEnvironment("YJAVA_OPTS", "-Dvespa.freezedetector.disable=true")
+ .withEntrypoint("/usr/local/bin/start-node-admin.sh");
+
+ Arrays.asList(
+ "/home/y/logs",
+ "/home/y/var/cache",
+ "/home/y/var/crash",
+ "/home/y/var/db/jdisc",
+ "/home/y/var/db/vespa",
+ "/home/y/var/jdisc_container",
+ "/home/y/var/jdisc_core",
+ "/home/y/var/maven",
+ "/home/y/var/run",
+ "/home/y/var/scoreboards",
+ "/home/y/var/service",
+ "/home/y/var/share",
+ "/home/y/var/spool",
+ "/home/y/var/vespa",
+ "/home/y/var/yca",
+ "/home/y/var/ycore++",
+ "/home/y/var/ymon",
+ "/home/y/var/zookeeper")
+ .forEach(path -> createCmd.withVolume(pathToContainerStorage.resolve("node-admin" + path).toString(), path));
+
+ createCmd.create();
+ docker.startContainer(NODE_ADMIN_CONTAINER_NAME);
}
public static void buildVespaLocalDockerImage(Docker docker, DockerImage vespaBaseImage) throws IOException {
@@ -108,11 +150,26 @@ public class LocalZoneUtils {
docker.buildImage(dockerfilePath.getParent().toFile(), VESPA_LOCAL_IMAGE);
}
+ public static void provisionHost(String hostname) {
+ List<Map<String, String>> nodesToAdd = new ArrayList<>();
+ Map<String, String> provisionNodeRequest = new HashMap<>();
+ provisionNodeRequest.put("type", "host");
+ provisionNodeRequest.put("flavor", "docker");
+ provisionNodeRequest.put("hostname", hostname);
+ provisionNodeRequest.put("openStackId", "fake-" + hostname);
+ nodesToAdd.add(provisionNodeRequest);
+
+ try {
+ requestExecutor.post("/nodes/v2/node", CONFIG_SERVER_WEB_SERVICE_PORT, nodesToAdd, Map.class);
+ } catch (RuntimeException e) {
+ if (! e.getMessage().contains("A node with this name already exists")) throw e;
+ }
+ }
+
/**
* Adds numberOfNodes to node-repo and returns a set of node hostnames.
*/
public static Set<String> provisionNodes(String parentHostname, int numberOfNodes) {
- Set<String> hostnames = new HashSet<>();
List<Map<String, String>> nodesToAdd = new ArrayList<>();
for (int i = 1; i <= numberOfNodes; i++) {
final String hostname = APP_HOSTNAME_PREFIX + i;
@@ -123,16 +180,23 @@ public class LocalZoneUtils {
provisionNodeRequest.put("hostname", hostname);
provisionNodeRequest.put("openStackId", "fake-" + hostname);
nodesToAdd.add(provisionNodeRequest);
- hostnames.add(hostname);
}
- requestExecutor.post("/nodes/v2/node", CONFIG_SERVER_WEB_SERVICE_PORT, nodesToAdd, Map.class);
- return hostnames;
+ try {
+ requestExecutor.post("/nodes/v2/node", CONFIG_SERVER_WEB_SERVICE_PORT, nodesToAdd, Map.class);
+ } catch (RuntimeException e) {
+ if (! e.getMessage().contains("A node with this name already exists")) throw e;
+ }
+ return nodesToAdd.stream().map(i -> i.get("hostname")).collect(Collectors.toSet());
}
public static void setState(Node.State state, String hostname) {
- requestExecutor.put("/nodes/v2/state/" + state + "/" + hostname,
- CONFIG_SERVER_WEB_SERVICE_PORT, Optional.empty(), Map.class);
+ try {
+ requestExecutor.put("/nodes/v2/state/" + state + "/" + hostname,
+ CONFIG_SERVER_WEB_SERVICE_PORT, Optional.empty(), Map.class);
+ } catch (RuntimeException e) {
+ if (! e.getMessage().contains("Not registered as provisioned, dirty, failed or parked")) throw e;
+ }
}
public static void deployApp(Docker docker, Path pathToApp) {
@@ -176,5 +240,39 @@ public class LocalZoneUtils {
requestExecutor.delete("/application/v2/tenant/" + tenantName + "/application/" + appName,
CONFIG_SERVER_WEB_SERVICE_PORT, Map.class);
}
+
+ public static boolean isReachableURL(URL url, Duration timeout) {
+ Instant start = Instant.now();
+ while (Instant.now().minus(timeout).isBefore(start)) {
+ try {
+ Thread.sleep(100);
+ HttpURLConnection http = (HttpURLConnection) url.openConnection();
+ if (http.getResponseCode() == 200) return true;
+ } catch (IOException | InterruptedException ignored) { }
+ }
+
+ return false;
+ }
+
+ public static Maintainer getMaintainer(Path pathToContainerStorage) {
+ Maintainer maintainer = mock(Maintainer.class);
+ when(maintainer.pathInHostFromPathInNode(any(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ return pathToContainerStorage
+ .resolve(((ContainerName) args[0]).asString())
+ .resolve((String) args[1]);
+ });
+ when(maintainer.pathInNodeAdminFromPathInNode(any(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ return maintainer.pathInHostFromPathInNode((ContainerName) args[0], (String) args[1]);
+ });
+ when(maintainer.pathInNodeAdminToNodeCleanup(any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ return pathToContainerStorage
+ .resolve("cleanup_" + ((ContainerName) args[0]).asString() + "_" + System.currentTimeMillis());
+ });
+
+ return maintainer;
+ }
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/RunVespaLocal.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/RunVespaLocal.java
index 58dd64f48bc..bc333adde22 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/RunVespaLocal.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/RunVespaLocal.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.node.admin.docker;
import com.yahoo.metrics.simple.MetricReceiver;
import com.yahoo.net.HostName;
-import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.DockerImage;
import com.yahoo.vespa.hosted.dockerapi.DockerTestUtils;
@@ -14,47 +13,26 @@ import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.node.admin.provider.ComponentsProviderImpl;
import com.yahoo.vespa.hosted.node.admin.util.Environment;
import com.yahoo.vespa.hosted.node.admin.util.InetAddressResolver;
-import com.yahoo.vespa.hosted.node.maintenance.Maintainer;
import com.yahoo.vespa.hosted.provision.Node;
-import org.junit.Before;
import java.io.IOException;
-import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
import java.net.URL;
+import java.net.UnknownHostException;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.Instant;
+import java.time.Duration;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
/**
* <pre>
* Requires docker daemon, see {@link com.yahoo.vespa.hosted.dockerapi.DockerTestUtils} for more details.
*
- * To get started:
- * 1. Add config-server and container nodes hostnames to /etc/hosts:
- * $ sudo ./vespa/node-admin/scripts/etc-hosts.sh
- * 2. Set environmental variables in shell or e.g. ~/.bashrc:
- * VESPA_HOME="/home/y"
- * VESPA_WEB_SERVICE_PORT="4080"
- *
- * Linux only:
- * 1. Create /home/docker/container-storage with read/write permissions
- *
- * Example usage:
- DockerImage vespaDockerBase = new DockerImage("docker-registry.ops.yahoo.com:4443/vespa/ci:6.52.35");
- Path pathToAppToDeploy = Paths.get("/home/valerijf/dev/basic-search/target/application.zip");
- RunVespaLocal runVespaLocal = new RunVespaLocal(vespaDockerBase, pathToAppToDeploy);
- runVespaLocal.runVespaLocalTest();
- *
* Issues:
* 1. If you cannot make Docker Toolbox start, try starting Virtualbox and turn off the "default" machine
* 2. If the above is not enough try "sudo ifconfig vboxnet0 down && sudo ifconfig vboxnet0 up" (see https://github.com/docker/kitematic/issues/1193)
@@ -66,34 +44,20 @@ public class RunVespaLocal {
private static final Environment environment = new Environment(
Collections.singleton(LocalZoneUtils.CONFIG_SERVER_HOSTNAME), "prod", "vespa-local",
HostName.getLocalhost(), new InetAddressResolver());
- private static final Maintainer maintainer = mock(Maintainer.class);
+ private NodeAdminStateUpdater nodeAdminStateUpdater = null;
private final Docker docker;
- private final DockerImage vespaBaseImage;
- private final Path pathToAppToDeploy;
-
private final Logger logger = Logger.getLogger("RunVespaLocal");
-
- RunVespaLocal(DockerImage vespaBaseImage, Path pathToAppToDeploy) {
+ RunVespaLocal() {
this.docker = DockerTestUtils.getDocker();
- this.vespaBaseImage = vespaBaseImage;
- this.pathToAppToDeploy = pathToAppToDeploy;
}
- void runVespaLocalTest() throws IOException, InterruptedException, ExecutionException {
- DockerTestUtils.OS operatingSystem = DockerTestUtils.getSystemOS();
- if (operatingSystem == DockerTestUtils.OS.Mac_OS_X) {
- when(maintainer.pathInHostFromPathInNode(any(), any())).thenReturn(Paths.get("/tmp/"));
- } else {
- when(maintainer.pathInHostFromPathInNode(any(), any())).thenCallRealMethod();
- }
- when(maintainer.pathInNodeAdminToNodeCleanup(any())).thenReturn(Paths.get("/tmp"));
- when(maintainer.pathInNodeAdminFromPathInNode(any(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- return maintainer.pathInHostFromPathInNode((ContainerName) args[0], (String) args[1]);
- });
-
+ /**
+ * Pulls the base image and builds the vespa-local image
+ * @param vespaBaseImage Vespa docker image to use as base for the image that the config-server and nodes will run
+ */
+ void buildVespaLocalImage(DockerImage vespaBaseImage) throws ExecutionException, InterruptedException, IOException {
if (!docker.imageIsDownloaded(vespaBaseImage)) {
logger.info("Pulling " + vespaBaseImage.asString() + " (This may take a while)");
docker.pullImageAsync(vespaBaseImage).get();
@@ -101,49 +65,78 @@ public class RunVespaLocal {
logger.info("Building " + LocalZoneUtils.VESPA_LOCAL_IMAGE.asString());
LocalZoneUtils.buildVespaLocalDockerImage(docker, vespaBaseImage);
+ }
+ /**
+ * Starts config server, provisions numNodesToProvision and puts them in ready state
+ */
+ void startLocalZoneWithNodes(int numNodesToProvision) throws IOException, InterruptedException, ExecutionException {
logger.info("Starting config-server");
- assertTrue("Could not start config server", LocalZoneUtils.startConfigServerIfNeeded(docker, environment));
+ LocalZoneUtils.startConfigServerIfNeeded(docker, environment);
+
+ logger.info("Waiting until config-server is ready to serve");
+ URL configServerUrl = new URL("http://" + LocalZoneUtils.CONFIG_SERVER_HOSTNAME +
+ ":" + LocalZoneUtils.CONFIG_SERVER_WEB_SERVICE_PORT + "/state/v1/health");
+ assertTrue("Could not start config server", LocalZoneUtils.isReachableURL(configServerUrl, Duration.ofSeconds(120)));
logger.info("Provisioning nodes");
+ Set<String> hostnames = LocalZoneUtils.provisionNodes(HostName.getLocalhost(), numNodesToProvision);
+ hostnames.forEach(hostname -> LocalZoneUtils.setState(Node.State.ready, hostname));
+ }
+
+ /**
+ * Start node-admin in IDE
+ * @param pathToContainerStorage Path to where the container data will be stored, the path must exist and must
+ * be writeable by user, normally /home/docker/container-storage
+ */
+ void startNodeAdminInIDE(Path pathToContainerStorage) {
+ logger.info("Starting node-admin");
+ nodeAdminStateUpdater = new ComponentsProviderImpl(docker,
+ new MetricReceiverWrapper(MetricReceiver.nullImplementation),
+ new StorageMaintainerMock(LocalZoneUtils.getMaintainer(pathToContainerStorage), new CallOrderVerifier()),
+ environment).getNodeAdminStateUpdater();
+ }
+
+ /**
+ * Starts node-admin inside a container
+ * @param pathToContainerStorage Path to where the container data will be stored, the path must exist and must
+ * be writeable by user, normally /home/docker/container-storage
+ */
+ void startNodeAdminAsContainer(Path pathToContainerStorage) throws UnknownHostException {
+ String hostname = InetAddress.getByName("172.18.0.1").getHostName();
+ logger.info("Provisioning host at " + hostname);
+ LocalZoneUtils.provisionHost(hostname);
+
+ logger.info("Starting node-admin");
+ LocalZoneUtils.startNodeAdminIfNeeded(docker, environment, pathToContainerStorage);
try {
- Set<String> hostnames = LocalZoneUtils.provisionNodes(HostName.getLocalhost(), 5);
- for (String hostname : hostnames) {
- try {
- LocalZoneUtils.setState(Node.State.ready, hostname);
- } catch (RuntimeException e) {
- logger.warning(e.getMessage());
- }
- }
- } catch (RuntimeException e) {
- logger.warning(e.getMessage());
+ URL nodeUrl = new URL("http://localhost:" + System.getenv("VESPA_WEB_SERVICE_PORT") + "/");
+ assertTrue(LocalZoneUtils.isReachableURL(nodeUrl, Duration.ofSeconds(120)));
+ logger.info("Ready");
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
}
+ }
+ /**
+ * Deploys an app and waits for the node to come up
+ * @param pathToAppToDeploy Path to .zip file of the application to deploy
+ */
+ void deployApplication(Path pathToAppToDeploy) {
logger.info("Deploying application");
LocalZoneUtils.deployApp(docker, pathToAppToDeploy);
- logger.info("Starting node-admin");
- NodeAdminStateUpdater nodeAdminStateUpdater = new ComponentsProviderImpl(docker,
- new MetricReceiverWrapper(MetricReceiver.nullImplementation),
- new StorageMaintainerMock(maintainer, new CallOrderVerifier()),
- environment).getNodeAdminStateUpdater();
-
- logger.info("Ready");
// TODO: Automatically find correct node to send request to
- URL url = new URL("http://cnode-1:" + System.getenv("VESPA_WEB_SERVICE_PORT") + "/");
- Instant start = Instant.now();
- boolean okResponse = false;
- do {
- try {
- HttpURLConnection http = (HttpURLConnection) url.openConnection();
- if (http != null && http.getResponseCode() == 200) okResponse = true;
- } catch (IOException e) {
- Thread.sleep(100);
- }
- } while (! okResponse && Instant.now().isBefore(start.plusSeconds(120)));
- assertTrue(okResponse);
+ try {
+ URL nodeUrl = new URL("http://cnode-1:" + System.getenv("VESPA_WEB_SERVICE_PORT") + "/");
+ assertTrue(LocalZoneUtils.isReachableURL(nodeUrl, Duration.ofSeconds(120)));
+ logger.info("Ready");
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+ void deleteApplication() {
LocalZoneUtils.deleteApplication();
- nodeAdminStateUpdater.deconstruct();
}
}