summaryrefslogtreecommitdiffstats
path: root/orchestrator
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@yahoo-inc.com>2017-04-28 17:29:57 +0200
committerHåkon Hallingstad <hakon@yahoo-inc.com>2017-04-28 17:29:57 +0200
commit019a33d591366a025fa012951e9dbac21f83c1de (patch)
treefa65aa8ecf5a139eff855eabc22b8ee86eab9565 /orchestrator
parentc00a3b8a6318e7dd6c7c09b8d87042734dfdc382 (diff)
Adds classes to give the Orchestrator policy classes a simplified view of Vespa.
This should be a no-op. The only changes that actually could have an impact are the changes to getting the cluster controllers, but it should be functionally equivalent. This PR will make it easier to change the Orchestrator policy to allow suspending several nodes (NodeGroup) in an application on a single Docker host.
Diffstat (limited to 'orchestrator')
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java3
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorUtil.java2
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactory.java6
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java11
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactory.java22
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApi.java22
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImpl.java139
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java28
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java186
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/NodeGroup.java75
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNode.java11
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNodeImpl.java122
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/VespaModelUtil.java (renamed from orchestrator/src/main/java/com/yahoo/vespa/orchestrator/VespaModelUtil.java)26
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicy.java5
-rw-r--r--orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/ServiceClusterSuspendPolicy.java2
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyInstanceLookupService.java1
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactoryMock.java7
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactoryTest.java25
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImplTest.java321
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java133
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ModelTestUtils.java92
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/NodeGroupTest.java33
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/VespaModelUtilTest.java (renamed from orchestrator/src/test/java/com/yahoo/vespa/orchestrator/VespaModelUtilTest.java)12
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicyTest.java12
24 files changed, 1226 insertions, 70 deletions
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java
index 7a9623ecffd..8019ee7725a 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorImpl.java
@@ -13,6 +13,7 @@ import com.yahoo.vespa.orchestrator.controller.ClusterControllerClient;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerState;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerStateResponse;
+import com.yahoo.vespa.orchestrator.model.VespaModelUtil;
import com.yahoo.vespa.orchestrator.policy.BatchHostStateChangeDeniedException;
import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException;
import com.yahoo.vespa.orchestrator.policy.HostedVespaPolicy;
@@ -294,7 +295,7 @@ public class OrchestratorImpl implements Orchestrator {
contentClusterIds,application.applicationInstanceId(),state));
for (ClusterId clusterId : contentClusterIds) {
ClusterControllerClient client = clusterControllerClientFactory.createClient(
- VespaModelUtil.getClusterControllerInstances(application, clusterId),
+ VespaModelUtil.getClusterControllerInstancesInOrder(application, clusterId),
clusterId.s());
try {
ClusterControllerStateResponse response = client.setApplicationState(state);
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorUtil.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorUtil.java
index 565a96de13b..aa5b4fd78c0 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorUtil.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestratorUtil.java
@@ -126,7 +126,7 @@ public class OrchestratorUtil {
public static ApplicationId toApplicationId(ApplicationInstanceReference appRef) {
- String appNameStr = appRef.toString();
+ String appNameStr = appRef.asString();
String[] appNameParts = appNameStr.split(":");
// Env, region and instance seems to be optional due to the hardcoded config server app
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactory.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactory.java
index e25957d3eaf..56bba91a016 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactory.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactory.java
@@ -1,15 +1,15 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.orchestrator.controller;
-import com.yahoo.vespa.applicationmodel.ServiceInstance;
+import com.yahoo.vespa.applicationmodel.HostName;
-import java.util.Collection;
+import java.util.List;
/**
* @author bakksjo
*/
public interface ClusterControllerClientFactory {
- ClusterControllerClient createClient(Collection<? extends ServiceInstance<?>> clusterControllers, String clusterName);
+ ClusterControllerClient createClient(List<HostName> clusterControllers, String clusterName);
}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java
index 375b0a84eb3..8c70622cfba 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/RetryingClusterControllerClientFactory.java
@@ -3,13 +3,12 @@ package com.yahoo.vespa.orchestrator.controller;
import com.google.inject.Inject;
import com.yahoo.vespa.applicationmodel.HostName;
-import com.yahoo.vespa.applicationmodel.ServiceInstance;
import com.yahoo.vespa.jaxrs.client.JaxRsClientFactory;
import com.yahoo.vespa.jaxrs.client.JaxRsStrategy;
import com.yahoo.vespa.jaxrs.client.JaxRsStrategyFactory;
import com.yahoo.vespa.jaxrs.client.JerseyJaxRsClientFactory;
-import java.util.Collection;
+import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -36,13 +35,11 @@ public class RetryingClusterControllerClientFactory implements ClusterController
}
@Override
- public ClusterControllerClient createClient(Collection<? extends ServiceInstance<?>> clusterControllers,
+ public ClusterControllerClient createClient(List<HostName> clusterControllers,
String clusterName) {
- Set<HostName> hostNames = clusterControllers.stream()
- .map(s -> s.hostName())
- .collect(Collectors.toSet());
+ Set<HostName> clusterControllerSet = clusterControllers.stream().collect(Collectors.toSet());
JaxRsStrategy<ClusterControllerJaxRsApi> jaxRsApi
- = new JaxRsStrategyFactory(hostNames, HARDCODED_CLUSTERCONTROLLER_PORT, jaxRsClientFactory)
+ = new JaxRsStrategyFactory(clusterControllerSet, HARDCODED_CLUSTERCONTROLLER_PORT, jaxRsClientFactory)
.apiWithRetries(ClusterControllerJaxRsApi.class, CLUSTERCONTROLLER_API_PATH);
return new ClusterControllerClientImpl(jaxRsApi, clusterName);
}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactory.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactory.java
index 8a245edd187..66925b5e3ce 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactory.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactory.java
@@ -2,18 +2,14 @@
package com.yahoo.vespa.orchestrator.controller;
import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.jaxrs.client.JaxRsClientFactory;
import com.yahoo.vespa.jaxrs.client.JaxRsStrategy;
import com.yahoo.vespa.jaxrs.client.NoRetryJaxRsStrategy;
-import com.yahoo.vespa.applicationmodel.HostName;
-import com.yahoo.vespa.applicationmodel.ServiceInstance;
-import java.util.Collection;
-import java.util.Comparator;
+import java.util.List;
import java.util.logging.Logger;
-import static com.yahoo.vespa.orchestrator.VespaModelUtil.getClusterControllerIndex;
-
/**
* @author bakksjo
*/
@@ -24,10 +20,6 @@ public class SingleInstanceClusterControllerClientFactory implements ClusterCont
private static final Logger log = Logger.getLogger(SingleInstanceClusterControllerClientFactory.class.getName());
- private static final Comparator<ServiceInstance<?>> CLUSTER_CONTROLLER_INDEX_COMPARATOR = Comparator.comparing(
- serviceInstance ->
- getClusterControllerIndex(serviceInstance.configId()));
-
private JaxRsClientFactory jaxRsClientFactory;
public SingleInstanceClusterControllerClientFactory(JaxRsClientFactory jaxRsClientFactory) {
@@ -35,12 +27,12 @@ public class SingleInstanceClusterControllerClientFactory implements ClusterCont
}
@Override
- public ClusterControllerClient createClient(Collection<? extends ServiceInstance<?>> clusterControllers,
+ public ClusterControllerClient createClient(List<HostName> clusterControllers,
String clusterName) {
- ServiceInstance<?> serviceInstance = clusterControllers.stream()
- .min(CLUSTER_CONTROLLER_INDEX_COMPARATOR)
- .orElseThrow(() -> new IllegalArgumentException("No cluster controller instances found"));
- HostName controllerHostName = serviceInstance.hostName();
+ if (clusterControllers.isEmpty()) {
+ throw new IllegalArgumentException("No cluster controller instances found");
+ }
+ HostName controllerHostName = clusterControllers.iterator().next();
int port = CLUSTERCONTROLLER_HARDCODED_PORT; // TODO: Get this from service monitor.
log.log(LogLevel.DEBUG, () ->
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApi.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApi.java
new file mode 100644
index 00000000000..0ac602b106e
--- /dev/null
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApi.java
@@ -0,0 +1,22 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.orchestrator.status.HostStatus;
+
+import java.util.List;
+
+/**
+ * The API a Policy has access to
+ */
+public interface ApplicationApi {
+ String applicationInfo();
+
+ List<ClusterApi> getClusters();
+
+ void setHostState(HostName hostName, HostStatus status);
+ List<HostName> getNodesInGroupWithStatus(HostStatus status);
+
+ List<StorageNode> getUpStorageNodesInGroupInClusterOrder();
+ List<StorageNode> getStorageNodesAllowedToBeDownInGroupInReverseClusterOrder();
+}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImpl.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImpl.java
new file mode 100644
index 00000000000..5f84c80a051
--- /dev/null
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImpl.java
@@ -0,0 +1,139 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.ApplicationInstance;
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.applicationmodel.ServiceCluster;
+import com.yahoo.vespa.applicationmodel.ServiceInstance;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory;
+import com.yahoo.vespa.orchestrator.status.HostStatus;
+import com.yahoo.vespa.orchestrator.status.MutableStatusRegistry;
+import com.yahoo.vespa.orchestrator.status.ReadOnlyStatusRegistry;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.yahoo.vespa.orchestrator.OrchestratorUtil.getHostsUsedByApplicationInstance;
+
+public class ApplicationApiImpl implements ApplicationApi {
+ private final ApplicationInstance<ServiceMonitorStatus> applicationInstance;
+ private final NodeGroup nodeGroup;
+ private final MutableStatusRegistry hostStatusService;
+ private final List<ClusterApi> clusterInOrder;
+ private final ClusterControllerClientFactory clusterControllerClientFactory;
+ private final Map<HostName, HostStatus> hostStatusMap;
+
+ public ApplicationApiImpl(NodeGroup nodeGroup,
+ MutableStatusRegistry hostStatusService,
+ ClusterControllerClientFactory clusterControllerClientFactory) {
+ this.applicationInstance = nodeGroup.getApplication();
+ this.nodeGroup = nodeGroup;
+ this.hostStatusService = hostStatusService;
+ this.hostStatusMap = createHostStatusMap(
+ getHostsUsedByApplicationInstance(applicationInstance),
+ hostStatusService);
+ this.clusterInOrder = makeClustersInOrder(nodeGroup, hostStatusMap, clusterControllerClientFactory);
+ this.clusterControllerClientFactory = clusterControllerClientFactory;
+ }
+
+ @Override
+ public String applicationInfo() {
+ return applicationInstance.reference().toString();
+ }
+
+ private static Map<HostName, HostStatus> createHostStatusMap(Collection<HostName> hosts,
+ ReadOnlyStatusRegistry hostStatusService) {
+ return hosts.stream()
+ .collect(Collectors.toMap(
+ hostName -> hostName,
+ hostName -> hostStatusService.getHostStatus(hostName)));
+ }
+
+ private HostStatus getHostStatus(HostName hostName) {
+ return hostStatusMap.getOrDefault(hostName, HostStatus.NO_REMARKS);
+ }
+
+ @Override
+ public List<ClusterApi> getClusters() {
+ return clusterInOrder;
+ }
+
+ @Override
+ public List<StorageNode> getStorageNodesAllowedToBeDownInGroupInReverseClusterOrder() {
+ return clusterInOrder.stream()
+ .map(ClusterApi::storageNodeInGroup)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .filter(storageNode -> getHostStatus(storageNode.hostName()) == HostStatus.ALLOWED_TO_BE_DOWN)
+ .sorted(Comparator.reverseOrder())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<StorageNode> getUpStorageNodesInGroupInClusterOrder() {
+ return clusterInOrder.stream()
+ .map(ClusterApi::upStorageNodeInGroup)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void setHostState(HostName hostName, HostStatus status) {
+ hostStatusService.setHostState(hostName, status);
+ }
+
+ @Override
+ public List<HostName> getNodesInGroupWithStatus(HostStatus status) {
+ return nodeGroup.getHostNames().stream()
+ .filter(hostName -> getHostStatus(hostName) == status)
+ .collect(Collectors.toList());
+ }
+
+ private static List<ClusterApi> makeClustersInOrder
+ (NodeGroup nodeGroup,
+ Map<HostName, HostStatus> hostStatusMap,
+ ClusterControllerClientFactory clusterControllerClientFactory) {
+ Set<ServiceCluster<ServiceMonitorStatus>> clustersInGroup = getServiceClustersInGroup(nodeGroup);
+ return clustersInGroup.stream()
+ .map(serviceCluster -> new ClusterApiImpl(
+ serviceCluster,
+ nodeGroup,
+ hostStatusMap,
+ clusterControllerClientFactory))
+ .sorted(ApplicationApiImpl::compareClusters)
+ .collect(Collectors.toList());
+ }
+
+ private static int compareClusters(ClusterApi lhs, ClusterApi rhs) {
+ int diff = lhs.serviceType().toString().compareTo(rhs.serviceType().toString());
+ if (diff != 0) {
+ return diff;
+ }
+
+ return lhs.clusterId().toString().compareTo(rhs.clusterId().toString());
+ }
+
+ private static Set<ServiceCluster<ServiceMonitorStatus>> getServiceClustersInGroup(NodeGroup nodeGroup) {
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance = nodeGroup.getApplication();
+
+ Set<ServiceCluster<ServiceMonitorStatus>> serviceClustersInGroup = new HashSet<>();
+ for (ServiceCluster<ServiceMonitorStatus> cluster : applicationInstance.serviceClusters()) {
+ for (ServiceInstance<ServiceMonitorStatus> instance : cluster.serviceInstances()) {
+ if (nodeGroup.contains(instance.hostName())) {
+ serviceClustersInGroup.add(cluster);
+ break;
+ }
+ }
+ }
+
+ return serviceClustersInGroup;
+ }
+}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java
new file mode 100644
index 00000000000..daa4cea6570
--- /dev/null
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.ClusterId;
+import com.yahoo.vespa.applicationmodel.ServiceType;
+
+import java.util.Optional;
+
+public interface ClusterApi {
+ NodeGroup getNodeGroup();
+
+ String clusterInfo();
+ ClusterId clusterId();
+ ServiceType serviceType();
+ boolean isStorageCluster();
+
+ boolean noServicesInGroupIsUp();
+ boolean noServicesOutsideGroupIsDown();
+
+ int percentageOfServicesDown();
+ int percentageOfServicesDownIfGroupIsAllowedToBeDown();
+
+ Optional<StorageNode> storageNodeInGroup();
+ Optional<StorageNode> upStorageNodeInGroup();
+
+ String servicesDownAndNotInGroupDescription();
+ String nodesAllowedToBeDownNotInGroupDescription();
+}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java
new file mode 100644
index 00000000000..5ac34d8df4d
--- /dev/null
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java
@@ -0,0 +1,186 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.ClusterId;
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.applicationmodel.ServiceCluster;
+import com.yahoo.vespa.applicationmodel.ServiceInstance;
+import com.yahoo.vespa.applicationmodel.ServiceType;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory;
+import com.yahoo.vespa.orchestrator.status.HostStatus;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+class ClusterApiImpl implements ClusterApi {
+ private final ServiceCluster<ServiceMonitorStatus> serviceCluster;
+ private final NodeGroup nodeGroup;
+ private final Map<HostName, HostStatus> hostStatusMap;
+ private final ClusterControllerClientFactory clusterControllerClientFactory;
+ private final Set<ServiceInstance<ServiceMonitorStatus>> servicesInGroup;
+ private final Set<ServiceInstance<ServiceMonitorStatus>> servicesDownInGroup;
+ private final Set<ServiceInstance<ServiceMonitorStatus>> servicesNotInGroup;
+ private final Set<ServiceInstance<ServiceMonitorStatus>> servicesDownAndNotInGroup;
+
+ public ClusterApiImpl(ServiceCluster<ServiceMonitorStatus> serviceCluster,
+ NodeGroup nodeGroup,
+ Map<HostName, HostStatus> hostStatusMap,
+ ClusterControllerClientFactory clusterControllerClientFactory) {
+ this.serviceCluster = serviceCluster;
+ this.nodeGroup = nodeGroup;
+ this.hostStatusMap = hostStatusMap;
+ this.clusterControllerClientFactory = clusterControllerClientFactory;
+
+ Map<Boolean, Set<ServiceInstance<ServiceMonitorStatus>>> serviceInstancesByLocality =
+ serviceCluster.serviceInstances().stream()
+ .collect(
+ Collectors.groupingBy(
+ instance -> nodeGroup.contains(instance.hostName()),
+ Collectors.toSet()));
+ servicesInGroup = serviceInstancesByLocality.getOrDefault(true, Collections.emptySet());
+ servicesNotInGroup = serviceInstancesByLocality.getOrDefault(false, Collections.emptySet());
+
+ servicesDownInGroup = servicesInGroup.stream().filter(this::serviceEffectivelyDown).collect(Collectors.toSet());
+ servicesDownAndNotInGroup = servicesNotInGroup.stream().filter(this::serviceEffectivelyDown).collect(Collectors.toSet());
+ }
+
+ @Override
+ public NodeGroup getNodeGroup() {
+ return nodeGroup;
+ }
+
+ @Override
+ public ClusterId clusterId() {
+ return serviceCluster.clusterId();
+ }
+
+ @Override
+ public ServiceType serviceType() {
+ return serviceCluster.serviceType();
+ }
+
+ @Override
+ public boolean isStorageCluster() {
+ return VespaModelUtil.isStorage(serviceCluster);
+ }
+
+ @Override
+ public boolean noServicesInGroupIsUp() {
+ return servicesDownInGroup.size() == servicesInGroup.size();
+ }
+
+ @Override
+ public boolean noServicesOutsideGroupIsDown() {
+ return servicesDownAndNotInGroup.size() == 0;
+ }
+
+ @Override
+ public int percentageOfServicesDown() {
+ int numberOfServicesDown = servicesDownAndNotInGroup.size() + servicesDownInGroup.size();
+ return numberOfServicesDown * 100 / serviceCluster.serviceInstances().size();
+ }
+
+ @Override
+ public int percentageOfServicesDownIfGroupIsAllowedToBeDown() {
+ int numberOfServicesDown = servicesDownAndNotInGroup.size() + servicesInGroup.size();
+ return numberOfServicesDown * 100 / serviceCluster.serviceInstances().size();
+ }
+
+ @Override
+ public String servicesDownAndNotInGroupDescription() {
+ // Sort these for readability and testing stability
+ return servicesDownAndNotInGroup.stream()
+ .map(service -> service.toString())
+ .sorted()
+ .collect(Collectors.toList())
+ .toString();
+ }
+
+ @Override
+ public String nodesAllowedToBeDownNotInGroupDescription() {
+ return servicesNotInGroup.stream()
+ .map(ServiceInstance::hostName)
+ .filter(hostName -> hostStatus(hostName) == HostStatus.ALLOWED_TO_BE_DOWN)
+ .sorted()
+ .distinct()
+ .collect(Collectors.toList())
+ .toString();
+ }
+
+ private Optional<StorageNode> storageNodeInGroup(
+ Predicate<ServiceInstance<ServiceMonitorStatus>> storageServicePredicate) {
+ if (!VespaModelUtil.isStorage(serviceCluster)) {
+ return Optional.empty();
+ }
+
+ Set<StorageNode> storageNodes = new HashSet<>();
+
+ for (ServiceInstance<ServiceMonitorStatus> serviceInstance : servicesInGroup) {
+ if (!storageServicePredicate.test(serviceInstance)) {
+ continue;
+ }
+
+ HostName hostName = serviceInstance.hostName();
+ if (nodeGroup.contains(hostName)) {
+ if (storageNodes.contains(hostName)) {
+ throw new IllegalStateException("Found more than 1 storage service instance on " + hostName
+ + ": last service instance is " + serviceInstance.configId()
+ + " in storage cluster " + clusterInfo());
+ }
+
+ StorageNode storageNode = new StorageNodeImpl(
+ nodeGroup.getApplication(),
+ clusterId(),
+ serviceInstance,
+ clusterControllerClientFactory);
+ storageNodes.add(storageNode);
+ }
+ }
+
+ if (storageNodes.size() > 1) {
+ throw new IllegalStateException("Found more than 1 storage node (" + storageNodes
+ + ") in the same cluster (" + clusterInfo() + ") in the same node group ("
+ + getNodeGroup().toCommaSeparatedString() + "): E.g. suspension of such a setup is not supported "
+ + " by the Cluster Controller and is dangerous w.r.t. data redundancy.");
+ }
+
+ return storageNodes.stream().findFirst();
+ }
+
+ @Override
+ public Optional<StorageNode> storageNodeInGroup() {
+ return storageNodeInGroup(serviceInstance-> true);
+ }
+
+ @Override
+ public Optional<StorageNode> upStorageNodeInGroup() {
+ return storageNodeInGroup(serviceInstance-> !serviceEffectivelyDown(serviceInstance));
+ }
+
+ @Override
+ public String clusterInfo() {
+ return "{ clusterId=" + clusterId() + ", serviceType=" + serviceType() + " }";
+ }
+
+ private HostStatus hostStatus(HostName hostName) {
+ return hostStatusMap.getOrDefault(hostName, HostStatus.NO_REMARKS);
+ }
+
+ private boolean serviceEffectivelyDown(ServiceInstance<ServiceMonitorStatus> service) {
+ if (hostStatus(service.hostName()) == HostStatus.ALLOWED_TO_BE_DOWN) {
+ return true;
+ }
+
+ if (service.serviceStatus() == ServiceMonitorStatus.DOWN) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/NodeGroup.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/NodeGroup.java
new file mode 100644
index 00000000000..ff38f39d6ec
--- /dev/null
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/NodeGroup.java
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.ApplicationInstance;
+import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference;
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A group of nodes belonging to the same application instance.
+ */
+public class NodeGroup {
+ private final ApplicationInstance<ServiceMonitorStatus> application;
+ private final Set<HostName> hostNames = new HashSet<>();
+
+ public NodeGroup(ApplicationInstance<ServiceMonitorStatus> application, HostName... hostNames) {
+ this.application = application;
+ this.hostNames.addAll(Arrays.asList(hostNames));
+ }
+
+ public void addNode(HostName hostName) {
+ if (!this.hostNames.add(hostName)) {
+ throw new IllegalArgumentException("Node " + hostName + " is already in the group");
+ }
+ }
+
+ public ApplicationInstanceReference getApplicationReference() {
+ return application.reference();
+ }
+
+ ApplicationInstance<ServiceMonitorStatus> getApplication() {
+ return application;
+ }
+
+ public boolean contains(HostName hostName) {
+ return hostNames.contains(hostName);
+ }
+
+ public List<HostName> getHostNames() {
+ return hostNames.stream().collect(Collectors.toList()).stream().sorted().collect(Collectors.toList());
+ }
+
+ public String toCommaSeparatedString() {
+ return getHostNames().stream().map(HostName::toString).collect(Collectors.joining(","));
+ }
+
+ @Override
+ public String toString() {
+ return "NodeGroup{" +
+ "application=" + application.reference() +
+ ", hostNames=" + hostNames +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NodeGroup)) return false;
+ NodeGroup nodeGroup = (NodeGroup) o;
+ return Objects.equals(application, nodeGroup.application) &&
+ Objects.equals(hostNames, nodeGroup.hostNames);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(application, hostNames);
+ }
+}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNode.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNode.java
new file mode 100644
index 00000000000..1698770904f
--- /dev/null
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNode.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerState;
+import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException;
+
+public interface StorageNode extends Comparable<StorageNode> {
+ HostName hostName();
+ void setNodeState(ClusterControllerState wantedState) throws HostStateChangeDeniedException;
+}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNodeImpl.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNodeImpl.java
new file mode 100644
index 00000000000..1984c5cca40
--- /dev/null
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/StorageNodeImpl.java
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.applicationmodel.ApplicationInstance;
+import com.yahoo.vespa.applicationmodel.ClusterId;
+import com.yahoo.vespa.applicationmodel.ConfigId;
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.applicationmodel.ServiceInstance;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerClient;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerState;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerStateResponse;
+import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException;
+import com.yahoo.vespa.orchestrator.policy.HostedVespaPolicy;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+public class StorageNodeImpl implements StorageNode {
+ private static final Logger logger = Logger.getLogger(StorageNodeImpl.class.getName());
+
+ private final ApplicationInstance<ServiceMonitorStatus> applicationInstance;
+ private final ClusterId clusterId;
+ private final ServiceInstance<ServiceMonitorStatus> storageService;
+ private final ClusterControllerClientFactory clusterControllerClientFactory;
+
+ StorageNodeImpl(ApplicationInstance<ServiceMonitorStatus> applicationInstance,
+ ClusterId clusterId,
+ ServiceInstance<ServiceMonitorStatus> storageService,
+ ClusterControllerClientFactory clusterControllerClientFactory) {
+ this.applicationInstance = applicationInstance;
+ this.clusterId = clusterId;
+ this.storageService = storageService;
+ this.clusterControllerClientFactory = clusterControllerClientFactory;
+ }
+
+ @Override
+ public HostName hostName() {
+ return storageService.hostName();
+ }
+
+ @Override
+ public void setNodeState(ClusterControllerState wantedNodeState)
+ throws HostStateChangeDeniedException {
+ // The "cluster name" used by the Cluster Controller IS the cluster ID.
+ String clusterId = this.clusterId.s();
+
+ List<HostName> clusterControllers = VespaModelUtil.getClusterControllerInstancesInOrder(applicationInstance, this.clusterId);
+
+ ClusterControllerClient client = clusterControllerClientFactory.createClient(
+ clusterControllers,
+ clusterId);
+
+ ConfigId configId = storageService.configId();
+ int nodeIndex = VespaModelUtil.getStorageNodeIndex(configId);
+
+ logger.log(LogLevel.DEBUG, () -> "Setting cluster controller state for " +
+ "application " + applicationInstance.reference().asString() +
+ ", host " + hostName() +
+ ", cluster name " + clusterId +
+ ", node index " + nodeIndex +
+ ", node state " + wantedNodeState);
+
+ ClusterControllerStateResponse response;
+ try {
+ response = client.setNodeState(nodeIndex, wantedNodeState);
+ } catch (IOException e) {
+ throw new HostStateChangeDeniedException(
+ hostName(),
+ HostedVespaPolicy.CLUSTER_CONTROLLER_AVAILABLE_CONSTRAINT,
+ VespaModelUtil.CLUSTER_CONTROLLER_SERVICE_TYPE,
+ "Failed to communicate with cluster controllers " + clusterControllers + ": " + e,
+ e);
+ }
+
+ if ( ! response.wasModified) {
+ throw new HostStateChangeDeniedException(
+ hostName(),
+ HostedVespaPolicy.SET_NODE_STATE_CONSTRAINT,
+ VespaModelUtil.CLUSTER_CONTROLLER_SERVICE_TYPE,
+ "Failed to set state to " + wantedNodeState + " in controller: " + response.reason);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "StorageNodeImpl{" +
+ "applicationInstance=" + applicationInstance +
+ ", clusterId=" + clusterId +
+ ", storageService=" + storageService +
+ '}';
+ }
+
+ /** Only base it on the service instance, e.g. the cluster ID is included in its equals(). */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof StorageNodeImpl)) return false;
+ StorageNodeImpl that = (StorageNodeImpl) o;
+ return Objects.equals(storageService, that.storageService);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(storageService);
+ }
+
+ @Override
+ public int compareTo(StorageNode otherStorageNode) {
+ if (!(otherStorageNode instanceof StorageNodeImpl)) {
+ throw new IllegalArgumentException("Unable to compare our class to any StorageNode object");
+ }
+ StorageNodeImpl that = (StorageNodeImpl) otherStorageNode;
+
+ // We're guaranteed there's only one storage service per node.
+ return this.storageService.hostName().compareTo(that.storageService.hostName());
+ }
+}
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/VespaModelUtil.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/VespaModelUtil.java
index fd210a901d5..a362d7710a9 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/VespaModelUtil.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/VespaModelUtil.java
@@ -1,5 +1,5 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.orchestrator;
+package com.yahoo.vespa.orchestrator.model;
import com.yahoo.vespa.applicationmodel.ApplicationInstance;
import com.yahoo.vespa.applicationmodel.ClusterId;
@@ -11,6 +11,7 @@ import com.yahoo.vespa.applicationmodel.ServiceType;
import java.util.Collection;
import java.util.Comparator;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -39,6 +40,9 @@ public class VespaModelUtil {
public static final ServiceType SEARCHNODE_SERVICE_TYPE = new ServiceType("searchnode");
public static final ServiceType STORAGENODE_SERVICE_TYPE = new ServiceType("storagenode");
+ private static final Comparator<ServiceInstance<?>> CLUSTER_CONTROLLER_INDEX_COMPARATOR =
+ Comparator.comparing(serviceInstance -> VespaModelUtil.getClusterControllerIndex(serviceInstance.configId()));
+
// @return true iff the service cluster refers to a cluster controller service cluster.
public static boolean isClusterController(ServiceCluster<?> cluster) {
return CLUSTER_CONTROLLER_SERVICE_TYPE.equals(cluster.serviceType());
@@ -66,26 +70,32 @@ public class VespaModelUtil {
/**
* @return The set of all Cluster Controller service instances for the application.
*/
- public static <T> Set<ServiceInstance<T>> getClusterControllerInstances(ApplicationInstance<T> application,
- ClusterId contentClusterId)
+ public static <T> List<HostName> getClusterControllerInstancesInOrder(ApplicationInstance<T> application,
+ ClusterId contentClusterId)
{
Set<ServiceCluster<T>> controllerClusters = getClusterControllerServiceClusters(application);
Collection<ServiceCluster<T>> controllerClustersForContentCluster = filter(controllerClusters, contentClusterId);
+ Set<ServiceInstance<T>> clusterControllerInstances;
if (controllerClustersForContentCluster.size() == 1) {
- return first(controllerClustersForContentCluster).serviceInstances();
+ clusterControllerInstances = first(controllerClustersForContentCluster).serviceInstances();
} else if (controllerClusters.size() == 1) {
ServiceCluster<T> cluster = first(controllerClusters);
log.warning("No cluster controller cluster for content cluster " + contentClusterId
+ ", using the only cluster controller cluster available: " + cluster.clusterId());
- return cluster.serviceInstances();
+ clusterControllerInstances = cluster.serviceInstances();
} else {
throw new RuntimeException("Failed getting cluster controller for content cluster " + contentClusterId +
". Available clusters = " + controllerClusters +
", matching clusters = " + controllerClustersForContentCluster);
}
+
+ return clusterControllerInstances.stream()
+ .sorted(CLUSTER_CONTROLLER_INDEX_COMPARATOR)
+ .map(serviceInstance -> serviceInstance.hostName())
+ .collect(Collectors.toList());
}
private static <T> Collection<ServiceCluster<T>> filter(Set<ServiceCluster<T>> controllerClusters,
@@ -110,12 +120,10 @@ public class VespaModelUtil {
*/
public static HostName getControllerHostName(ApplicationInstance<?> application, ClusterId contentClusterId) {
// It happens that the master Cluster Controller is the one with the lowest index, if up.
- ServiceInstance<?> serviceInstance = getClusterControllerInstances(application, contentClusterId)
- .stream()
- .min(Comparator.comparing(instance -> getClusterControllerIndex(instance.configId())))
+ return getClusterControllerInstancesInOrder(application, contentClusterId).stream()
+ .findFirst()
.orElseThrow(() ->
new IllegalArgumentException("No cluster controllers found in application " + application));
- return serviceInstance.hostName();
}
/**
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicy.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicy.java
index a85de34097f..b7574a43ce1 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicy.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicy.java
@@ -7,11 +7,11 @@ import com.yahoo.vespa.applicationmodel.ClusterId;
import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.applicationmodel.ServiceCluster;
import com.yahoo.vespa.applicationmodel.ServiceInstance;
-import com.yahoo.vespa.orchestrator.VespaModelUtil;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerClient;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerState;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerStateResponse;
+import com.yahoo.vespa.orchestrator.model.VespaModelUtil;
import com.yahoo.vespa.orchestrator.status.HostStatus;
import com.yahoo.vespa.orchestrator.status.MutableStatusRegistry;
import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
@@ -19,6 +19,7 @@ import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
@@ -193,7 +194,7 @@ public class HostedVespaPolicy implements Policy {
HostName hostName,
ClusterControllerState nodeState) throws HostStateChangeDeniedException {
ClusterId contentClusterId = VespaModelUtil.getContentClusterName(application, hostName);
- Set<? extends ServiceInstance<?>> clusterControllers = VespaModelUtil.getClusterControllerInstances(application, contentClusterId);
+ List<HostName> clusterControllers = VespaModelUtil.getClusterControllerInstancesInOrder(application, contentClusterId);
ClusterControllerClient client = clusterControllerClientFactory.createClient(
clusterControllers,
contentClusterId.s());
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/ServiceClusterSuspendPolicy.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/ServiceClusterSuspendPolicy.java
index ba14b616d96..438cbebab44 100644
--- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/ServiceClusterSuspendPolicy.java
+++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/ServiceClusterSuspendPolicy.java
@@ -1,8 +1,8 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.orchestrator.policy;
-import com.yahoo.vespa.orchestrator.VespaModelUtil;
import com.yahoo.vespa.applicationmodel.ServiceCluster;
+import com.yahoo.vespa.orchestrator.model.VespaModelUtil;
/**
* @author hakonhall
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyInstanceLookupService.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyInstanceLookupService.java
index d48bc1f302b..dede55f402a 100644
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyInstanceLookupService.java
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/DummyInstanceLookupService.java
@@ -12,6 +12,7 @@ import com.yahoo.vespa.applicationmodel.ServiceCluster;
import com.yahoo.vespa.applicationmodel.ServiceInstance;
import com.yahoo.vespa.applicationmodel.ServiceType;
import com.yahoo.vespa.applicationmodel.TenantId;
+import com.yahoo.vespa.orchestrator.model.VespaModelUtil;
import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
import java.util.HashSet;
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactoryMock.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactoryMock.java
index 4fc9ad2d56d..93dcb5815c6 100644
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactoryMock.java
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/ClusterControllerClientFactoryMock.java
@@ -4,15 +4,14 @@ package com.yahoo.vespa.orchestrator.controller;
import com.yahoo.vespa.applicationmodel.ApplicationInstance;
import com.yahoo.vespa.applicationmodel.ClusterId;
import com.yahoo.vespa.applicationmodel.HostName;
-import com.yahoo.vespa.applicationmodel.ServiceInstance;
import com.yahoo.vespa.orchestrator.DummyInstanceLookupService;
-import com.yahoo.vespa.orchestrator.VespaModelUtil;
+import com.yahoo.vespa.orchestrator.model.VespaModelUtil;
import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
import java.io.IOException;
-import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -50,7 +49,7 @@ public class ClusterControllerClientFactoryMock implements ClusterControllerClie
}
@Override
- public ClusterControllerClient createClient(Collection<? extends ServiceInstance<?>> clusterControllers, String clusterName) {
+ public ClusterControllerClient createClient(List<HostName> clusterControllers, String clusterName) {
return new ClusterControllerClient() {
@Override
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactoryTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactoryTest.java
index cc217885047..cde644e7e8b 100644
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactoryTest.java
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/controller/SingleInstanceClusterControllerClientFactoryTest.java
@@ -1,17 +1,14 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.orchestrator.controller;
-import com.yahoo.vespa.jaxrs.client.JaxRsClientFactory;
-import com.yahoo.vespa.orchestrator.TestUtil;
import com.yahoo.vespa.applicationmodel.ConfigId;
import com.yahoo.vespa.applicationmodel.HostName;
-import com.yahoo.vespa.applicationmodel.ServiceInstance;
-import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+import com.yahoo.vespa.jaxrs.client.JaxRsClientFactory;
import org.junit.Before;
import org.junit.Test;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.List;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -52,7 +49,7 @@ public class SingleInstanceClusterControllerClientFactoryTest {
@Test
public void testCreateClientWithNoClusterControllerInstances() throws Exception {
- final Collection<ServiceInstance<ServiceMonitorStatus>> clusterControllers = Collections.emptySet();
+ final List<HostName> clusterControllers = Arrays.asList();
try {
clientFactory.createClient(clusterControllers, "clusterName");
@@ -64,8 +61,7 @@ public class SingleInstanceClusterControllerClientFactoryTest {
@Test
public void testCreateClientWithSingleClusterControllerInstance() throws Exception {
- final Collection<ServiceInstance<ServiceMonitorStatus>> clusterControllers = Collections.singleton(
- new ServiceInstance<>(clusterControllerConfigId(1), HOST_NAME_1, ServiceMonitorStatus.UP));
+ final List<HostName> clusterControllers = Arrays.asList(HOST_NAME_1);
clientFactory.createClient(clusterControllers, "clusterName")
.setNodeState(0, ClusterControllerState.MAINTENANCE);
@@ -78,10 +74,8 @@ public class SingleInstanceClusterControllerClientFactoryTest {
}
@Test
- public void testCreateClientWithTwoNonClusterControllerInstances() throws Exception {
- final Collection<ServiceInstance<ServiceMonitorStatus>> clusterControllers = TestUtil.makeServiceInstanceSet(
- new ServiceInstance<>(new ConfigId("not-a-cluster-controller-1"), HOST_NAME_1, ServiceMonitorStatus.UP),
- new ServiceInstance<>(new ConfigId("not-a-cluster-controller-2"), HOST_NAME_2, ServiceMonitorStatus.UP));
+ public void testCreateClientWithoutClusterControllerInstances() throws Exception {
+ final List<HostName> clusterControllers = Arrays.asList();
try {
clientFactory.createClient(clusterControllers, "clusterName");
@@ -93,10 +87,7 @@ public class SingleInstanceClusterControllerClientFactoryTest {
@Test
public void testCreateClientWithThreeClusterControllerInstances() throws Exception {
- final Collection<ServiceInstance<ServiceMonitorStatus>> clusterControllers = TestUtil.makeServiceInstanceSet(
- new ServiceInstance<>(clusterControllerConfigId(1), HOST_NAME_1, ServiceMonitorStatus.UP),
- new ServiceInstance<>(clusterControllerConfigId(2), HOST_NAME_2, ServiceMonitorStatus.UP),
- new ServiceInstance<>(clusterControllerConfigId(3), HOST_NAME_3, ServiceMonitorStatus.UP));
+ final List<HostName> clusterControllers = Arrays.asList(HOST_NAME_1, HOST_NAME_2, HOST_NAME_3);
clientFactory.createClient(clusterControllers, "clusterName")
.setNodeState(0, ClusterControllerState.MAINTENANCE);
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImplTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImplTest.java
new file mode 100644
index 00000000000..684f7e76d94
--- /dev/null
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ApplicationApiImplTest.java
@@ -0,0 +1,321 @@
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.ApplicationInstance;
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.applicationmodel.ServiceType;
+import com.yahoo.vespa.orchestrator.status.HostStatus;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+
+public class ApplicationApiImplTest {
+ final ModelTestUtils modelUtils = new ModelTestUtils();
+
+ @Test
+ public void testApplicationInfo() {
+ ApplicationApiImpl applicationApi =
+ modelUtils.createApplicationApiImpl(modelUtils.createApplicationInstance(new ArrayList<>()));
+ assertEquals("tenant:application-name:foo:bar:default", applicationApi.applicationInfo());
+ }
+
+ @Test
+ public void testGetClustersThatAreOnAtLeastOneNodeInGroup() {
+ HostName hostName1 = new HostName("host1");
+ HostName hostName2 = new HostName("host2");
+ HostName hostName3 = new HostName("host3");
+ HostName hostName4 = new HostName("host4");
+
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance =
+ modelUtils.createApplicationInstance(Arrays.asList(
+ modelUtils.createServiceCluster(
+ "cluster-3",
+ new ServiceType("service-type-3"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-1", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-2", hostName2, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-1",
+ new ServiceType("service-type-1"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-3", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-4", hostName3, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-2",
+ new ServiceType("service-type-2"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-5", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-6", hostName2, ServiceMonitorStatus.UP)
+ )
+ )
+ ));
+
+ verifyClustersInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName1), 1, 2, 3);
+ verifyClustersInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName2), 2, 3);
+ verifyClustersInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName3), 1);
+ verifyClustersInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName4));
+ }
+
+ private void verifyClustersInOrder(ApplicationApi applicationApi,
+ Integer... expectedClusterNumbers) {
+ // Note: we require the clusters to be in order.
+ List<ClusterApi> clusterApis = applicationApi.getClusters();
+ String clusterInfos = clusterApis.stream().map(clusterApi -> clusterApi.clusterInfo()).collect(Collectors.joining(","));
+
+ String expectedClusterInfos = Arrays.stream(expectedClusterNumbers)
+ .map(number -> "{ clusterId=cluster-" + number + ", serviceType=service-type-" + number + " }")
+ .collect(Collectors.joining(","));
+
+ assertEquals(expectedClusterInfos, clusterInfos);
+ }
+
+ @Test
+ public void testGetUpStorageNodesInGroupInClusterOrder() {
+ HostName hostName1 = new HostName("host1");
+ HostName hostName2 = new HostName("host2");
+ HostName hostName3 = new HostName("host3");
+ HostName hostName4 = new HostName("host4");
+ HostName hostName5 = new HostName("host5");
+ HostName hostName6 = new HostName("host6");
+ HostName hostName7 = new HostName("host7");
+
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance =
+ modelUtils.createApplicationInstance(Arrays.asList(
+ modelUtils.createServiceCluster(
+ "cluster-3",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-30", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-31", hostName2, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-1",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-10", hostName3, ServiceMonitorStatus.DOWN),
+ modelUtils.createServiceInstance("config-id-11", hostName4, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-4",
+ new ServiceType("service-type-4"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-40", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-41", hostName2, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-42", hostName3, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-43", hostName5, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-2",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-20", hostName6, ServiceMonitorStatus.DOWN),
+ modelUtils.createServiceInstance("config-id-21", hostName7, ServiceMonitorStatus.UP)
+ )
+ )
+ ));
+
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName1), hostName1);
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName2), hostName2);
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName3)); // host3 is DOWN
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName4), hostName4);
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName5)); // not a storage cluster
+
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName1, hostName3), hostName1);
+
+ // For the node group (host1, host4), they both have an up storage node (service instance)
+ // with clusters (cluster-3, cluster-1) respectively, and so the order of the hosts are reversed
+ // (host4, host1) when sorted by the clusters.
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(applicationInstance, hostName1, hostName4), hostName4, hostName1);
+
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(
+ applicationInstance, hostName1, hostName4, hostName5), hostName4, hostName1);
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(
+ applicationInstance, hostName1, hostName4, hostName5, hostName6), hostName4, hostName1);
+ verifyUpStorageNodesInOrder(modelUtils.createApplicationApiImpl(
+ applicationInstance, hostName1, hostName4, hostName5, hostName7), hostName4, hostName7, hostName1);
+ }
+
+ private void verifyUpStorageNodesInOrder(ApplicationApi applicationApi,
+ HostName... expectedHostNames) {
+ List<HostName> upStorageNodes = applicationApi.getUpStorageNodesInGroupInClusterOrder().stream()
+ .map(storageNode -> storageNode.hostName())
+ .collect(Collectors.toList());
+ assertEquals(Arrays.asList(expectedHostNames), upStorageNodes);
+ }
+
+ @Test
+ public void testUpConditionOfStorageNode() {
+ verifyUpConditionWith(HostStatus.NO_REMARKS, ServiceMonitorStatus.UP, true);
+ verifyUpConditionWith(HostStatus.NO_REMARKS, ServiceMonitorStatus.NOT_CHECKED, true);
+ verifyUpConditionWith(HostStatus.NO_REMARKS, ServiceMonitorStatus.DOWN, false);
+ verifyUpConditionWith(HostStatus.ALLOWED_TO_BE_DOWN, ServiceMonitorStatus.UP, false);
+ verifyUpConditionWith(HostStatus.ALLOWED_TO_BE_DOWN, ServiceMonitorStatus.NOT_CHECKED, false);
+ verifyUpConditionWith(HostStatus.ALLOWED_TO_BE_DOWN, ServiceMonitorStatus.DOWN, false);
+ }
+
+ private void verifyUpConditionWith(HostStatus hostStatus, ServiceMonitorStatus serviceStatus, boolean expectUp) {
+ HostName hostName1 = modelUtils.createNode("host1", hostStatus);
+
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance =
+ modelUtils.createApplicationInstance(Arrays.asList(
+ modelUtils.createServiceCluster(
+ "cluster-1",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(modelUtils.createServiceInstance("config-id-1", hostName1, serviceStatus))
+ )
+ ));
+
+ ApplicationApiImpl applicationApi = modelUtils.createApplicationApiImpl(applicationInstance, hostName1);
+ List<HostName> upStorageNodes = expectUp ? Arrays.asList(hostName1) : new ArrayList<>();
+
+ List<HostName> actualStorageNodes = applicationApi.getUpStorageNodesInGroupInClusterOrder().stream()
+ .map(storageNode -> storageNode.hostName())
+ .collect(Collectors.toList());
+ assertEquals(upStorageNodes, actualStorageNodes);
+ }
+
+ @Test
+ public void testGetNodesInGroupWithStatus() {
+ HostName hostName1 = modelUtils.createNode("host1", HostStatus.NO_REMARKS);
+ HostName hostName2 = modelUtils.createNode("host2", HostStatus.NO_REMARKS);
+ HostName hostName3 = modelUtils.createNode("host3", HostStatus.ALLOWED_TO_BE_DOWN);
+
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance =
+ modelUtils.createApplicationInstance(Arrays.asList(
+ modelUtils.createServiceCluster(
+ "cluster-1",
+ new ServiceType("service-type-1"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-10", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-11", hostName2, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-2",
+ new ServiceType("service-type-2"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-20", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-21", hostName3, ServiceMonitorStatus.UP)
+ )
+ )
+ ));
+
+ verifyNodesInGroupWithoutRemarks(
+ modelUtils.createApplicationApiImpl(applicationInstance, hostName1),
+ Arrays.asList(hostName1),
+ Arrays.asList());
+ verifyNodesInGroupWithoutRemarks(
+ modelUtils.createApplicationApiImpl(applicationInstance, hostName1, hostName2),
+ Arrays.asList(hostName1, hostName2),
+ Arrays.asList());
+ verifyNodesInGroupWithoutRemarks(
+ modelUtils.createApplicationApiImpl(applicationInstance, hostName1, hostName2, hostName3),
+ Arrays.asList(hostName1, hostName2),
+ Arrays.asList(hostName3));
+ verifyNodesInGroupWithoutRemarks(
+ modelUtils.createApplicationApiImpl(applicationInstance, hostName3),
+ Arrays.asList(),
+ Arrays.asList(hostName3));
+ }
+
+ private void verifyNodesInGroupWithoutRemarks(ApplicationApi applicationApi,
+ List<HostName> noRemarksHostNames,
+ List<HostName> allowedToBeDownHostNames) {
+ List<HostName> actualNoRemarksHosts = applicationApi.getNodesInGroupWithStatus(HostStatus.NO_REMARKS);
+ assertEquals(noRemarksHostNames, actualNoRemarksHosts);
+ List<HostName> actualAllowedToBeDownHosts = applicationApi.getNodesInGroupWithStatus(HostStatus.ALLOWED_TO_BE_DOWN);
+ assertEquals(allowedToBeDownHostNames, actualAllowedToBeDownHosts);
+ }
+
+ @Test
+ public void testGetStorageNodesAllowedToBeDownInGroupInReverseClusterOrder() {
+ HostName allowedToBeDownHost1 = modelUtils.createNode("host1", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName noRemarksHost2 = modelUtils.createNode("host2", HostStatus.NO_REMARKS);
+ HostName allowedToBeDownHost3 = modelUtils.createNode("host3", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName allowedToBeDownHost4 = modelUtils.createNode("host4", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName noRemarksHost5 = modelUtils.createNode("host5", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName noRemarksHost6 = modelUtils.createNode("host6", HostStatus.NO_REMARKS);
+ HostName allowedToBeDownHost7 = modelUtils.createNode("host7", HostStatus.ALLOWED_TO_BE_DOWN);
+
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance =
+ modelUtils.createApplicationInstance(Arrays.asList(
+ modelUtils.createServiceCluster(
+ "cluster-4",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-40", allowedToBeDownHost1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-41", noRemarksHost2, ServiceMonitorStatus.DOWN)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-1",
+ new ServiceType("service-type-1"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-10", allowedToBeDownHost1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-11", allowedToBeDownHost3, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-3",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-30", allowedToBeDownHost4, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-31", noRemarksHost5, ServiceMonitorStatus.UP)
+ )
+ ),
+ modelUtils.createServiceCluster(
+ "cluster-2",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(
+ modelUtils.createServiceInstance("config-id-20", noRemarksHost6, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("config-id-21", allowedToBeDownHost7, ServiceMonitorStatus.UP)
+ )
+ )
+ ));
+
+ verifyStorageNodesAllowedToBeDown(
+ modelUtils.createApplicationApiImpl(applicationInstance, allowedToBeDownHost1), allowedToBeDownHost1);
+ verifyStorageNodesAllowedToBeDown(
+ modelUtils.createApplicationApiImpl(applicationInstance, noRemarksHost2));
+ verifyStorageNodesAllowedToBeDown(
+ modelUtils.createApplicationApiImpl(applicationInstance, allowedToBeDownHost3));
+
+ verifyStorageNodesAllowedToBeDown(
+ modelUtils.createApplicationApiImpl(applicationInstance, allowedToBeDownHost1, noRemarksHost6), allowedToBeDownHost1);
+
+ // allowedToBeDownHost4 is in cluster-3, while allowedToBeDownHost1 is in cluster-4, so allowedToBeDownHost4 should be ordered
+ // before allowedToBeDownHost1.
+ verifyStorageNodesAllowedToBeDown(
+ modelUtils.createApplicationApiImpl(applicationInstance, allowedToBeDownHost1, noRemarksHost6, allowedToBeDownHost4),
+ allowedToBeDownHost4, allowedToBeDownHost1);
+
+ verifyStorageNodesAllowedToBeDown(
+ modelUtils.createApplicationApiImpl(applicationInstance, allowedToBeDownHost1, allowedToBeDownHost4, allowedToBeDownHost7),
+ allowedToBeDownHost7, allowedToBeDownHost4, allowedToBeDownHost1);
+
+ verifyStorageNodesAllowedToBeDown(
+ modelUtils.createApplicationApiImpl(applicationInstance, allowedToBeDownHost4, allowedToBeDownHost1, allowedToBeDownHost7),
+ allowedToBeDownHost7, allowedToBeDownHost4, allowedToBeDownHost1);
+ }
+
+ private void verifyStorageNodesAllowedToBeDown(
+ ApplicationApi applicationApi, HostName... hostNames) {
+ List<HostName> actualStorageNodes =
+ applicationApi.getStorageNodesAllowedToBeDownInGroupInReverseClusterOrder().stream()
+ .map(storageNode -> storageNode.hostName())
+ .collect(Collectors.toList());
+ assertEquals(Arrays.asList(hostNames), actualStorageNodes);
+ }
+}
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java
new file mode 100644
index 00000000000..082002b7cf2
--- /dev/null
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java
@@ -0,0 +1,133 @@
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.applicationmodel.ServiceCluster;
+import com.yahoo.vespa.applicationmodel.ServiceType;
+import com.yahoo.vespa.orchestrator.status.HostStatus;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ClusterApiImplTest {
+ final ModelTestUtils modelUtils = new ModelTestUtils();
+
+ @Test
+ public void testServicesDownAndNotInGroup() {
+ HostName hostName1 = modelUtils.createNode("host1", HostStatus.NO_REMARKS);
+ HostName hostName2 = modelUtils.createNode("host2", HostStatus.NO_REMARKS);
+ HostName hostName3 = modelUtils.createNode("host3", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName hostName4 = modelUtils.createNode("host4", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName hostName5 = modelUtils.createNode("host5", HostStatus.NO_REMARKS);
+
+
+ ServiceCluster<ServiceMonitorStatus> serviceCluster = modelUtils.createServiceCluster(
+ "cluster",
+ new ServiceType("service-type"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("service-1", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("service-2", hostName2, ServiceMonitorStatus.DOWN),
+ modelUtils.createServiceInstance("service-3", hostName3, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("service-4", hostName4, ServiceMonitorStatus.DOWN),
+ modelUtils.createServiceInstance("service-5", hostName5, ServiceMonitorStatus.UP)
+ )
+ );
+
+ ClusterApiImpl clusterApi = new ClusterApiImpl(
+ serviceCluster,
+ new NodeGroup(modelUtils.createApplicationInstance(new ArrayList<>()), hostName5),
+ modelUtils.getHostStatusMap(),
+ modelUtils.getClusterControllerClientFactory());
+
+ assertEquals("{ clusterId=cluster, serviceType=service-type }", clusterApi.clusterInfo());
+ assertFalse(clusterApi.isStorageCluster());
+ assertEquals("[ServiceInstance{configId=service-2, hostName=host2, serviceStatus=DOWN}, "
+ + "ServiceInstance{configId=service-3, hostName=host3, serviceStatus=UP}, "
+ + "ServiceInstance{configId=service-4, hostName=host4, serviceStatus=DOWN}]",
+ clusterApi.servicesDownAndNotInGroupDescription());
+ assertEquals("[host3, host4]",
+ clusterApi.nodesAllowedToBeDownNotInGroupDescription());
+ assertEquals(60, clusterApi.percentageOfServicesDown());
+ assertEquals(80, clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown());
+ }
+
+ @Test
+ public void testNoServices() {
+ HostName hostName1 = modelUtils.createNode("host1", HostStatus.NO_REMARKS);
+ HostName hostName2 = modelUtils.createNode("host2", HostStatus.NO_REMARKS);
+ HostName hostName3 = modelUtils.createNode("host3", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName hostName4 = modelUtils.createNode("host4", HostStatus.ALLOWED_TO_BE_DOWN);
+ HostName hostName5 = modelUtils.createNode("host5", HostStatus.NO_REMARKS);
+
+
+ ServiceCluster<ServiceMonitorStatus> serviceCluster = modelUtils.createServiceCluster(
+ "cluster",
+ new ServiceType("service-type"),
+ Arrays.asList(
+ modelUtils.createServiceInstance("service-1", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("service-2", hostName2, ServiceMonitorStatus.DOWN),
+ modelUtils.createServiceInstance("service-3", hostName3, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("service-4", hostName4, ServiceMonitorStatus.DOWN),
+ modelUtils.createServiceInstance("service-5", hostName5, ServiceMonitorStatus.UP)
+ )
+ );
+
+ verifyNoServices(serviceCluster, false, false, hostName1);
+ verifyNoServices(serviceCluster, true, false, hostName2);
+ verifyNoServices(serviceCluster, true, false, hostName3);
+ verifyNoServices(serviceCluster, true, false, hostName4);
+ verifyNoServices(serviceCluster, false, false, hostName5);
+
+ verifyNoServices(serviceCluster, false, false, hostName1, hostName2);
+ verifyNoServices(serviceCluster, true, false, hostName2, hostName3);
+ verifyNoServices(serviceCluster, true, true, hostName2, hostName3, hostName4);
+ verifyNoServices(serviceCluster, false, true, hostName1, hostName2, hostName3, hostName4);
+ }
+
+ private void verifyNoServices(ServiceCluster<ServiceMonitorStatus> serviceCluster,
+ boolean expectedNoServicesInGroupIsUp,
+ boolean expectedNoServicesOutsideGroupIsDown,
+ HostName... groupNodes) {
+ ClusterApiImpl clusterApi = new ClusterApiImpl(
+ serviceCluster,
+ new NodeGroup(modelUtils.createApplicationInstance(new ArrayList<>()), groupNodes),
+ modelUtils.getHostStatusMap(),
+ modelUtils.getClusterControllerClientFactory());
+
+ assertEquals(expectedNoServicesInGroupIsUp, clusterApi.noServicesInGroupIsUp());
+ assertEquals(expectedNoServicesOutsideGroupIsDown, clusterApi.noServicesOutsideGroupIsDown());
+ }
+
+ @Test
+ public void testStorageCluster() {
+ HostName hostName1 = new HostName("host1");
+ HostName hostName2 = new HostName("host2");
+ HostName hostName3 = new HostName("host3");
+
+ ServiceCluster<ServiceMonitorStatus> serviceCluster = modelUtils.createServiceCluster(
+ "cluster",
+ VespaModelUtil.STORAGENODE_SERVICE_TYPE,
+ Arrays.asList(
+ modelUtils.createServiceInstance("storage-1", hostName1, ServiceMonitorStatus.UP),
+ modelUtils.createServiceInstance("storage-2", hostName2, ServiceMonitorStatus.DOWN)
+ )
+ );
+
+ ClusterApiImpl clusterApi = new ClusterApiImpl(
+ serviceCluster,
+ new NodeGroup(modelUtils.createApplicationInstance(new ArrayList<>()), hostName1, hostName3),
+ new HashMap<>(),
+ modelUtils.getClusterControllerClientFactory());
+
+ assertTrue(clusterApi.isStorageCluster());
+ assertEquals(Optional.of(hostName1), clusterApi.storageNodeInGroup().map(storageNode -> storageNode.hostName()));
+ assertEquals(Optional.of(hostName1), clusterApi.upStorageNodeInGroup().map(storageNode -> storageNode.hostName()));
+ }
+} \ No newline at end of file
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ModelTestUtils.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ModelTestUtils.java
new file mode 100644
index 00000000000..03eb0184a88
--- /dev/null
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ModelTestUtils.java
@@ -0,0 +1,92 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.ApplicationInstance;
+import com.yahoo.vespa.applicationmodel.ApplicationInstanceId;
+import com.yahoo.vespa.applicationmodel.ClusterId;
+import com.yahoo.vespa.applicationmodel.ConfigId;
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.applicationmodel.ServiceCluster;
+import com.yahoo.vespa.applicationmodel.ServiceInstance;
+import com.yahoo.vespa.applicationmodel.ServiceType;
+import com.yahoo.vespa.applicationmodel.TenantId;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory;
+import com.yahoo.vespa.orchestrator.status.HostStatus;
+import com.yahoo.vespa.orchestrator.status.MutableStatusRegistry;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ModelTestUtils {
+ private final MutableStatusRegistry statusRegistry = mock(MutableStatusRegistry.class);
+ private final ClusterControllerClientFactory clusterControllerClientFactory = mock(ClusterControllerClientFactory.class);
+ private final Map<HostName, HostStatus> hostStatusMap = new HashMap<>();
+
+ ModelTestUtils() {
+ when(statusRegistry.getHostStatus(any())).thenReturn(HostStatus.NO_REMARKS);
+ }
+
+ Map<HostName, HostStatus> getHostStatusMap() {
+ return hostStatusMap;
+ }
+
+ HostName createNode(String name, HostStatus hostStatus) {
+ HostName hostName = new HostName(name);
+ hostStatusMap.put(hostName, hostStatus);
+ when(statusRegistry.getHostStatus(hostName)).thenReturn(hostStatus);
+ return hostName;
+ }
+
+ ApplicationApiImpl createApplicationApiImpl(
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance,
+ HostName... hostnames) {
+ NodeGroup nodeGroup = new NodeGroup(applicationInstance, hostnames);
+ return new ApplicationApiImpl(nodeGroup, statusRegistry, clusterControllerClientFactory);
+ }
+
+ ApplicationInstance<ServiceMonitorStatus> createApplicationInstance(
+ List<ServiceCluster<ServiceMonitorStatus>> serviceClusters) {
+ Set<ServiceCluster<ServiceMonitorStatus>> serviceClusterSet = serviceClusters.stream()
+ .collect(Collectors.toSet());
+
+ return new ApplicationInstance<>(
+ new TenantId("tenant"),
+ new ApplicationInstanceId("application-name:foo:bar:default"),
+ serviceClusterSet);
+ }
+
+ ServiceCluster<ServiceMonitorStatus> createServiceCluster(
+ String clusterId,
+ ServiceType serviceType,
+ List<ServiceInstance<ServiceMonitorStatus>> serviceInstances) {
+ Set<ServiceInstance<ServiceMonitorStatus>> serviceInstanceSet = serviceInstances.stream()
+ .collect(Collectors.toSet());
+
+ return new ServiceCluster<>(
+ new ClusterId(clusterId),
+ serviceType,
+ serviceInstanceSet);
+ }
+
+ ServiceInstance<ServiceMonitorStatus> createServiceInstance(
+ String configId,
+ HostName hostName,
+ ServiceMonitorStatus status) {
+ return new ServiceInstance<>(
+ new ConfigId(configId),
+ hostName,
+ status);
+ }
+
+ public ClusterControllerClientFactory getClusterControllerClientFactory() {
+ return clusterControllerClientFactory;
+ }
+}
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/NodeGroupTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/NodeGroupTest.java
new file mode 100644
index 00000000000..56a9e6bf147
--- /dev/null
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/NodeGroupTest.java
@@ -0,0 +1,33 @@
+package com.yahoo.vespa.orchestrator.model;
+
+import com.yahoo.vespa.applicationmodel.ApplicationInstance;
+import com.yahoo.vespa.applicationmodel.ApplicationInstanceId;
+import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.applicationmodel.TenantId;
+import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.junit.Assert.assertEquals;
+
+public class NodeGroupTest {
+ @Test
+ public void testBasics() {
+ ApplicationInstance<ServiceMonitorStatus> applicationInstance = new ApplicationInstance<>(
+ new TenantId("tenant"),
+ new ApplicationInstanceId("application-instance"),
+ new HashSet<>());
+
+ HostName hostName1 = new HostName("host1");
+ HostName hostName2 = new HostName("host2");
+ HostName hostName3 = new HostName("host3");
+ NodeGroup nodeGroup = new NodeGroup(applicationInstance, hostName1, hostName3);
+ nodeGroup.addNode(hostName2);
+
+ // hostnames are sorted (for no good reason other than testability due to stability, readability)
+ assertEquals(Arrays.asList(hostName1, hostName2, hostName3), nodeGroup.getHostNames());
+ assertEquals("host1,host2,host3", nodeGroup.toCommaSeparatedString());
+ }
+} \ No newline at end of file
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/VespaModelUtilTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/VespaModelUtilTest.java
index c39410e9ca6..f2c17299d39 100644
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/VespaModelUtilTest.java
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/VespaModelUtilTest.java
@@ -1,5 +1,5 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.orchestrator;
+package com.yahoo.vespa.orchestrator.model;
import com.yahoo.vespa.applicationmodel.ApplicationInstance;
import com.yahoo.vespa.applicationmodel.ApplicationInstanceId;
@@ -10,14 +10,15 @@ import com.yahoo.vespa.applicationmodel.ServiceCluster;
import com.yahoo.vespa.applicationmodel.ServiceInstance;
import com.yahoo.vespa.applicationmodel.ServiceType;
import com.yahoo.vespa.applicationmodel.TenantId;
+import com.yahoo.vespa.orchestrator.TestUtil;
import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
import org.junit.Test;
+import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
-import java.util.Set;
-import com.google.common.collect.ImmutableSet;
import static com.yahoo.vespa.orchestrator.TestUtil.makeServiceClusterSet;
import static com.yahoo.vespa.orchestrator.TestUtil.makeServiceInstanceSet;
import static junit.framework.TestCase.assertFalse;
@@ -169,9 +170,8 @@ public class VespaModelUtilTest {
@Test
public void testGettingClusterControllerInstances() {
- Set<ServiceInstance<?>> controllers =
- new HashSet<>(VespaModelUtil.getClusterControllerInstances(application, CONTENT_CLUSTER_ID));
- Set<ServiceInstance<ServiceMonitorStatus>> expectedControllers = ImmutableSet.of(controller0, controller1);
+ List<HostName> controllers = VespaModelUtil.getClusterControllerInstancesInOrder(application, CONTENT_CLUSTER_ID);
+ List<HostName> expectedControllers = Arrays.asList(controller0.hostName(), controller1.hostName());
assertThat(controllers).isEqualTo(expectedControllers);
}
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicyTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicyTest.java
index 45d605b6d8a..09471f9d6ae 100644
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicyTest.java
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaPolicyTest.java
@@ -12,19 +12,21 @@ import com.yahoo.vespa.applicationmodel.ServiceInstance;
import com.yahoo.vespa.applicationmodel.ServiceType;
import com.yahoo.vespa.applicationmodel.TenantId;
import com.yahoo.vespa.orchestrator.TestUtil;
-import com.yahoo.vespa.orchestrator.VespaModelUtil;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerClient;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactory;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerState;
import com.yahoo.vespa.orchestrator.controller.ClusterControllerStateResponse;
+import com.yahoo.vespa.orchestrator.model.VespaModelUtil;
import com.yahoo.vespa.orchestrator.status.HostStatus;
import com.yahoo.vespa.orchestrator.status.MutableStatusRegistry;
import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
import org.junit.Test;
import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import static com.yahoo.vespa.orchestrator.TestUtil.makeServiceClusterSet;
import static com.yahoo.vespa.orchestrator.TestUtil.makeServiceInstanceSet;
@@ -417,10 +419,12 @@ public class HostedVespaPolicyTest {
// Verification phase.
if (expectedNodeStateSentToClusterController.isPresent()) {
+ List<HostName> clusterControllers = CLUSTER_CONTROLLER_SERVICE_CLUSTER.serviceInstances().stream()
+ .map(service -> service.hostName())
+ .collect(Collectors.toList());
+
verify(clusterControllerClientFactory, times(1))
- .createClient(
- CLUSTER_CONTROLLER_SERVICE_CLUSTER.serviceInstances(),
- CONTENT_CLUSTER_NAME);
+ .createClient(clusterControllers, CONTENT_CLUSTER_NAME);
verify(client, times(1))
.setNodeState(
STORAGE_NODE_INDEX,