summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application-model/src/main/java/com/yahoo/vespa/applicationmodel/ApplicationInstanceReference.java4
-rw-r--r--application-model/src/main/java/com/yahoo/vespa/applicationmodel/HostName.java7
-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
26 files changed, 1236 insertions, 71 deletions
diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ApplicationInstanceReference.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ApplicationInstanceReference.java
index d8feec5af3b..570e0d632ed 100644
--- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ApplicationInstanceReference.java
+++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ApplicationInstanceReference.java
@@ -33,6 +33,10 @@ public class ApplicationInstanceReference {
@JsonValue
@Override
public String toString() {
+ return asString();
+ }
+
+ public String asString() {
return tenantId.s() + ":" + applicationInstanceId.s();
}
diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/HostName.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/HostName.java
index 0b1998059f0..6fea6846ec9 100644
--- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/HostName.java
+++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/HostName.java
@@ -7,7 +7,7 @@ import java.util.Objects;
/**
* @author bjorncs
*/
-public class HostName {
+public class HostName implements Comparable<HostName> {
private final String id;
@@ -40,4 +40,9 @@ public class HostName {
public int hashCode() {
return Objects.hash(id);
}
+
+ @Override
+ public int compareTo(HostName o) {
+ return id.compareTo(o.id);
+ }
}
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,