summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorValerij Fredriksen <freva@users.noreply.github.com>2018-02-16 08:51:06 +0100
committerGitHub <noreply@github.com>2018-02-16 08:51:06 +0100
commit5d9b3466e7bfa41e1a7855576d1bb32d83f5b9e0 (patch)
tree485795d732fe441738a5c50c6f16fddf31e8ba1e
parent61c5c820c167cd26d9544380af755ed46d267f7b (diff)
parent1da246219d581219b7d558cbcc3740baf224161a (diff)
Merge pull request #5050 from vespa-engine/freva/update-config-server-certificate
Update config server certificate in node-admin
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/DockerAdminComponent.java32
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/Environment.java10
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApi.java28
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImpl.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java)110
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java15
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java54
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/HttpException.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/HttpException.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SelfCloseableHttpClient.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/SelfCloseableHttpClient.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java120
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java69
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java188
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java66
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepository.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepository.java)5
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImpl.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java)70
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetAclResponse.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetAclResponse.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetNodesResponse.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetNodesResponse.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/NodeMessageResponse.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesRequestBody.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesRequestBody.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesResponse.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesResponse.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/Orchestrator.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/Orchestrator.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorException.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorException.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImpl.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java)18
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorNotFoundException.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorNotFoundException.java)2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java38
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/KeyStoreOptions.java29
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImplTest.java (renamed from node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java)8
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java162
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImplTest.java (renamed from node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java)25
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImplTest.java (renamed from node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java)32
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java5
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java6
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java13
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java28
37 files changed, 891 insertions, 272 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/DockerAdminComponent.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/DockerAdminComponent.java
index a678b8607fd..cd2e0cc5064 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/DockerAdminComponent.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/DockerAdminComponent.java
@@ -7,6 +7,8 @@ import com.yahoo.system.ProcessExecuter;
import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.config.ConfigServerConfig;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerClients;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerClientsImpl;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperationsImpl;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
@@ -16,12 +18,7 @@ import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdaterImpl;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepositoryImpl;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorImpl;
import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
-import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
import java.time.Clock;
import java.time.Duration;
@@ -41,7 +38,7 @@ public class DockerAdminComponent implements AdminComponent {
private final Optional<ClassLocking> classLocking;
private Optional<Environment> environment = Optional.empty();
- private Optional<ConfigServerHttpRequestExecutor> requestExecutor = Optional.empty();
+ private Optional<ConfigServerClients> configServerClients = Optional.empty();
private Optional<NodeAdminStateUpdaterImpl> nodeAdminStateUpdater = Optional.empty();
public DockerAdminComponent(ConfigServerConfig configServerConfig,
@@ -85,17 +82,10 @@ public class DockerAdminComponent implements AdminComponent {
environment = Optional.of(new Environment(configServerConfig));
}
- if (!requestExecutor.isPresent()) {
- requestExecutor = Optional.of(ConfigServerHttpRequestExecutor.create(
- environment.get().getConfigServerUris(),
- environment.get().getKeyStoreOptions(),
- environment.get().getTrustStoreOptions(),
- environment.get().getAthenzIdentity()));
+ if (!configServerClients.isPresent()) {
+ configServerClients = Optional.of(new ConfigServerClientsImpl(environment.get()));
}
- NodeRepository nodeRepository = new NodeRepositoryImpl(requestExecutor.get());
- Orchestrator orchestrator = new OrchestratorImpl(requestExecutor.get());
-
Clock clock = Clock.systemUTC();
String dockerHostHostName = HostName.getLocalhost();
ProcessExecuter processExecuter = new ProcessExecuter();
@@ -115,13 +105,13 @@ public class DockerAdminComponent implements AdminComponent {
AclMaintainer aclMaintainer = new AclMaintainer(
dockerOperations,
- nodeRepository,
+ configServerClients.get().nodeRepository(),
dockerHostHostName);
Function<String, NodeAgent> nodeAgentFactory = (hostName) -> new NodeAgentImpl(
hostName,
- nodeRepository,
- orchestrator,
+ configServerClients.get().nodeRepository(),
+ configServerClients.get().orchestrator(),
dockerOperations,
storageMaintainer,
aclMaintainer,
@@ -138,8 +128,8 @@ public class DockerAdminComponent implements AdminComponent {
clock);
return new NodeAdminStateUpdaterImpl(
- nodeRepository,
- orchestrator,
+ configServerClients.get().nodeRepository(),
+ configServerClients.get().orchestrator(),
storageMaintainer,
nodeAdmin,
dockerHostHostName,
@@ -155,7 +145,7 @@ public class DockerAdminComponent implements AdminComponent {
}
nodeAdminStateUpdater.ifPresent(NodeAdminStateUpdaterImpl::stop);
- requestExecutor.ifPresent(ConfigServerHttpRequestExecutor::close);
+ configServerClients.ifPresent(ConfigServerClients::stop);
nodeAdminStateUpdater = Optional.empty();
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/Environment.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/Environment.java
index 7376a59bd5c..5581415fec2 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/Environment.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/Environment.java
@@ -86,11 +86,13 @@ public class Environment {
createKeyStoreOptions(
configServerConfig.keyStoreConfig().path(),
configServerConfig.keyStoreConfig().password().toCharArray(),
- configServerConfig.keyStoreConfig().type().name()),
+ configServerConfig.keyStoreConfig().type().name(),
+ "BC"),
createKeyStoreOptions(
configServerConfig.trustStoreConfig().path(),
configServerConfig.trustStoreConfig().password().toCharArray(),
- configServerConfig.trustStoreConfig().type().name()),
+ configServerConfig.trustStoreConfig().type().name(),
+ null),
createAthenzIdentity(
configServerConfig.athenzDomain(),
configServerConfig.serviceName())
@@ -161,10 +163,10 @@ public class Environment {
return Arrays.asList(logstashNodes.split("[,\\s]+"));
}
- private static Optional<KeyStoreOptions> createKeyStoreOptions(String pathToKeyStore, char[] password, String type) {
+ private static Optional<KeyStoreOptions> createKeyStoreOptions(String pathToKeyStore, char[] password, String type, String provider) {
return Optional.ofNullable(pathToKeyStore)
.filter(path -> !Strings.isNullOrEmpty(path))
- .map(path -> new KeyStoreOptions(Paths.get(path), password, type));
+ .map(path -> new KeyStoreOptions(Paths.get(path), password, type, provider));
}
private static Optional<AthenzIdentity> createAthenzIdentity(String athenzDomain, String serviceName) {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApi.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApi.java
new file mode 100644
index 00000000000..4d4a6c0328d
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApi.java
@@ -0,0 +1,28 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver;
+
+import java.util.Optional;
+
+/**
+ * Interface to execute basic HTTP request against config server(s)
+ *
+ * @author freva
+ */
+public interface ConfigServerApi extends AutoCloseable {
+
+ <T> T get(String path, Class<T> wantedReturnType);
+
+ <T> T post(String path, Object bodyJsonPojo, Class<T> wantedReturnType);
+
+ <T> T put(String path, Optional<Object> bodyJsonPojo, Class<T> wantedReturnType);
+
+ <T> T patch(String path, Object bodyJsonPojo, Class<T> wantedReturnType);
+
+ <T> T delete(String path, Class<T> wantedReturnType);
+
+ /**
+ * Close the underlying HTTP client and any threads this class might have started.
+ */
+ @Override
+ void close();
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImpl.java
index 13bfc949533..c5592e91973 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutor.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImpl.java
@@ -1,12 +1,9 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.util;
+package com.yahoo.vespa.hosted.node.admin.configserver;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.yahoo.concurrent.ThreadFactoryFactory;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
-import com.yahoo.vespa.athenz.tls.AthenzSslContextBuilder;
+import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
@@ -18,28 +15,15 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLContext;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
-import java.nio.file.Path;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.Security;
-import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
/**
* Retries request on config server a few times before giving up. Assumes that all requests should be sent with
@@ -47,13 +31,11 @@ import java.util.function.Supplier;
*
* @author dybdahl
*/
-public class ConfigServerHttpRequestExecutor implements AutoCloseable {
- private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(ConfigServerHttpRequestExecutor.class);
- private static final Duration CLIENT_REFRESH_INTERVAL = Duration.ofHours(1);
+public class ConfigServerApiImpl implements ConfigServerApi {
+ private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(ConfigServerApiImpl.class);
private final ObjectMapper mapper = new ObjectMapper();
- private final ScheduledExecutorService clientRefresherScheduler =
- Executors.newScheduledThreadPool(1, ThreadFactoryFactory.getDaemonThreadFactory("http-client-refresher"));
+
private final List<URI> configServerHosts;
/**
@@ -66,30 +48,20 @@ public class ConfigServerHttpRequestExecutor implements AutoCloseable {
*/
private volatile SelfCloseableHttpClient client;
- public static ConfigServerHttpRequestExecutor create(
- Collection<URI> configServerUris,
- Optional<KeyStoreOptions> keyStoreOptions,
- Optional<KeyStoreOptions> trustStoreOptions,
- Optional<AthenzIdentity> athenzIdentity) {
- Security.addProvider(new BouncyCastleProvider());
-
- Supplier<SelfCloseableHttpClient> clientSupplier = () -> createHttpClient(keyStoreOptions, trustStoreOptions, athenzIdentity);
- ConfigServerHttpRequestExecutor requestExecutor = new ConfigServerHttpRequestExecutor(
- randomizeConfigServerUris(configServerUris), clientSupplier.get());
+ public ConfigServerApiImpl(Collection<URI> configServerUris) {
+ this(configServerUris, SSLConnectionSocketFactory.getSocketFactory());
+ }
- if (keyStoreOptions.isPresent() || trustStoreOptions.isPresent()) {
- requestExecutor.clientRefresherScheduler.scheduleAtFixedRate(() -> requestExecutor.client = clientSupplier.get(),
- CLIENT_REFRESH_INTERVAL.toMillis(), CLIENT_REFRESH_INTERVAL.toMillis(), TimeUnit.MILLISECONDS);
- }
- return requestExecutor;
+ ConfigServerApiImpl(Collection<URI> configServerUris, SSLConnectionSocketFactory sslConnectionSocketFactory) {
+ this(randomizeConfigServerUris(configServerUris), new SelfCloseableHttpClient(sslConnectionSocketFactory));
}
- ConfigServerHttpRequestExecutor(List<URI> configServerHosts, SelfCloseableHttpClient client) {
+ ConfigServerApiImpl(List<URI> configServerHosts, SelfCloseableHttpClient client) {
this.configServerHosts = configServerHosts;
this.client = client;
}
- public interface CreateRequest {
+ interface CreateRequest {
HttpUriRequest createRequest(URI configServerUri) throws JsonProcessingException, UnsupportedEncodingException;
}
@@ -181,6 +153,10 @@ public class ConfigServerHttpRequestExecutor implements AutoCloseable {
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
}
+ public void setSSLConnectionSocketFactory(SSLConnectionSocketFactory sslSocketFactory) {
+ this.client = new SelfCloseableHttpClient(sslSocketFactory);
+ }
+
// Shuffle config server URIs to balance load
private static List<URI> randomizeConfigServerUris(Collection<URI> configServerUris) {
List<URI> shuffledConfigServerHosts = new ArrayList<>(configServerUris);
@@ -188,62 +164,8 @@ public class ConfigServerHttpRequestExecutor implements AutoCloseable {
return shuffledConfigServerHosts;
}
- private static SelfCloseableHttpClient createHttpClient(Optional<KeyStoreOptions> keyStoreOptions,
- Optional<KeyStoreOptions> trustStoreOptions,
- Optional<AthenzIdentity> athenzIdentity) {
- NODE_ADMIN_LOGGER.info("Creating new HTTP client");
- try {
- SSLContext sslContext = makeSslContext(keyStoreOptions, trustStoreOptions);
- HostnameVerifier hostnameVerifier = makeHostnameVerifier(athenzIdentity);
- SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
- return new SelfCloseableHttpClient(sslSocketFactory);
- } catch (Exception e) {
- NODE_ADMIN_LOGGER.error("Failed to create HTTP client with custom SSL Context, proceeding with default", e);
- return new SelfCloseableHttpClient();
- }
- }
-
- private static SSLContext makeSslContext(Optional<KeyStoreOptions> keyStoreOptions, Optional<KeyStoreOptions> trustStoreOptions) {
- AthenzSslContextBuilder sslContextBuilder = new AthenzSslContextBuilder();
- trustStoreOptions.ifPresent(options -> sslContextBuilder.withTrustStore(options.path.toFile(), options.type));
- keyStoreOptions.ifPresent(options -> {
- try {
- KeyStore keyStore = loadKeyStoreFromFileWithProvider(options.path, options.password, options.type, "BC");
- sslContextBuilder.withKeyStore(keyStore, options.password);
- } catch (Exception e) {
- throw new RuntimeException("Failed to read key store", e);
- }
- });
-
- return sslContextBuilder.build();
- }
-
- private static HostnameVerifier makeHostnameVerifier(Optional<AthenzIdentity> athenzIdentity) {
- return athenzIdentity
- .map(identity -> (HostnameVerifier) new AthenzIdentityVerifier(Collections.singleton(identity)))
- .orElse(SSLConnectionSocketFactory.getDefaultHostnameVerifier());
- }
-
@Override
public void close() {
- clientRefresherScheduler.shutdown();
- do {
- try {
- clientRefresherScheduler.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
- } catch (InterruptedException e1) {
- NODE_ADMIN_LOGGER.info("Interrupted while waiting for clientRefresherScheduler to shutdown");
- }
- } while (!clientRefresherScheduler.isTerminated());
-
client.close();
}
-
- private static KeyStore loadKeyStoreFromFileWithProvider(Path path, char[] password, String keyStoreType, String provider)
- throws IOException, GeneralSecurityException {
- KeyStore keyStore = KeyStore.getInstance(keyStoreType, provider);
- try (FileInputStream in = new FileInputStream(path.toFile())) {
- keyStore.load(in, password);
- }
- return keyStore;
- }
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java
new file mode 100644
index 00000000000..f52487c306f
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java
@@ -0,0 +1,15 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver;
+
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
+
+/**
+ * @author freva
+ */
+public interface ConfigServerClients {
+ NodeRepository nodeRepository();
+ Orchestrator orchestrator();
+
+ void stop();
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java
new file mode 100644
index 00000000000..43a2c66a9e5
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClientsImpl.java
@@ -0,0 +1,54 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver;
+
+import com.yahoo.vespa.hosted.node.admin.component.Environment;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepositoryImpl;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorImpl;
+
+import java.util.Optional;
+
+/**
+ * @author freva
+ */
+public class ConfigServerClientsImpl implements ConfigServerClients {
+
+ private final Optional<ConfigServerApi> configServerApi;
+ private final NodeRepository nodeRepository;
+ private final Orchestrator orchestrator;
+
+ public ConfigServerClientsImpl(Environment environment) {
+ this(new SslConfigServerApiImpl(environment));
+ }
+
+ public ConfigServerClientsImpl(NodeRepository nodeRepository, Orchestrator orchestrator) {
+ this(nodeRepository, orchestrator, Optional.empty());
+ }
+
+ private ConfigServerClientsImpl(ConfigServerApi configServerApi) {
+ this(new NodeRepositoryImpl(configServerApi), new OrchestratorImpl(configServerApi), Optional.of(configServerApi));
+ }
+
+ private ConfigServerClientsImpl(NodeRepository nodeRepository, Orchestrator orchestrator,
+ Optional<ConfigServerApi> configServerApi) {
+ this.nodeRepository = nodeRepository;
+ this.orchestrator = orchestrator;
+ this.configServerApi = configServerApi;
+ }
+
+ @Override
+ public NodeRepository nodeRepository() {
+ return nodeRepository;
+ }
+
+ @Override
+ public Orchestrator orchestrator() {
+ return orchestrator;
+ }
+
+ @Override
+ public void stop() {
+ configServerApi.ifPresent(ConfigServerApi::close);
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/HttpException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/HttpException.java
index 55d3ecc4e60..0a2ae1bd426 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/HttpException.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/HttpException.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.util;
+package com.yahoo.vespa.hosted.node.admin.configserver;
import javax.ws.rs.core.Response;
import java.util.Optional;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/SelfCloseableHttpClient.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SelfCloseableHttpClient.java
index 8e516729aff..cead7816387 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/SelfCloseableHttpClient.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SelfCloseableHttpClient.java
@@ -1,5 +1,5 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.util;
+package com.yahoo.vespa.hosted.node.admin.configserver;
import com.yahoo.log.LogLevel;
import org.apache.http.client.config.RequestConfig;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java
new file mode 100644
index 00000000000..8c2b87f4068
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConfigServerApiImpl.java
@@ -0,0 +1,120 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver;
+
+import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
+import com.yahoo.vespa.athenz.tls.AthenzSslContextBuilder;
+import com.yahoo.vespa.hosted.node.admin.component.Environment;
+import com.yahoo.vespa.hosted.node.admin.configserver.certificate.ConfigServerKeyStoreRefresher;
+import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import java.security.Security;
+import java.util.Collections;
+import java.util.Optional;
+
+/**
+ * ConfigServerApi with proper keystore, truststore and hostname verifier to communicate with the
+ * configserver(s). The keystore is refreshed automatically.
+ *
+ * @author freva
+ */
+public class SslConfigServerApiImpl implements ConfigServerApi {
+
+ private final ConfigServerApiImpl configServerApi;
+ private final Environment environment;
+ private final Optional<ConfigServerKeyStoreRefresher> keyStoreRefresher;
+
+ public SslConfigServerApiImpl(Environment environment) {
+ Security.addProvider(new BouncyCastleProvider());
+
+ this.environment = environment;
+
+ // At this point we don't know the state of the keystore, it may not exist at all, or the keystore
+ // maybe exists, but the certificate in it is expired. Create the ConfigServerApi without a keystore
+ // (but with truststore and hostname verifier).
+ this.configServerApi = new ConfigServerApiImpl(
+ environment.getConfigServerUris(), makeSslConnectionSocketFactory(Optional.empty()));
+
+ // If we have keystore options, we should make sure we use the keystore with the latest certificate,
+ // start the keystore refresher.
+ this.keyStoreRefresher = environment.getKeyStoreOptions().map(keyStoreOptions -> {
+ // Any callback from KeyStoreRefresher should result in using the latest keystore on disk
+ Runnable connectionFactoryRefresher = () -> configServerApi.setSSLConnectionSocketFactory(
+ makeSslConnectionSocketFactory(Optional.of(keyStoreOptions)));
+
+ ConfigServerKeyStoreRefresher keyStoreRefresher = new ConfigServerKeyStoreRefresher(
+ keyStoreOptions, connectionFactoryRefresher, configServerApi);
+
+ // Run the refresh once manually to make sure that we have a valid certificate, otherwise fail.
+ try {
+ keyStoreRefresher.refreshKeyStoreIfNeeded();
+ connectionFactoryRefresher.run(); // Update connectionFactory with the keystore on disk
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to acquire certificate to config server", e);
+ }
+
+ keyStoreRefresher.start();
+ return keyStoreRefresher;
+ });
+ }
+
+ @Override
+ public <T> T get(String path, Class<T> wantedReturnType) {
+ return configServerApi.get(path, wantedReturnType);
+ }
+
+ @Override
+ public <T> T post(String path, Object bodyJsonPojo, Class<T> wantedReturnType) {
+ return configServerApi.post(path, bodyJsonPojo, wantedReturnType);
+ }
+
+ @Override
+ public <T> T put(String path, Optional<Object> bodyJsonPojo, Class<T> wantedReturnType) {
+ return configServerApi.put(path, bodyJsonPojo, wantedReturnType);
+ }
+
+ @Override
+ public <T> T patch(String path, Object bodyJsonPojo, Class<T> wantedReturnType) {
+ return configServerApi.patch(path, bodyJsonPojo, wantedReturnType);
+ }
+
+ @Override
+ public <T> T delete(String path, Class<T> wantedReturnType) {
+ return configServerApi.delete(path, wantedReturnType);
+ }
+
+ @Override
+ public void close() {
+ keyStoreRefresher.ifPresent(ConfigServerKeyStoreRefresher::stop);
+ configServerApi.close();
+ }
+
+ private SSLConnectionSocketFactory makeSslConnectionSocketFactory(Optional<KeyStoreOptions> keyStoreOptions) {
+ return new SSLConnectionSocketFactory(makeSslContext(keyStoreOptions), makeHostnameVerifier());
+ }
+
+ private SSLContext makeSslContext(Optional<KeyStoreOptions> keyStoreOptions) {
+ AthenzSslContextBuilder sslContextBuilder = new AthenzSslContextBuilder();
+ environment.getTrustStoreOptions().ifPresent(options ->
+ sslContextBuilder.withTrustStore(options.path.toFile(), options.type));
+
+ keyStoreOptions.ifPresent(options -> {
+ try {
+ sslContextBuilder.withKeyStore(options.loadKeyStore(), options.password);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to read key store", e);
+ }
+ });
+
+ return sslContextBuilder.build();
+ }
+
+ private HostnameVerifier makeHostnameVerifier() {
+ return environment.getAthenzIdentity()
+ .map(identity -> (HostnameVerifier) new AthenzIdentityVerifier(Collections.singleton(identity)))
+ .orElseGet(SSLConnectionSocketFactory::getDefaultHostnameVerifier);
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java
new file mode 100644
index 00000000000..e7148754fde
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CertificateSerializedPayload.java
@@ -0,0 +1,69 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.certificate;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.openssl.PEMParser;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * Contains PEM formatted signed certificate
+ * TODO: Combine with its counterpart in athenz-identity-provider-service?
+ *
+ * @author freva
+ */
+public class CertificateSerializedPayload {
+
+ @JsonProperty("certificate") public final X509Certificate certificate;
+
+ @JsonCreator
+ public CertificateSerializedPayload(@JsonProperty("certificate") @JsonDeserialize(using = CertificateDeserializer.class)
+ X509Certificate certificate) {
+ this.certificate = certificate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CertificateSerializedPayload that = (CertificateSerializedPayload) o;
+
+ return certificate.equals(that.certificate);
+ }
+
+ @Override
+ public int hashCode() {
+ return certificate.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "CertificateSerializedPayload{" +
+ "certificate='" + certificate + '\'' +
+ '}';
+ }
+
+ public static class CertificateDeserializer extends JsonDeserializer<X509Certificate> {
+ @Override
+ public X509Certificate deserialize(
+ JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+ try (PEMParser pemParser = new PEMParser(new StringReader(jsonParser.getValueAsString()))) {
+ X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) pemParser.readObject();
+ return new JcaX509CertificateConverter().getCertificate(x509CertificateHolder);
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to deserialize X509Certificate", e);
+ }
+ }
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java
new file mode 100644
index 00000000000..1595db1047a
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresher.java
@@ -0,0 +1,188 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.certificate;
+
+import com.yahoo.net.HostName;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
+import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Clock;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Automatically refreshes the KeyStore used to authenticate this node to the configserver.
+ * The keystore contains a single certificate signed by one of the configservers.
+ *
+ * @author freva
+ */
+public class ConfigServerKeyStoreRefresher {
+
+ private static final Logger logger = Logger.getLogger(ConfigServerKeyStoreRefresher.class.getName());
+ private static final String KEY_STORE_ALIAS = "alias";
+ static final long MINIMUM_SECONDS_BETWEEN_REFRESH_RETRY = 3600;
+ static final String SIGNER_ALGORITHM = "SHA256withRSA";
+ static final String CONFIG_SERVER_CERTIFICATE_SIGNING_PATH = "/athenz/v1/provider/sign";
+
+ private final ScheduledExecutorService executor;
+ private final KeyStoreOptions keyStoreOptions;
+ private final Runnable keyStoreUpdatedCallback;
+ private final ConfigServerApi configServerApi;
+ private final Clock clock;
+ private final String hostname;
+
+ public ConfigServerKeyStoreRefresher(
+ KeyStoreOptions keyStoreOptions, Runnable keyStoreUpdatedCallback, ConfigServerApi configServerApi) {
+ this(keyStoreOptions, keyStoreUpdatedCallback, configServerApi, Executors.newScheduledThreadPool(0),
+ Clock.systemUTC(), HostName.getLocalhost());
+ }
+
+ ConfigServerKeyStoreRefresher(KeyStoreOptions keyStoreOptions,
+ Runnable keyStoreUpdatedCallback,
+ ConfigServerApi configServerApi,
+ ScheduledExecutorService executor,
+ Clock clock,
+ String hostname) {
+ this.keyStoreOptions = keyStoreOptions;
+ this.keyStoreUpdatedCallback = keyStoreUpdatedCallback;
+ this.configServerApi = configServerApi;
+ this.executor = executor;
+ this.clock = clock;
+ this.hostname = hostname;
+ }
+
+ public void start() {
+ executor.schedule(this::refresh, getSecondsUntilNextRefresh(), TimeUnit.SECONDS);
+ }
+
+ void refresh() {
+ try {
+ if (refreshKeyStoreIfNeeded()) {
+ keyStoreUpdatedCallback.run();
+ }
+ final long secondsUntilNextRefresh = getSecondsUntilNextRefresh();
+ executor.schedule(this::refresh, secondsUntilNextRefresh, TimeUnit.SECONDS);
+ logger.log(Level.INFO, "Successfully updated keystore, scheduled next refresh in " +
+ secondsUntilNextRefresh + "sec");
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Failed to update keystore on schedule, will try again in " +
+ MINIMUM_SECONDS_BETWEEN_REFRESH_RETRY + "sec", e);
+ executor.schedule(this::refresh, MINIMUM_SECONDS_BETWEEN_REFRESH_RETRY, TimeUnit.SECONDS);
+ }
+ }
+
+ public void stop() {
+ do {
+ try {
+ executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+ } catch (InterruptedException e1) {
+ logger.info("Interrupted while waiting for ConfigServerKeyStoreRefresher thread to shutdown");
+ }
+ } while (!executor.isTerminated());
+ }
+
+ public boolean refreshKeyStoreIfNeeded() throws
+ IOException, NoSuchAlgorithmException, OperatorCreationException, CertificateException, KeyStoreException, NoSuchProviderException {
+ if (!shouldRefreshCertificate()) return false;
+
+ KeyPair keyPair = generateKeyPair();
+ PKCS10CertificationRequest csr = generateCsr(keyPair, hostname);
+ X509Certificate certificate = sendCsr(csr);
+
+ storeCertificate(keyPair, certificate);
+ return true;
+ }
+
+ private long getSecondsUntilNextRefresh() {
+ long secondsUntilNextCheck = 0;
+ try {
+ secondsUntilNextCheck = getSecondsUntilCertificateShouldBeRefreshed();
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Failed to get remaining certificate lifetime", e);
+ }
+
+ return Math.max(MINIMUM_SECONDS_BETWEEN_REFRESH_RETRY, secondsUntilNextCheck);
+ }
+
+ private boolean shouldRefreshCertificate() {
+ try {
+ return getSecondsUntilCertificateShouldBeRefreshed() <= 0;
+ } catch (Exception e) { // We can't read the key store for whatever reason, let's just try to refresh it
+ return true;
+ }
+ }
+
+ /**
+ * Returns number of seconds until we should start trying to refresh the certificate, this should be
+ * well before the certificate actually expires so that we have enough time to retry without
+ * overloading config server.
+ */
+ private long getSecondsUntilCertificateShouldBeRefreshed()
+ throws NoSuchAlgorithmException, CertificateException, NoSuchProviderException, KeyStoreException, IOException {
+ X509Certificate cert = getConfigServerCertificate();
+ long notBefore = cert.getNotBefore().getTime() / 1000;
+ long notAfter = cert.getNotAfter().getTime() / 1000;
+ long now = clock.millis() / 1000;
+ long thirdOfLifetime = (notAfter - notBefore) / 3;
+
+ return Math.max(0, notBefore + thirdOfLifetime - now);
+ }
+
+ X509Certificate getConfigServerCertificate() throws NoSuchAlgorithmException, CertificateException, NoSuchProviderException, KeyStoreException, IOException {
+ return (X509Certificate) keyStoreOptions.loadKeyStore().getCertificate(KEY_STORE_ALIAS);
+ }
+
+ private void storeCertificate(KeyPair keyPair, X509Certificate certificate)
+ throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, NoSuchProviderException {
+ keyStoreOptions.path.getParent().toFile().mkdirs();
+ X509Certificate[] certificateChain = {certificate};
+
+ try (FileOutputStream fos = new FileOutputStream(keyStoreOptions.path.toFile())) {
+ KeyStore keyStore = keyStoreOptions.getKeyStoreInstance();
+ keyStore.load(null, null);
+ keyStore.setKeyEntry(KEY_STORE_ALIAS, keyPair.getPrivate(), keyStoreOptions.password, certificateChain);
+ keyStore.store(fos, keyStoreOptions.password);
+ }
+ }
+
+ private X509Certificate sendCsr(PKCS10CertificationRequest csr) {
+ CertificateSerializedPayload certificateSerializedPayload = configServerApi.post(
+ CONFIG_SERVER_CERTIFICATE_SIGNING_PATH,
+ new CsrSerializedPayload(csr),
+ CertificateSerializedPayload.class);
+
+ return certificateSerializedPayload.certificate;
+ }
+
+ static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
+ rsa.initialize(2048);
+ return rsa.genKeyPair();
+ }
+
+ private static PKCS10CertificationRequest generateCsr(KeyPair keyPair, String commonName)
+ throws NoSuchAlgorithmException, OperatorCreationException {
+ ContentSigner signer = new JcaContentSignerBuilder(SIGNER_ALGORITHM).build(keyPair.getPrivate());
+
+ return new JcaPKCS10CertificationRequestBuilder(new X500Name("CN=" + commonName), keyPair.getPublic())
+ .build(signer);
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java
new file mode 100644
index 00000000000..aa83fdc9e22
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/CsrSerializedPayload.java
@@ -0,0 +1,66 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.certificate;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.util.io.pem.PemObject;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+/**
+ * Contains PEM formatted Certificate Signing Request (CSR)
+ * TODO: Combine with its counterpart in athenz-identity-provider-service?
+ *
+ * @author freva
+ */
+public class CsrSerializedPayload {
+
+ @JsonProperty("csr") @JsonSerialize(using = CertificateRequestSerializer.class)
+ public final PKCS10CertificationRequest csr;
+
+ @JsonCreator
+ public CsrSerializedPayload(@JsonProperty("csr") PKCS10CertificationRequest csr) {
+ this.csr = csr;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CsrSerializedPayload that = (CsrSerializedPayload) o;
+
+ return csr.equals(that.csr);
+ }
+
+ @Override
+ public int hashCode() {
+ return csr.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "CsrSerializedPayload{" +
+ "csr='" + csr + '\'' +
+ '}';
+ }
+
+ public static class CertificateRequestSerializer extends JsonSerializer<PKCS10CertificationRequest> {
+ @Override
+ public void serialize(
+ PKCS10CertificationRequest csr, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
+ pemWriter.writeObject(new PemObject("CERTIFICATE REQUEST", csr.getEncoded()));
+ pemWriter.flush();
+ gen.writeString(stringWriter.toString());
+ }
+ }
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepository.java
index 9f4c6916b48..8012805f4d1 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepository.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepository.java
@@ -1,11 +1,10 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository;
import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes;
-import java.io.IOException;
import java.util.List;
import java.util.Optional;
@@ -13,7 +12,7 @@ import java.util.Optional;
* @author stiankri
*/
public interface NodeRepository {
- List<ContainerNodeSpec> getContainersToRun(String baseHostName) throws IOException;
+ List<ContainerNodeSpec> getContainersToRun(String baseHostName);
Optional<ContainerNodeSpec> getContainerNodeSpec(String hostName);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImpl.java
index ab7332ca556..f2152dffc0c 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImpl.java
@@ -1,22 +1,21 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository;
import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.dockerapi.DockerImage;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes;
-import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.GetAclResponse;
-import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.GetNodesResponse;
-import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.NodeMessageResponse;
-import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.UpdateNodeAttributesRequestBody;
-import com.yahoo.vespa.hosted.node.admin.noderepository.bindings.UpdateNodeAttributesResponse;
-import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
-import com.yahoo.vespa.hosted.node.admin.util.HttpException;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.GetAclResponse;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.GetNodesResponse;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.NodeMessageResponse;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.UpdateNodeAttributesRequestBody;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.UpdateNodeAttributesResponse;
+import com.yahoo.vespa.hosted.node.admin.configserver.HttpException;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import com.yahoo.vespa.hosted.provision.Node;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -30,44 +29,37 @@ import java.util.stream.Collectors;
public class NodeRepositoryImpl implements NodeRepository {
private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(NodeRepositoryImpl.class);
- private final ConfigServerHttpRequestExecutor requestExecutor;
+ private final ConfigServerApi configServerApi;
- public NodeRepositoryImpl(ConfigServerHttpRequestExecutor requestExecutor) {
- this.requestExecutor = requestExecutor;
+ public NodeRepositoryImpl(ConfigServerApi configServerApi) {
+ this.configServerApi = configServerApi;
}
@Override
- public List<ContainerNodeSpec> getContainersToRun(String baseHostName) throws IOException {
- try {
- final GetNodesResponse nodesForHost = requestExecutor.get(
- "/nodes/v2/node/?parentHost=" + baseHostName + "&recursive=true",
- GetNodesResponse.class);
-
- if (nodesForHost.nodes == null) {
- throw new IOException("Response didn't contain nodes element");
- }
- List<ContainerNodeSpec> nodes = new ArrayList<>(nodesForHost.nodes.size());
- for (GetNodesResponse.Node node : nodesForHost.nodes) {
- ContainerNodeSpec nodeSpec;
- try {
- nodeSpec = createContainerNodeSpec(node);
- } catch (IllegalArgumentException | NullPointerException e) {
- NODE_ADMIN_LOGGER.warning("Bad node received from node repo when requesting children of the "
- + baseHostName + " host: " + node, e);
- continue;
- }
- nodes.add(nodeSpec);
+ public List<ContainerNodeSpec> getContainersToRun(String baseHostName) {
+ final GetNodesResponse nodesForHost = configServerApi.get(
+ "/nodes/v2/node/?parentHost=" + baseHostName + "&recursive=true",
+ GetNodesResponse.class);
+
+ List<ContainerNodeSpec> nodes = new ArrayList<>(nodesForHost.nodes.size());
+ for (GetNodesResponse.Node node : nodesForHost.nodes) {
+ ContainerNodeSpec nodeSpec;
+ try {
+ nodeSpec = createContainerNodeSpec(node);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ NODE_ADMIN_LOGGER.warning("Bad node received from node repo when requesting children of the "
+ + baseHostName + " host: " + node, e);
+ continue;
}
- return nodes;
- } catch (Exception e) {
- throw new IOException(e);
+ nodes.add(nodeSpec);
}
+ return nodes;
}
@Override
public Optional<ContainerNodeSpec> getContainerNodeSpec(String hostName) {
try {
- GetNodesResponse.Node nodeResponse = requestExecutor.get("/nodes/v2/node/" + hostName,
+ GetNodesResponse.Node nodeResponse = configServerApi.get("/nodes/v2/node/" + hostName,
GetNodesResponse.Node.class);
if (nodeResponse == null) {
return Optional.empty();
@@ -82,7 +74,7 @@ public class NodeRepositoryImpl implements NodeRepository {
public List<ContainerAclSpec> getContainerAclSpecs(String hostName) {
try {
final String path = String.format("/nodes/v2/acl/%s?children=true", hostName);
- final GetAclResponse response = requestExecutor.get(path, GetAclResponse.class);
+ final GetAclResponse response = configServerApi.get(path, GetAclResponse.class);
return response.trustedNodes.stream()
.map(node -> new ContainerAclSpec(
node.hostname, node.ipAddress, ContainerName.fromHostname(node.trustedBy)))
@@ -142,7 +134,7 @@ public class NodeRepositoryImpl implements NodeRepository {
@Override
public void updateNodeAttributes(final String hostName, final NodeAttributes nodeAttributes) {
- UpdateNodeAttributesResponse response = requestExecutor.patch(
+ UpdateNodeAttributesResponse response = configServerApi.patch(
"/nodes/v2/node/" + hostName,
new UpdateNodeAttributesRequestBody(nodeAttributes),
UpdateNodeAttributesResponse.class);
@@ -166,7 +158,7 @@ public class NodeRepositoryImpl implements NodeRepository {
}
private void markNodeToState(String hostName, String state) {
- NodeMessageResponse response = requestExecutor.put(
+ NodeMessageResponse response = configServerApi.put(
"/nodes/v2/state/" + state + "/" + hostName,
Optional.empty(), /* body */
NodeMessageResponse.class);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetAclResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetAclResponse.java
index 254ab5fa3ba..b7762cf6aa9 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetAclResponse.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetAclResponse.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository.bindings;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetNodesResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetNodesResponse.java
index ce31c3c1b4c..c94b3836100 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/GetNodesResponse.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/GetNodesResponse.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository.bindings;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/NodeMessageResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java
index b8c903f863d..0f5e896c290 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/NodeMessageResponse.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository.bindings;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesRequestBody.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesRequestBody.java
index 7acd94a2947..28605bc3a8d 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesRequestBody.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesRequestBody.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository.bindings;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesResponse.java
index a11f3bf46a9..80c90e8311f 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/noderepository/bindings/UpdateNodeAttributesResponse.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/UpdateNodeAttributesResponse.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository.bindings;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/Orchestrator.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/Orchestrator.java
index d98378e194c..b5d41b7fbb4 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/Orchestrator.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/Orchestrator.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.orchestrator;
+package com.yahoo.vespa.hosted.node.admin.configserver.orchestrator;
import java.util.List;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorException.java
index 51d542622da..fe19da0c41c 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorException.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorException.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.orchestrator;
+package com.yahoo.vespa.hosted.node.admin.configserver.orchestrator;
@SuppressWarnings("serial")
public class OrchestratorException extends RuntimeException {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImpl.java
index 7093f3f12e7..0409004c6e6 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImpl.java
@@ -1,9 +1,9 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.orchestrator;
+package com.yahoo.vespa.hosted.node.admin.configserver.orchestrator;
-import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
-import com.yahoo.vespa.hosted.node.admin.util.HttpException;
+import com.yahoo.vespa.hosted.node.admin.configserver.HttpException;
import com.yahoo.vespa.orchestrator.restapi.HostApi;
import com.yahoo.vespa.orchestrator.restapi.HostSuspensionApi;
import com.yahoo.vespa.orchestrator.restapi.wire.BatchHostSuspendRequest;
@@ -25,17 +25,17 @@ public class OrchestratorImpl implements Orchestrator {
static final String ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API
= ORCHESTRATOR_PATH_PREFIX + HostSuspensionApi.PATH_PREFIX;
- private final ConfigServerHttpRequestExecutor requestExecutor;
+ private final ConfigServerApi configServerApi;
- public OrchestratorImpl(ConfigServerHttpRequestExecutor requestExecutor) {
- this.requestExecutor = requestExecutor;
+ public OrchestratorImpl(ConfigServerApi configServerApi) {
+ this.configServerApi = configServerApi;
}
@Override
public void suspend(final String hostName) {
UpdateHostResponse response;
try {
- response = requestExecutor.put(getSuspendPath(hostName),
+ response = configServerApi.put(getSuspendPath(hostName),
Optional.empty(), /* body */
UpdateHostResponse.class);
} catch (HttpException.NotFoundException n) {
@@ -56,7 +56,7 @@ public class OrchestratorImpl implements Orchestrator {
public void suspend(String parentHostName, List<String> hostNames) {
final BatchOperationResult batchOperationResult;
try {
- batchOperationResult = requestExecutor.put(
+ batchOperationResult = configServerApi.put(
ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class);
@@ -77,7 +77,7 @@ public class OrchestratorImpl implements Orchestrator {
UpdateHostResponse response;
try {
String path = getSuspendPath(hostName);
- response = requestExecutor.delete(path, UpdateHostResponse.class);
+ response = configServerApi.delete(path, UpdateHostResponse.class);
} catch (HttpException.NotFoundException n) {
throw new OrchestratorNotFoundException("Failed to resume " + hostName + ", host not found");
} catch (HttpException e) {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorNotFoundException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorNotFoundException.java
index 088152bd7a3..ac39f7c3280 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorNotFoundException.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorNotFoundException.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.orchestrator;
+package com.yahoo.vespa.hosted.node.admin.configserver.orchestrator;
@SuppressWarnings("serial")
public class OrchestratorNotFoundException extends OrchestratorException {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
index 2947ef68ba4..a453ea46ffd 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java
@@ -10,7 +10,7 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.Chain;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.Command;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.FlushCommand;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.iptables.PolicyCommand;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import java.util.HashMap;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java
index cdc7c9badaf..8f6e5bf9748 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java
@@ -7,16 +7,15 @@ import com.yahoo.concurrent.classlock.ClassLocking;
import com.yahoo.concurrent.classlock.LockInterruptException;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
-import com.yahoo.vespa.hosted.node.admin.util.HttpException;
+import com.yahoo.vespa.hosted.node.admin.configserver.HttpException;
import com.yahoo.vespa.hosted.provision.Node;
-import java.io.IOException;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
@@ -269,35 +268,22 @@ public class NodeAdminStateUpdaterImpl implements NodeAdminStateUpdater {
log.info("Frozen, skipping fetching info from node repository");
return;
}
- final List<ContainerNodeSpec> containersToRun;
- try {
- containersToRun = nodeRepository.getContainersToRun(dockerHostHostName);
- } catch (Exception e) {
- log.log(LogLevel.WARNING, "Failed fetching container info from node repository", e);
- return;
- }
- if (containersToRun == null) {
- log.warning("Got null from node repository");
- return;
- }
+
try {
+ final List<ContainerNodeSpec> containersToRun = nodeRepository.getContainersToRun(dockerHostHostName);
nodeAdmin.refreshContainersToRun(containersToRun);
} catch (Exception e) {
- log.log(LogLevel.WARNING, "Failed updating node admin: ", e);
+ log.log(LogLevel.WARNING, "Failed to update which containers should be running", e);
}
}
}
private List<String> getNodesInActiveState() {
- try {
- return nodeRepository.getContainersToRun(dockerHostHostName)
- .stream()
- .filter(nodespec -> nodespec.nodeState == Node.State.active)
- .map(nodespec -> nodespec.hostname)
- .collect(Collectors.toList());
- } catch (IOException e) {
- throw new RuntimeException("Failed to get nodes from node repo", e);
- }
+ return nodeRepository.getContainersToRun(dockerHostHostName)
+ .stream()
+ .filter(nodespec -> nodespec.nodeState == Node.State.active)
+ .map(nodespec -> nodespec.hostname)
+ .collect(Collectors.toList());
}
public void start() {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
index f05a4054924..893054e5ac0 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
@@ -17,9 +17,9 @@ import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.component.Environment;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import com.yahoo.vespa.hosted.provision.Node;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/KeyStoreOptions.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/KeyStoreOptions.java
index 643abde101b..1115f6dca91 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/KeyStoreOptions.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/util/KeyStoreOptions.java
@@ -1,16 +1,45 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.util;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateException;
+import java.util.Optional;
public class KeyStoreOptions {
public final Path path;
public final char[] password;
public final String type;
+ private final Optional<String> provider;
public KeyStoreOptions(Path path, char[] password, String type) {
+ this(path, password, type, null);
+ }
+
+ public KeyStoreOptions(Path path, char[] password, String type, String provider) {
this.path = path;
this.password = password;
this.type = type;
+ this.provider = Optional.ofNullable(provider);
+ }
+
+ public KeyStore loadKeyStore()
+ throws IOException, NoSuchProviderException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
+ try (FileInputStream in = new FileInputStream(path.toFile())) {
+ KeyStore keyStore = getKeyStoreInstance();
+ keyStore.load(in, password);
+ return keyStore;
+ }
+ }
+
+ public KeyStore getKeyStoreInstance() throws NoSuchProviderException, KeyStoreException {
+ return provider.isPresent() ?
+ KeyStore.getInstance(type, provider.get()) :
+ KeyStore.getInstance(type);
}
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImplTest.java
index 175d3a9a051..f39a64d2dee 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/util/ConfigServerHttpRequestExecutorTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerApiImplTest.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.util;
+package com.yahoo.vespa.hosted.node.admin.configserver;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -34,7 +34,7 @@ import static org.mockito.Mockito.when;
*
* @author dybis
*/
-public class ConfigServerHttpRequestExecutorTest {
+public class ConfigServerApiImplTest {
@JsonIgnoreProperties(ignoreUnknown = true)
public static class TestPojo {
@@ -49,7 +49,7 @@ public class ConfigServerHttpRequestExecutorTest {
private final List<URI> configServers = Arrays.asList(URI.create(uri1), URI.create(uri2));
private final StringBuilder mockLog = new StringBuilder();
- private ConfigServerHttpRequestExecutor executor;
+ private ConfigServerApiImpl executor;
private int mockReturnCode = 200;
@Before
@@ -72,7 +72,7 @@ public class ConfigServerHttpRequestExecutorTest {
return response;
});
- executor = new ConfigServerHttpRequestExecutor(configServers, httpMock);
+ executor = new ConfigServerApiImpl(configServers, httpMock);
}
@Test
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java
new file mode 100644
index 00000000000..f9f8b230154
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/certificate/ConfigServerKeyStoreRefresherTest.java
@@ -0,0 +1,162 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.certificate;
+
+import com.yahoo.test.ManualClock;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi;
+import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.Date;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author freva
+ */
+public class ConfigServerKeyStoreRefresherTest {
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private final ManualClock clock = new ManualClock();
+ private final String commonName = "CertificateRefresherTest";
+ private final Duration certificateExpiration = Duration.ofDays(6);
+ private final ConfigServerApi configServerApi = mock(ConfigServerApi.class);
+ private final Runnable keyStoreUpdatedCallback = mock(Runnable.class);
+ private final ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+ private KeyStoreOptions keyStoreOptions;
+
+ @Before
+ public void setup() {
+ keyStoreOptions = new KeyStoreOptions(
+ tempFolder.getRoot().toPath().resolve("some/path/keystore.p12"), new char[0], "PKCS12", null);
+ }
+
+ @Test
+ public void manually_trigger_certificate_refresh() throws Exception {
+ X509Certificate firstCertificate = mockConfigServerCertificateSigning(1);
+
+ ConfigServerKeyStoreRefresher keyStoreRefresher = new ConfigServerKeyStoreRefresher(
+ keyStoreOptions, keyStoreUpdatedCallback, configServerApi, executor, clock, commonName);
+
+ // No keystore previously existed, so a new one should be written
+ assertTrue(keyStoreRefresher.refreshKeyStoreIfNeeded());
+ assertEquals(firstCertificate, keyStoreRefresher.getConfigServerCertificate());
+
+ // Calling it again before a third of certificate lifetime has passed has no effect
+ assertFalse(keyStoreRefresher.refreshKeyStoreIfNeeded());
+ assertEquals(firstCertificate, keyStoreRefresher.getConfigServerCertificate());
+
+ // After a third of the expiration time passes, we should refresh the certificate
+ clock.advance(certificateExpiration.dividedBy(3).plusSeconds(1));
+ X509Certificate secondCertificate = mockConfigServerCertificateSigning(2);
+ assertTrue(keyStoreRefresher.refreshKeyStoreIfNeeded());
+ assertEquals(secondCertificate, keyStoreRefresher.getConfigServerCertificate());
+
+ verify(configServerApi, times(2))
+ .post(eq(ConfigServerKeyStoreRefresher.CONFIG_SERVER_CERTIFICATE_SIGNING_PATH), any(), any());
+
+ // We're just triggering refresh manually, so callback and executor should not have been touched
+ verifyZeroInteractions(keyStoreUpdatedCallback);
+ verifyZeroInteractions(executor);
+ }
+
+ @Test
+ public void certificate_refresh_schedule_test() throws Exception {
+ ConfigServerKeyStoreRefresher keyStoreRefresher = new ConfigServerKeyStoreRefresher(
+ keyStoreOptions, keyStoreUpdatedCallback, configServerApi, executor, clock, commonName);
+
+ // No keystore exist, so refresh once
+ mockConfigServerCertificateSigning(1);
+ assertTrue(keyStoreRefresher.refreshKeyStoreIfNeeded());
+
+ // Start automatic refreshment, since keystore was just written, next check should be in 1/3rd of
+ // certificate lifetime, which is in 2 days.
+ keyStoreRefresher.start();
+ Duration nextExpectedExecution = Duration.ofDays(2);
+ verify(executor, times(1)).schedule(any(Runnable.class), eq(nextExpectedExecution.getSeconds()), eq(TimeUnit.SECONDS));
+
+ // First automatic refreshment goes without any problems
+ clock.advance(nextExpectedExecution);
+ mockConfigServerCertificateSigning(2);
+ keyStoreRefresher.refresh();
+ verify(executor, times(2)).schedule(any(Runnable.class), eq(nextExpectedExecution.getSeconds()), eq(TimeUnit.SECONDS));
+ verify(keyStoreUpdatedCallback).run();
+
+ // We fail to refresh the certificate, wait minimum amount of time and try again
+ clock.advance(nextExpectedExecution);
+ mockConfigServerCertificateSigningFailure(new RuntimeException());
+ keyStoreRefresher.refresh();
+ nextExpectedExecution = Duration.ofSeconds(ConfigServerKeyStoreRefresher.MINIMUM_SECONDS_BETWEEN_REFRESH_RETRY);
+ verify(executor, times(1)).schedule(any(Runnable.class), eq(nextExpectedExecution.getSeconds()), eq(TimeUnit.SECONDS));
+
+ clock.advance(nextExpectedExecution);
+ keyStoreRefresher.refresh();
+ verify(executor, times(2)).schedule(any(Runnable.class), eq(nextExpectedExecution.getSeconds()), eq(TimeUnit.SECONDS));
+ verifyNoMoreInteractions(keyStoreUpdatedCallback); // Callback not called after the last 2 failures
+
+ clock.advance(nextExpectedExecution);
+ mockConfigServerCertificateSigning(3);
+ keyStoreRefresher.refresh();
+ nextExpectedExecution = Duration.ofDays(2);
+ verify(executor, times(3)).schedule(any(Runnable.class), eq(nextExpectedExecution.getSeconds()), eq(TimeUnit.SECONDS));
+ verify(keyStoreUpdatedCallback, times(2)).run();
+ }
+
+ private X509Certificate mockConfigServerCertificateSigning(int serial) throws Exception {
+ X509Certificate certificate = makeCertificate(serial);
+
+ when(configServerApi.post(eq(ConfigServerKeyStoreRefresher.CONFIG_SERVER_CERTIFICATE_SIGNING_PATH), any(), any()))
+ .thenReturn(new CertificateSerializedPayload(certificate));
+ return certificate;
+ }
+
+ private void mockConfigServerCertificateSigningFailure(Exception exception) throws Exception {
+ when(configServerApi.post(eq(ConfigServerKeyStoreRefresher.CONFIG_SERVER_CERTIFICATE_SIGNING_PATH), any(), any()))
+ .thenThrow(exception);
+ }
+
+ private X509Certificate makeCertificate(int serial) throws Exception {
+ try {
+ KeyPair keyPair = ConfigServerKeyStoreRefresher.generateKeyPair();
+ X500Name subject = new X500Name("CN=" + commonName);
+ Date notBefore = Date.from(clock.instant());
+ Date notAfter = Date.from(clock.instant().plus(certificateExpiration));
+
+ JcaX509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(subject,
+ BigInteger.valueOf(serial), notBefore, notAfter, subject, keyPair.getPublic());
+ ContentSigner sigGen = new JcaContentSignerBuilder(ConfigServerKeyStoreRefresher.SIGNER_ALGORITHM)
+ .build(keyPair.getPrivate());
+ return new JcaX509CertificateConverter()
+ .setProvider(new BouncyCastleProvider())
+ .getCertificate(certGen.build(sigGen));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImplTest.java
index 949b4ccdf78..85e101714e8 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/noderepository/NodeRepositoryImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeRepositoryImplTest.java
@@ -1,13 +1,13 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.noderepository;
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository;
import com.yahoo.application.Networking;
import com.yahoo.application.container.JDisc;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.dockerapi.DockerImage;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApiImpl;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes;
-import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.testutils.ContainerConfig;
@@ -36,7 +36,7 @@ import static org.junit.Assert.fail;
*/
public class NodeRepositoryImplTest {
private JDisc container;
- private ConfigServerHttpRequestExecutor requestExecutor;
+ private ConfigServerApiImpl configServerApi;
private int findRandomOpenPort() throws IOException {
@@ -63,8 +63,7 @@ public class NodeRepositoryImplTest {
try {
final int port = findRandomOpenPort();
container = JDisc.fromServicesXml(ContainerConfig.servicesXmlV2(port), Networking.enable);
- requestExecutor = ConfigServerHttpRequestExecutor.create(
- Collections.singleton(URI.create("http://127.0.0.1:" + port)), Optional.empty(), Optional.empty(), Optional.empty());
+ configServerApi = new ConfigServerApiImpl(Collections.singleton(URI.create("http://127.0.0.1:" + port)));
return;
} catch (RuntimeException e) {
lastException = e;
@@ -75,7 +74,7 @@ public class NodeRepositoryImplTest {
private void waitForJdiscContainerToServe() throws InterruptedException {
Instant start = Instant.now();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi);
while (Instant.now().minusSeconds(120).isBefore(start)) {
try {
nodeRepositoryApi.getContainersToRun("foobar");
@@ -95,9 +94,9 @@ public class NodeRepositoryImplTest {
}
@Test
- public void testGetContainersToRunApi() throws IOException, InterruptedException {
+ public void testGetContainersToRunApi() throws InterruptedException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi);
String dockerHostHostname = "dockerhost1.yahoo.com";
final List<ContainerNodeSpec> containersToRun = nodeRepositoryApi.getContainersToRun(dockerHostHostname);
@@ -116,7 +115,7 @@ public class NodeRepositoryImplTest {
@Test
public void testGetContainer() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi);
String hostname = "host4.yahoo.com";
Optional<ContainerNodeSpec> nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname);
assertThat(nodeSpec.isPresent(), is(true));
@@ -126,7 +125,7 @@ public class NodeRepositoryImplTest {
@Test
public void testGetContainerForNonExistingNode() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi);
String hostname = "host-that-does-not-exist";
Optional<ContainerNodeSpec> nodeSpec = nodeRepositoryApi.getContainerNodeSpec(hostname);
assertFalse(nodeSpec.isPresent());
@@ -135,7 +134,7 @@ public class NodeRepositoryImplTest {
@Test
public void testUpdateNodeAttributes() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi);
String hostname = "host4.yahoo.com";
nodeRepositoryApi.updateNodeAttributes(
hostname,
@@ -148,7 +147,7 @@ public class NodeRepositoryImplTest {
@Test(expected = RuntimeException.class)
public void testUpdateNodeAttributesWithBadValue() throws InterruptedException, IOException {
waitForJdiscContainerToServe();
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi);
String hostname = "host4.yahoo.com";
nodeRepositoryApi.updateNodeAttributes(
hostname,
@@ -160,7 +159,7 @@ public class NodeRepositoryImplTest {
@Test
public void testMarkAsReady() throws InterruptedException, IOException {
- NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(requestExecutor);
+ NodeRepository nodeRepositoryApi = new NodeRepositoryImpl(configServerApi);
waitForJdiscContainerToServe();
nodeRepositoryApi.markAsDirty("host5.yahoo.com");
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImplTest.java
index 779a0a6a376..2d355c93c09 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/orchestrator/OrchestratorImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/orchestrator/OrchestratorImplTest.java
@@ -1,8 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.orchestrator;
+package com.yahoo.vespa.hosted.node.admin.configserver.orchestrator;
-import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
-import com.yahoo.vespa.hosted.node.admin.util.HttpException;
+import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApiImpl;
+import com.yahoo.vespa.hosted.node.admin.configserver.HttpException;
import com.yahoo.vespa.orchestrator.restapi.wire.BatchHostSuspendRequest;
import com.yahoo.vespa.orchestrator.restapi.wire.BatchOperationResult;
import com.yahoo.vespa.orchestrator.restapi.wire.HostStateChangeDenialReason;
@@ -23,12 +23,12 @@ import static org.mockito.Mockito.when;
public class OrchestratorImplTest {
private static final String hostName = "host123.yahoo.com";
- private final ConfigServerHttpRequestExecutor requestExecutor = mock(ConfigServerHttpRequestExecutor.class);
- private final OrchestratorImpl orchestrator = new OrchestratorImpl(requestExecutor);
+ private final ConfigServerApiImpl configServerApi = mock(ConfigServerApiImpl.class);
+ private final OrchestratorImpl orchestrator = new OrchestratorImpl(configServerApi);
@Test
public void testSuspendCall() {
- when(requestExecutor.put(
+ when(configServerApi.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
Optional.empty(),
UpdateHostResponse.class
@@ -39,7 +39,7 @@ public class OrchestratorImplTest {
@Test(expected=OrchestratorException.class)
public void testSuspendCallWithFailureReason() {
- when(requestExecutor.put(
+ when(configServerApi.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
Optional.empty(),
UpdateHostResponse.class
@@ -50,7 +50,7 @@ public class OrchestratorImplTest {
@Test(expected=OrchestratorNotFoundException.class)
public void testSuspendCallWithNotFound() {
- when(requestExecutor.put(
+ when(configServerApi.put(
any(String.class),
any(),
any()
@@ -61,7 +61,7 @@ public class OrchestratorImplTest {
@Test(expected=RuntimeException.class)
public void testSuspendCallWithSomeOtherException() {
- when(requestExecutor.put(
+ when(configServerApi.put(
any(String.class),
any(),
any()
@@ -73,7 +73,7 @@ public class OrchestratorImplTest {
@Test
public void testResumeCall() {
- when(requestExecutor.delete(
+ when(configServerApi.delete(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
UpdateHostResponse.class
)).thenReturn(new UpdateHostResponse(hostName, null));
@@ -83,7 +83,7 @@ public class OrchestratorImplTest {
@Test(expected=OrchestratorException.class)
public void testResumeCallWithFailureReason() {
- when(requestExecutor.delete(
+ when(configServerApi.delete(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_API + "/" + hostName+ "/suspended",
UpdateHostResponse.class
)).thenReturn(new UpdateHostResponse(hostName, new HostStateChangeDenialReason("hostname", "fail")));
@@ -93,7 +93,7 @@ public class OrchestratorImplTest {
@Test(expected=OrchestratorNotFoundException.class)
public void testResumeCallWithNotFound() {
- when(requestExecutor.delete(
+ when(configServerApi.delete(
any(String.class),
any()
)).thenThrow(new HttpException.NotFoundException("Not Found"));
@@ -103,7 +103,7 @@ public class OrchestratorImplTest {
@Test(expected=RuntimeException.class)
public void testResumeCallWithSomeOtherException() {
- when(requestExecutor.put(
+ when(configServerApi.put(
any(String.class),
any(),
any()
@@ -118,7 +118,7 @@ public class OrchestratorImplTest {
String parentHostName = "host1.test.yahoo.com";
List<String> hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com");
- when(requestExecutor.put(
+ when(configServerApi.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class
@@ -133,7 +133,7 @@ public class OrchestratorImplTest {
List<String> hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com");
String failureReason = "Failed to suspend";
- when(requestExecutor.put(
+ when(configServerApi.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class
@@ -148,7 +148,7 @@ public class OrchestratorImplTest {
List<String> hostNames = Arrays.asList("a1.host1.test.yahoo.com", "a2.host1.test.yahoo.com");
String exceptionMessage = "Exception: Something crashed!";
- when(requestExecutor.put(
+ when(configServerApi.put(
OrchestratorImpl.ORCHESTRATOR_PATH_PREFIX_HOST_SUSPENSION_API,
Optional.of(new BatchHostSuspendRequest(parentHostName, hostNames)),
BatchOperationResult.class
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java
index 2d2a622f8f0..8557da75ee9 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeRepoMock.java
@@ -4,10 +4,9 @@ package com.yahoo.vespa.hosted.node.admin.integrationTests;
import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.provision.Node;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -32,7 +31,7 @@ public class NodeRepoMock implements NodeRepository {
}
@Override
- public List<ContainerNodeSpec> getContainersToRun(String dockerHostHostname) throws IOException {
+ public List<ContainerNodeSpec> getContainersToRun(String dockerHostHostname) {
synchronized (monitor) {
return new ArrayList<>(containerNodeSpecsByHostname.values());
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java
index dc285bb27ce..469022cec56 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/OrchestratorMock.java
@@ -1,7 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.integrationTests;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
import java.util.List;
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java
index e4b6558e8e1..3a163c94caa 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java
@@ -18,9 +18,9 @@ import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdaterImpl;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.node.admin.component.Environment;
import com.yahoo.vespa.hosted.provision.Node;
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java
index a699377b4c3..d50f869617a 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java
@@ -6,7 +6,7 @@ import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.dockerapi.DockerImage;
import com.yahoo.vespa.hosted.node.admin.ContainerAclSpec;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
import org.junit.Before;
import org.junit.Test;
import org.mockito.verification.VerificationMode;
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java
index 1ab24fe8f9a..c9985247018 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java
@@ -4,14 +4,13 @@ package com.yahoo.vespa.hosted.node.admin.nodeadmin;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.provision.Node;
import org.junit.Test;
-import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -53,7 +52,7 @@ public class NodeAdminStateUpdaterImplTest {
@Test
- public void testStateConvergence() throws IOException {
+ public void testStateConvergence() {
mockNodeRepo(4);
List<String> activeHostnames = nodeRepository.getContainersToRun(parentHostname).stream()
.map(node -> node.hostname)
@@ -155,7 +154,7 @@ public class NodeAdminStateUpdaterImplTest {
}
@Test
- public void half_transition_revert() throws IOException {
+ public void half_transition_revert() {
mockNodeRepo(3);
// Initially everything is frozen to force convergence
@@ -182,7 +181,7 @@ public class NodeAdminStateUpdaterImplTest {
verify(nodeAdmin, times(2)).setFrozen(eq(false)); // Make sure that we unfreeze!
}
- private void mockNodeRepo(int numberOfNodes) throws IOException {
+ private void mockNodeRepo(int numberOfNodes) {
List<ContainerNodeSpec> containersToRun = IntStream.range(0, numberOfNodes)
.mapToObj(i -> new ContainerNodeSpec.Builder()
.hostname("host" + i + ".test.yahoo.com")
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
index f1852fda8a3..fb9303ea382 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
@@ -15,8 +15,8 @@ import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
-import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
-import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.component.Environment;
import com.yahoo.vespa.hosted.node.admin.util.InetAddressResolver;
import com.yahoo.vespa.hosted.node.admin.component.PathResolver;
@@ -95,7 +95,7 @@ public class NodeAgentImplTest {
@Test
- public void upToDateContainerIsUntouched() throws Exception {
+ public void upToDateContainerIsUntouched() {
final long restartGeneration = 1;
final long rebootGeneration = 0;
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
@@ -135,7 +135,7 @@ public class NodeAgentImplTest {
}
@Test
- public void verifyRemoveOldFilesIfDiskFull() throws Exception {
+ public void verifyRemoveOldFilesIfDiskFull() {
final long restartGeneration = 1;
final long rebootGeneration = 0;
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
@@ -199,7 +199,7 @@ public class NodeAgentImplTest {
}
@Test
- public void containerIsNotStoppedIfNewImageMustBePulled() throws Exception {
+ public void containerIsNotStoppedIfNewImageMustBePulled() {
final DockerImage newDockerImage = new DockerImage("new-image");
final long wantedRestartGeneration = 2;
final long currentRestartGeneration = 1;
@@ -230,7 +230,7 @@ public class NodeAgentImplTest {
}
@Test
- public void containerIsRestartedIfFlavorChanged() throws Exception {
+ public void containerIsRestartedIfFlavorChanged() {
final long wantedRestartGeneration = 1;
final long currentRestartGeneration = 1;
ContainerNodeSpec.Builder specBuilder = nodeSpecBuilder
@@ -268,7 +268,7 @@ public class NodeAgentImplTest {
}
@Test
- public void noRestartIfOrchestratorSuspendFails() throws Exception {
+ public void noRestartIfOrchestratorSuspendFails() {
final long wantedRestartGeneration = 2;
final long currentRestartGeneration = 1;
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
@@ -294,7 +294,7 @@ public class NodeAgentImplTest {
}
@Test
- public void failedNodeRunningContainerShouldStillBeRunning() throws Exception {
+ public void failedNodeRunningContainerShouldStillBeRunning() {
final long restartGeneration = 1;
final long rebootGeneration = 0;
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
@@ -324,7 +324,7 @@ public class NodeAgentImplTest {
}
@Test
- public void readyNodeLeadsToNoAction() throws Exception {
+ public void readyNodeLeadsToNoAction() {
final long restartGeneration = 1;
final long rebootGeneration = 0;
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
@@ -356,7 +356,7 @@ public class NodeAgentImplTest {
}
@Test
- public void inactiveNodeRunningContainerShouldStillBeRunning() throws Exception {
+ public void inactiveNodeRunningContainerShouldStillBeRunning() {
final long restartGeneration = 1;
final long rebootGeneration = 0;
@@ -390,7 +390,7 @@ public class NodeAgentImplTest {
}
@Test
- public void reservedNodeDoesNotUpdateNodeRepoWithVersion() throws Exception {
+ public void reservedNodeDoesNotUpdateNodeRepoWithVersion() {
final long restartGeneration = 1;
final long rebootGeneration = 0;
@@ -462,7 +462,7 @@ public class NodeAgentImplTest {
}
@Test
- public void provisionedNodeIsMarkedAsDirty() throws Exception {
+ public void provisionedNodeIsMarkedAsDirty() {
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
.wantedDockerImage(dockerImage)
.nodeState(Node.State.provisioned)
@@ -497,7 +497,7 @@ public class NodeAgentImplTest {
}
@Test
- public void resumeProgramRunsUntilSuccess() throws Exception {
+ public void resumeProgramRunsUntilSuccess() {
final long restartGeneration = 1;
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
.wantedDockerImage(dockerImage)
@@ -626,7 +626,7 @@ public class NodeAgentImplTest {
}
@Test
- public void testGetRelevantMetricsForReadyNode() throws Exception {
+ public void testGetRelevantMetricsForReadyNode() {
final ContainerNodeSpec nodeSpec = nodeSpecBuilder
.nodeState(Node.State.ready)
.build();