diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-04-25 15:39:27 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-04-25 15:39:27 +0200 |
commit | bd36566986e66cf046a1bd3f5abe4cc6b9f4f149 (patch) | |
tree | 85d6d418d993211ffdf705900d17ac55fc4c27ac /node-repository | |
parent | d46f324c29c7200928f6e5ae9ba8196e7b461849 (diff) |
Add /nodes/v2/maintenance
Diffstat (limited to 'node-repository')
20 files changed, 341 insertions, 198 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/JobControl.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/JobControl.java index 93c626c8b81..197c173e639 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/JobControl.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/JobControl.java @@ -35,21 +35,24 @@ public class JobControl { * Each job is represented by its simple (omitting package) class name. */ public Set<String> jobs() { return new HashSet<>(startedJobs); } + + /** Returns a snapshot containing the currently inactive jobs in this */ + public Set<String> inactiveJobs() { return db.readInactiveJobs(); } /** Returns true if this job is not currently deactivated */ public boolean isActive(String jobSimpleClassName) { - return ! db.readDeactivatedJobs().contains(jobSimpleClassName); + return ! db.readInactiveJobs().contains(jobSimpleClassName); } /** Set a job active or inactive */ public void setActive(String jobSimpleClassName, boolean active) { - try (CuratorMutex lock = db.lockDeactivatedJobs()) { - Set<String> deactivatedJobs = db.readDeactivatedJobs(); + try (CuratorMutex lock = db.lockInactiveJobs()) { + Set<String> inactiveJobs = db.readInactiveJobs(); if (active) - deactivatedJobs.remove(jobSimpleClassName); + inactiveJobs.remove(jobSimpleClassName); else - deactivatedJobs.add(jobSimpleClassName); - db.writeDeactivatedJobs(deactivatedJobs); + inactiveJobs.add(jobSimpleClassName); + db.writeInactiveJobs(inactiveJobs); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java index 970dbde9cc8..95466d1d4b1 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java @@ -83,6 +83,8 @@ public class NodeRepositoryMaintenance extends AbstractComponent { metricsReporter.deconstruct(); } + public JobControl jobControl() { return jobControl; } + private static Optional<Duration> durationFromEnv(String envVariable) { return Optional.ofNullable(System.getenv(envPrefix + envVariable)).map(Long::parseLong).map(Duration::ofSeconds); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java index 08d56c9cb42..544772ecb8f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java @@ -68,7 +68,7 @@ public class CuratorDatabaseClient { curatorDatabase.create(root); for (Node.State state : Node.State.values()) curatorDatabase.create(toPath(state)); - curatorDatabase.create(deactivatedJobsPath()); + curatorDatabase.create(inactiveJobsPath()); } /** @@ -308,26 +308,26 @@ public class CuratorDatabaseClient { return root.append("defaultFlavor").append(applicationId.serializedForm()); } - public Set<String> readDeactivatedJobs() { - return curatorDatabase.getData(deactivatedJobsPath()) + public Set<String> readInactiveJobs() { + return curatorDatabase.getData(inactiveJobsPath()) .map(data -> stringSetSerializer.fromJson(data)) .orElse(Collections.emptySet()); } - public void writeDeactivatedJobs(Set<String> deactivatedJobs) { + public void writeInactiveJobs(Set<String> inactiveJobs) { NestedTransaction transaction = new NestedTransaction(); CuratorTransaction curatorTransaction = curatorDatabase.newCuratorTransactionIn(transaction); - curatorTransaction.add(CuratorOperations.setData(deactivatedJobsPath().getAbsolute(), - stringSetSerializer.toJson(deactivatedJobs))); + curatorTransaction.add(CuratorOperations.setData(inactiveJobsPath().getAbsolute(), + stringSetSerializer.toJson(inactiveJobs))); transaction.commit(); } - public CuratorMutex lockDeactivatedJobs() { - return lock(deactivatedJobsPath(), defaultLockTimeout); + public CuratorMutex lockInactiveJobs() { + return lock(inactiveJobsPath(), defaultLockTimeout); } - private Path deactivatedJobsPath() { - return root.append("deactivatedJobs"); + private Path inactiveJobsPath() { + return root.append("inactiveJobs"); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java new file mode 100644 index 00000000000..aa1f1438fb7 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/JobsResponse.java @@ -0,0 +1,43 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.restapi.v2; + +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.provision.maintenance.JobControl; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +/** A response containing maintenance job status */ +public class JobsResponse extends HttpResponse { + + private final JobControl jobControl; + + public JobsResponse(JobControl jobControl) { + super(200); + this.jobControl = jobControl; + } + + @Override + public void render(OutputStream stream) throws IOException { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + + Cursor jobArray = root.setArray("jobs"); + for (String jobName : jobControl.jobs()) + jobArray.addObject().setString("name", jobName); + + Cursor inactiveArray = root.setArray("inactive"); + for (String jobName : jobControl.inactiveJobs()) + inactiveArray.addString(jobName); + + new JsonFormat(true).encode(stream, slime); + } + + @Override + public String getContentType() { return "application/json"; } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java index c126a1f29ec..1498ff10a81 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java @@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.provision.NoSuchNodeException; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.config.provision.NodeFlavors; +import com.yahoo.vespa.hosted.provision.maintenance.NodeRepositoryMaintenance; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; import com.yahoo.vespa.hosted.provision.node.filter.NodeFilter; @@ -48,13 +49,16 @@ import static com.yahoo.vespa.config.SlimeUtils.optionalString; public class NodesApiHandler extends LoggingRequestHandler { private final NodeRepository nodeRepository; + private final NodeRepositoryMaintenance maintenance; private final NodeFlavors nodeFlavors; private static final String nodeTypeKey = "type"; - public NodesApiHandler(Executor executor, AccessLog accessLog, NodeRepository nodeRepository, NodeFlavors flavors) { + public NodesApiHandler(Executor executor, AccessLog accessLog, NodeRepository nodeRepository, + NodeRepositoryMaintenance maintenance, NodeFlavors flavors) { super(executor, accessLog); this.nodeRepository = nodeRepository; + this.maintenance = maintenance; this.nodeFlavors = flavors; } @@ -84,13 +88,14 @@ public class NodesApiHandler extends LoggingRequestHandler { private HttpResponse handleGET(HttpRequest request) { String path = request.getUri().getPath(); - if (path.equals( "/nodes/v2/")) return ResourcesResponse.fromStrings(request.getUri(), "state", "node", "command"); + if (path.equals( "/nodes/v2/")) return ResourcesResponse.fromStrings(request.getUri(), "state", "node", "command", "maintenance"); if (path.equals( "/nodes/v2/node/")) return new NodesResponse(ResponseType.nodeList, request, nodeRepository); if (path.startsWith("/nodes/v2/node/")) return new NodesResponse(ResponseType.singleNode, request, nodeRepository); if (path.equals( "/nodes/v2/state/")) return new NodesResponse(ResponseType.stateList, request, nodeRepository); if (path.startsWith("/nodes/v2/state/")) return new NodesResponse(ResponseType.nodesInStateList, request, nodeRepository); if (path.startsWith("/nodes/v2/acl/")) return new NodeAclResponse(request, nodeRepository); if (path.equals( "/nodes/v2/command/")) return ResourcesResponse.fromStrings(request.getUri(), "restart", "reboot"); + if (path.equals( "/nodes/v2/maintenance/")) return new JobsResponse(maintenance.jobControl()); throw new NotFoundException("Nothing at path '" + path + "'"); } @@ -132,21 +137,34 @@ public class NodesApiHandler extends LoggingRequestHandler { } private HttpResponse handlePOST(HttpRequest request) { - switch (request.getUri().getPath()) { - case "/nodes/v2/command/restart" : - int restartCount = nodeRepository.restart(toNodeFilter(request)).size(); - return new MessageResponse("Scheduled restart of " + restartCount + " matching nodes"); - case "/nodes/v2/command/reboot" : - int rebootCount = nodeRepository.reboot(toNodeFilter(request)).size(); - return new MessageResponse("Scheduled reboot of " + rebootCount + " matching nodes"); - case "/nodes/v2/node" : - int addedNodes = addNodes(request.getData()); - return new MessageResponse("Added " + addedNodes + " nodes to the provisioned state"); - default: - throw new NotFoundException("Nothing at path '" + request.getUri().getPath() + "'"); + String path = request.getUri().getPath(); + if (path.equals("/nodes/v2/command/restart")) { + int restartCount = nodeRepository.restart(toNodeFilter(request)).size(); + return new MessageResponse("Scheduled restart of " + restartCount + " matching nodes"); + } + else if (path.equals("/nodes/v2/command/reboot")) { + int rebootCount = nodeRepository.reboot(toNodeFilter(request)).size(); + return new MessageResponse("Scheduled reboot of " + rebootCount + " matching nodes"); + } + else if (path.equals("/nodes/v2/node")) { + int addedNodes = addNodes(request.getData()); + return new MessageResponse("Added " + addedNodes + " nodes to the provisioned state"); + } + else if (path.startsWith("/nodes/v2/maintenance/inactive/")) { + return setActive(lastElement(path), false); + } + else { + throw new NotFoundException("Nothing at path '" + request.getUri().getPath() + "'"); } } + private MessageResponse setActive(String jobName, boolean active) { + if ( ! maintenance.jobControl().jobs().contains(jobName)) + throw new NotFoundException("No job named '" + jobName + "'"); + maintenance.jobControl().setActive(jobName, active); + return new MessageResponse((active ? "Re-activated" : "Deactivated" ) + " job '" + jobName + "'"); + } + private HttpResponse handleDELETE(HttpRequest request) { String path = request.getUri().getPath(); if (path.startsWith("/nodes/v2/node/")) { @@ -156,8 +174,13 @@ public class NodesApiHandler extends LoggingRequestHandler { else throw new NotFoundException("No node in the provisioned, parked or failed state with hostname " + hostname); } - - throw new NotFoundException("Nothing at path '" + request.getUri().getPath() + "'"); + else if (path.startsWith("/nodes/v2/maintenance/inactive/")) { + setActive(lastElement(path), true); + return new MessageResponse("Reactivated job '" + lastElement(path) + "'"); + } + else { + throw new NotFoundException("Nothing at path '" + request.getUri().getPath() + "'"); + } } private Node nodeFromRequest(HttpRequest request) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java index 1239069c1a0..d383e90d184 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java @@ -12,8 +12,15 @@ public class ContainerConfig { public static final String servicesXmlV2(int port) { return "<jdisc version='1.0'>" + + " <component id='com.yahoo.test.ManualClock'/>" + + " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock'/>" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDeployer'/>" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker'/>" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub'/>" + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors'/>" + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository'/>" + + " <component id='com.yahoo.vespa.hosted.provision.maintenance.NodeRepositoryMaintenance'/>" + " <handler id='com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler'>" + " <binding>http://*/nodes/v2/*</binding>" + " </handler>" + diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MockDeployer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java index 377cb2e4443..e46797240f2 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MockDeployer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java @@ -1,6 +1,7 @@ // Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.provision.maintenance; +package com.yahoo.vespa.hosted.provision.testutils; +import com.google.inject.Inject; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; @@ -12,6 +13,7 @@ import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -27,6 +29,12 @@ public class MockDeployer implements Deployer { /** The number of redeployments done to this */ public int redeployments = 0; + @Inject + @SuppressWarnings("unused") + public MockDeployer() { + this(null, Collections.emptyMap()); + } + /** * Create a mock deployer which contains a substitute for an application repository, fullfilled to * be able to call provision with the right parameters. diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java index a8490076d5b..adfccd1f874 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java @@ -43,19 +43,14 @@ public class MockNodeRepository extends NodeRepository { * Constructor * @param flavors flavors to have in node repo */ - public MockNodeRepository(NodeFlavors flavors) throws Exception { - super(flavors, mockCurator(), Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")), Zone.defaultZone(), + public MockNodeRepository(MockCurator curator, NodeFlavors flavors) throws Exception { + super(flavors, curator, Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")), Zone.defaultZone(), new MockNameResolver().mockAnyLookup()); this.flavors = flavors; + curator.setConnectionSpec("cfg1:1234,cfg2:1234,cfg3:1234"); populate(); } - private static Curator mockCurator() { - MockCurator mockCurator = new MockCurator(); - mockCurator.setConnectionSpec("cfg1:1234,cfg2:1234,cfg3:1234"); - return mockCurator; - } - private void populate() { NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(this, flavors, Zone.defaultZone()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java new file mode 100644 index 00000000000..3de5278cd41 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/OrchestratorMock.java @@ -0,0 +1,64 @@ +package com.yahoo.vespa.hosted.provision.testutils; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.applicationmodel.HostName; +import com.yahoo.vespa.orchestrator.ApplicationIdNotFoundException; +import com.yahoo.vespa.orchestrator.ApplicationStateChangeDeniedException; +import com.yahoo.vespa.orchestrator.BatchHostNameNotFoundException; +import com.yahoo.vespa.orchestrator.BatchInternalErrorException; +import com.yahoo.vespa.orchestrator.HostNameNotFoundException; +import com.yahoo.vespa.orchestrator.Orchestrator; +import com.yahoo.vespa.orchestrator.policy.BatchHostStateChangeDeniedException; +import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException; +import com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus; +import com.yahoo.vespa.orchestrator.status.HostStatus; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author bratseth + */ +public class OrchestratorMock implements Orchestrator { + + Set<ApplicationId> suspendedApplications = new HashSet<>(); + + @Override + public HostStatus getNodeStatus(HostName hostName) throws HostNameNotFoundException { + return null; + } + + @Override + public void resume(HostName hostName) throws HostStateChangeDeniedException, HostNameNotFoundException {} + + @Override + public void suspend(HostName hostName) throws HostStateChangeDeniedException, HostNameNotFoundException {} + + @Override + public ApplicationInstanceStatus getApplicationInstanceStatus(ApplicationId appId) throws ApplicationIdNotFoundException { + return suspendedApplications.contains(appId) + ? ApplicationInstanceStatus.ALLOWED_TO_BE_DOWN : ApplicationInstanceStatus.NO_REMARKS; + } + + @Override + public Set<ApplicationId> getAllSuspendedApplications() { + return null; + } + + @Override + public void resume(ApplicationId appId) throws ApplicationStateChangeDeniedException, ApplicationIdNotFoundException { + suspendedApplications.remove(appId); + } + + @Override + public void suspend(ApplicationId appId) throws ApplicationStateChangeDeniedException, ApplicationIdNotFoundException { + suspendedApplications.add(appId); + } + + @Override + public void suspendAll(HostName parentHostname, List<HostName> hostNames) throws BatchInternalErrorException, BatchHostStateChangeDeniedException, BatchHostNameNotFoundException { + throw new RuntimeException("Not implemented"); + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ServiceMonitorStub.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ServiceMonitorStub.java new file mode 100644 index 00000000000..983f81be126 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ServiceMonitorStub.java @@ -0,0 +1,91 @@ +package com.yahoo.vespa.hosted.provision.testutils; + +import com.google.inject.Inject; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.applicationmodel.ApplicationInstance; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceId; +import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; +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.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.service.monitor.ServiceMonitor; +import com.yahoo.vespa.service.monitor.ServiceMonitorStatus; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author bratseth + */ +public class ServiceMonitorStub implements ServiceMonitor { + + private final Map<ApplicationId, MockDeployer.ApplicationContext> apps; + private final NodeRepository nodeRepository; + + private Set<String> downHosts = new HashSet<>(); + private boolean statusIsKnown = true; + + /** Create a service monitor where all nodes are initially up */ + @Inject + @SuppressWarnings("unused") + public ServiceMonitorStub(NodeRepository nodeRepository) { + this(Collections.emptyMap(), nodeRepository); + } + + /** Create a service monitor where all nodes are initially up */ + public ServiceMonitorStub(Map<ApplicationId, MockDeployer.ApplicationContext> apps, NodeRepository nodeRepository) { + this.apps = apps; + this.nodeRepository = nodeRepository; + } + + public void setHostDown(String hostname) { + downHosts.add(hostname); + } + + public void setHostUp(String hostname) { + downHosts.remove(hostname); + } + + public void setStatusIsKnown(boolean statusIsKnown) { + this.statusIsKnown = statusIsKnown; + } + + private ServiceMonitorStatus getHostStatus(String hostname) { + if (!statusIsKnown) return ServiceMonitorStatus.NOT_CHECKED; + if (downHosts.contains(hostname)) return ServiceMonitorStatus.DOWN; + return ServiceMonitorStatus.UP; + } + + @Override + public Map<ApplicationInstanceReference, ApplicationInstance<ServiceMonitorStatus>> queryStatusOfAllApplicationInstances() { + // Convert apps information to the response payload to return + Map<ApplicationInstanceReference, ApplicationInstance<ServiceMonitorStatus>> status = new HashMap<>(); + for (Map.Entry<ApplicationId, MockDeployer.ApplicationContext> app : apps.entrySet()) { + Set<ServiceInstance<ServiceMonitorStatus>> serviceInstances = new HashSet<>(); + for (Node node : nodeRepository.getNodes(app.getValue().id(), Node.State.active)) { + serviceInstances.add(new ServiceInstance<>(new ConfigId("configid"), + new HostName(node.hostname()), + getHostStatus(node.hostname()))); + } + Set<ServiceCluster<ServiceMonitorStatus>> serviceClusters = new HashSet<>(); + serviceClusters.add(new ServiceCluster<>(new ClusterId(app.getValue().cluster().id().value()), + new ServiceType("serviceType"), + serviceInstances)); + TenantId tenantId = new TenantId(app.getKey().tenant().value()); + ApplicationInstanceId applicationInstanceId = new ApplicationInstanceId(app.getKey().application().value()); + status.put(new ApplicationInstanceReference(tenantId, applicationInstanceId), + new ApplicationInstance<>(tenantId, applicationInstanceId, serviceClusters)); + } + return status; + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/TestHostLivenessTracker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/TestHostLivenessTracker.java new file mode 100644 index 00000000000..e0ad7238f4a --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/TestHostLivenessTracker.java @@ -0,0 +1,31 @@ +package com.yahoo.vespa.hosted.provision.testutils; + +import com.yahoo.config.provision.HostLivenessTracker; + +import java.time.Clock; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** This is a fully functional implementation */ +public class TestHostLivenessTracker implements HostLivenessTracker { + + private final Clock clock; + private final Map<String, Instant> lastRequestFromHost = new HashMap<>(); + + public TestHostLivenessTracker(Clock clock) { + this.clock = clock; + } + + @Override + public void receivedRequestFrom(String hostname) { + lastRequestFromHost.put(hostname, clock.instant()); + } + + @Override + public Optional<Instant> lastRequestFrom(String hostname) { + return Optional.ofNullable(lastRequestFromHost.get(hostname)); + } + +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java index a07deff3543..47055af075e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java @@ -16,6 +16,7 @@ import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester; +import com.yahoo.vespa.hosted.provision.testutils.MockDeployer; import org.junit.Test; import java.time.Duration; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java index 4e51138eceb..378c1bf554b 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java @@ -7,7 +7,6 @@ import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.Flavor; -import com.yahoo.config.provision.HostLivenessTracker; import com.yahoo.config.provision.HostSpec; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.NodeFlavors; @@ -17,16 +16,6 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Zone; import com.yahoo.test.ManualClock; import com.yahoo.transaction.NestedTransaction; -import com.yahoo.vespa.applicationmodel.ApplicationInstance; -import com.yahoo.vespa.applicationmodel.ApplicationInstanceId; -import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; -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.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.curator.transaction.CuratorTransaction; @@ -34,23 +23,14 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; import com.yahoo.vespa.hosted.provision.testutils.FlavorConfigBuilder; +import com.yahoo.vespa.hosted.provision.testutils.MockDeployer; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; -import com.yahoo.vespa.orchestrator.ApplicationIdNotFoundException; -import com.yahoo.vespa.orchestrator.ApplicationStateChangeDeniedException; -import com.yahoo.vespa.orchestrator.BatchHostNameNotFoundException; -import com.yahoo.vespa.orchestrator.BatchInternalErrorException; -import com.yahoo.vespa.orchestrator.HostNameNotFoundException; +import com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock; +import com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub; +import com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker; import com.yahoo.vespa.orchestrator.Orchestrator; -import com.yahoo.vespa.orchestrator.policy.BatchHostStateChangeDeniedException; -import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException; -import com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus; -import com.yahoo.vespa.orchestrator.status.HostStatus; -import com.yahoo.vespa.service.monitor.ServiceMonitor; -import com.yahoo.vespa.service.monitor.ServiceMonitorStatus; - -import java.time.Clock; + import java.time.Duration; -import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -59,7 +39,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import static org.junit.Assert.assertEquals; @@ -265,125 +244,4 @@ public class NodeFailTester { return highestIndex; } - /** This is a fully functional implementation */ - private static class TestHostLivenessTracker implements HostLivenessTracker { - - private final Clock clock; - private final Map<String, Instant> lastRequestFromHost = new HashMap<>(); - - public TestHostLivenessTracker(Clock clock) { - this.clock = clock; - } - - @Override - public void receivedRequestFrom(String hostname) { - lastRequestFromHost.put(hostname, clock.instant()); - } - - @Override - public Optional<Instant> lastRequestFrom(String hostname) { - return Optional.ofNullable(lastRequestFromHost.get(hostname)); - } - - } - - public static class ServiceMonitorStub implements ServiceMonitor { - - private final Map<ApplicationId, MockDeployer.ApplicationContext> apps; - private final NodeRepository nodeRepository; - - private Set<String> downHosts = new HashSet<>(); - private boolean statusIsKnown = true; - - /** Create a service monitor where all nodes are initially up */ - public ServiceMonitorStub(Map<ApplicationId, MockDeployer.ApplicationContext> apps, NodeRepository nodeRepository) { - this.apps = apps; - this.nodeRepository = nodeRepository; - } - - public void setHostDown(String hostname) { - downHosts.add(hostname); - } - - public void setHostUp(String hostname) { - downHosts.remove(hostname); - } - - public void setStatusIsKnown(boolean statusIsKnown) { - this.statusIsKnown = statusIsKnown; - } - - private ServiceMonitorStatus getHostStatus(String hostname) { - if ( ! statusIsKnown) return ServiceMonitorStatus.NOT_CHECKED; - if (downHosts.contains(hostname)) return ServiceMonitorStatus.DOWN; - return ServiceMonitorStatus.UP; - } - - @Override - public Map<ApplicationInstanceReference, ApplicationInstance<ServiceMonitorStatus>> queryStatusOfAllApplicationInstances() { - // Convert apps information to the response payload to return - Map<ApplicationInstanceReference, ApplicationInstance<ServiceMonitorStatus>> status = new HashMap<>(); - for (Map.Entry<ApplicationId, MockDeployer.ApplicationContext> app : apps.entrySet()) { - Set<ServiceInstance<ServiceMonitorStatus>> serviceInstances = new HashSet<>(); - for (Node node : nodeRepository.getNodes(app.getValue().id(), Node.State.active)) { - serviceInstances.add(new ServiceInstance<>(new ConfigId("configid"), - new HostName(node.hostname()), - getHostStatus(node.hostname()))); - } - Set<ServiceCluster<ServiceMonitorStatus>> serviceClusters = new HashSet<>(); - serviceClusters.add(new ServiceCluster<>(new ClusterId(app.getValue().cluster().id().value()), - new ServiceType("serviceType"), - serviceInstances)); - TenantId tenantId = new TenantId(app.getKey().tenant().value()); - ApplicationInstanceId applicationInstanceId = new ApplicationInstanceId(app.getKey().application().value()); - status.put(new ApplicationInstanceReference(tenantId, applicationInstanceId), - new ApplicationInstance<>(tenantId, applicationInstanceId, serviceClusters)); - } - return status; - } - - } - - class OrchestratorMock implements Orchestrator { - - Set<ApplicationId> suspendedApplications = new HashSet<>(); - - @Override - public HostStatus getNodeStatus(HostName hostName) throws HostNameNotFoundException { - return null; - } - - @Override - public void resume(HostName hostName) throws HostStateChangeDeniedException, HostNameNotFoundException {} - - @Override - public void suspend(HostName hostName) throws HostStateChangeDeniedException, HostNameNotFoundException {} - - @Override - public ApplicationInstanceStatus getApplicationInstanceStatus(ApplicationId appId) throws ApplicationIdNotFoundException { - return suspendedApplications.contains(appId) - ? ApplicationInstanceStatus.ALLOWED_TO_BE_DOWN : ApplicationInstanceStatus.NO_REMARKS; - } - - @Override - public Set<ApplicationId> getAllSuspendedApplications() { - return null; - } - - @Override - public void resume(ApplicationId appId) throws ApplicationStateChangeDeniedException, ApplicationIdNotFoundException { - suspendedApplications.remove(appId); - } - - @Override - public void suspend(ApplicationId appId) throws ApplicationStateChangeDeniedException, ApplicationIdNotFoundException { - suspendedApplications.add(appId); - } - - @Override - public void suspendAll(HostName parentHostname, List<HostName> hostNames) throws BatchInternalErrorException, BatchHostStateChangeDeniedException, BatchHostNameNotFoundException { - throw new RuntimeException("Not implemented"); - } - } - } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java index 5ce876bf12c..91ce0a8ef59 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java @@ -25,6 +25,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; import com.yahoo.vespa.hosted.provision.testutils.FlavorConfigBuilder; +import com.yahoo.vespa.hosted.provision.testutils.MockDeployer; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import org.junit.Test; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java index 844571acce5..69ef4744001 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java @@ -27,6 +27,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; import com.yahoo.vespa.hosted.provision.testutils.FlavorConfigBuilder; +import com.yahoo.vespa.hosted.provision.testutils.MockDeployer; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import org.junit.Before; import org.junit.Test; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java index 085f711058b..f034490b3f7 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java @@ -23,6 +23,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; import com.yahoo.vespa.hosted.provision.testutils.FlavorConfigBuilder; +import com.yahoo.vespa.hosted.provision.testutils.MockDeployer; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import org.junit.Test; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java index b4f17cc003c..6ef0e6ab01f 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java @@ -11,7 +11,7 @@ import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.maintenance.JobControl; -import com.yahoo.vespa.hosted.provision.maintenance.MockDeployer; +import com.yahoo.vespa.hosted.provision.testutils.MockDeployer; import com.yahoo.vespa.hosted.provision.maintenance.RetiredExpirer; import org.junit.Ignore; import org.junit.Test; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodeStateSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodeStateSerializerTest.java index c4f96b0709a..de7a9bac878 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodeStateSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodeStateSerializerTest.java @@ -8,38 +8,39 @@ import org.junit.Test; import java.util.HashSet; import java.util.Set; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; /** * @author bakksjo */ public class NodeStateSerializerTest { + @Test public void allStatesHaveASerializedForm() { for (Node.State nodeState : Node.State.values()) { - assertThat(NodeStateSerializer.wireNameOf(nodeState), is(notNullValue())); + assertNotNull(NodeStateSerializer.wireNameOf(nodeState)); } } @Test public void wireNamesDoNotOverlap() { - final Set<String> wireNames = new HashSet<>(); + Set<String> wireNames = new HashSet<>(); for (Node.State nodeState : Node.State.values()) { wireNames.add(NodeStateSerializer.wireNameOf(nodeState)); } - assertThat(wireNames.size(), is(Node.State.values().length)); + assertEquals(Node.State.values().length, wireNames.size()); } @Test public void serializationAndDeserializationIsSymmetric() { for (Node.State nodeState : Node.State.values()) { - final String serialized = NodeStateSerializer.wireNameOf(nodeState); - final Node.State deserialized = NodeStateSerializer.fromWireName(serialized) + String serialized = NodeStateSerializer.wireNameOf(nodeState); + Node.State deserialized = NodeStateSerializer.fromWireName(serialized) .orElseThrow(() -> new RuntimeException( "Cannot deserialize '" + serialized + "', serialized form of " + nodeState.name())); - assertThat(deserialized, is(nodeState)); + assertEquals(nodeState, deserialized); } } + } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json new file mode 100644 index 00000000000..3ee004d1e29 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json @@ -0,0 +1,10 @@ +{ + "jobs": [ + { + "name" : "Job1" + } + ], + "inactive" : [ + + ] +}
\ No newline at end of file diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json index 9648c059af6..4224718ab06 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json @@ -8,6 +8,9 @@ }, { "url": "http://localhost:8080/nodes/v2/command/" + }, + { + "url": "http://localhost:8080/nodes/v2/maintenance/" } ] }
\ No newline at end of file |