diff options
author | Morten Tokle <morten.tokle@gmail.com> | 2019-01-11 13:12:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-11 13:12:03 +0100 |
commit | 25221dbd5bff429264aba05dadf7464303abd70a (patch) | |
tree | 9aca75743c0590db2984b2c03ca418ba767a91ae /node-repository/src | |
parent | 68807481defb0cc417388d3c3897f649f4bcda83 (diff) | |
parent | cb25bacee63ffc6b1a4023a45af683c967ef3cb3 (diff) |
Merge pull request #8088 from vespa-engine/mpolden/load-balancers-api
Add /loadbalancers/v1/
Diffstat (limited to 'node-repository/src')
10 files changed, 222 insertions, 34 deletions
diff --git a/node-repository/src/main/config/node-repository.xml b/node-repository/src/main/config/node-repository.xml index 9276ce0e7c9..22ab615bfad 100644 --- a/node-repository/src/main/config/node-repository.xml +++ b/node-repository/src/main/config/node-repository.xml @@ -11,5 +11,10 @@ <binding>https://*/nodes/v2/*</binding> </handler> +<handler id="com.yahoo.vespa.hosted.provision.restapi.v2.LoadBalancersApiHandler" bundle="node-repository"> + <binding>http://*/loadbalancers/v1/*</binding> + <binding>https://*/loadbalancers/v1/*</binding> +</handler> + <preprocess:include file="node-flavors.xml" required="false" /> <preprocess:include file="node-repository-config.xml" required="false" /> diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersApiHandler.java new file mode 100644 index 00000000000..6ffac2c0fbc --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersApiHandler.java @@ -0,0 +1,52 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.restapi.v2; + +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.vespa.hosted.provision.NoSuchNodeException; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.yolean.Exceptions; + +import javax.inject.Inject; +import java.util.logging.Level; + +/** + * @author mpolden + */ +public class LoadBalancersApiHandler extends LoggingRequestHandler { + + private final NodeRepository nodeRepository; + + @Inject + public LoadBalancersApiHandler(LoggingRequestHandler.Context parentCtx, NodeRepository nodeRepository) { + super(parentCtx); + this.nodeRepository = nodeRepository; + } + + @Override + public HttpResponse handle(HttpRequest request) { + try { + switch (request.getMethod()) { + case GET: return handleGET(request); + default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); + } + } + catch (NotFoundException | NoSuchNodeException e) { + return ErrorResponse.notFoundError(Exceptions.toMessageString(e)); + } + catch (IllegalArgumentException e) { + return ErrorResponse.badRequest(Exceptions.toMessageString(e)); + } + catch (RuntimeException e) { + log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e); + return ErrorResponse.internalServerError(Exceptions.toMessageString(e)); + } + } + + private HttpResponse handleGET(HttpRequest request) { + String path = request.getUri().getPath(); + if (path.equals("/loadbalancers/v1/")) return new LoadBalancersResponse(request, nodeRepository); + throw new NotFoundException("Nothing at path '" + path + "'"); + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java new file mode 100644 index 00000000000..04a1cdaeeda --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/LoadBalancersResponse.java @@ -0,0 +1,78 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.restapi.v2; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.container.jdisc.HttpRequest; +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.NodeRepository; +import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; +import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author mpolden + */ +public class LoadBalancersResponse extends HttpResponse { + + private final NodeRepository nodeRepository; + private final HttpRequest request; + + public LoadBalancersResponse(HttpRequest request, NodeRepository nodeRepository) { + super(200); + this.request = request; + this.nodeRepository = nodeRepository; + } + + private Optional<ApplicationId> application() { + return Optional.ofNullable(request.getProperty("application")).map(ApplicationFilter::toApplicationId); + } + + private List<LoadBalancer> loadBalancers() { + return application().map(nodeRepository.database()::readLoadBalancers) + .orElseGet(() -> new ArrayList<>(nodeRepository.database().readLoadBalancers().values())); + } + + @Override + public String getContentType() { return "application/json"; } + + @Override + public void render(OutputStream stream) throws IOException { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + Cursor loadBalancerArray = root.setArray("loadBalancers"); + + loadBalancers().forEach(lb -> { + Cursor lbObject = loadBalancerArray.addObject(); + lbObject.setString("id", lb.id().serializedForm()); + lbObject.setString("application", lb.id().application().application().value()); + lbObject.setString("tenant", lb.id().application().tenant().value()); + lbObject.setString("instance", lb.id().application().instance().value()); + lbObject.setString("cluster", lb.id().cluster().value()); + lbObject.setString("hostname", lb.hostname().value()); + + Cursor portArray = lbObject.setArray("ports"); + lb.ports().forEach(portArray::addLong); + + Cursor realArray = lbObject.setArray("reals"); + lb.reals().forEach(real -> { + Cursor realObject = realArray.addObject(); + realObject.setString("hostname", real.hostname().value()); + realObject.setString("ipAddress", real.ipAddress()); + realObject.setLong("port", real.port()); + }); + + lbObject.setBool("inactive", lb.inactive()); + }); + + new JsonFormat(true).encode(stream, slime); + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java index 733f5df7858..d2ab3c20080 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java @@ -6,10 +6,10 @@ import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.NodeType; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.slime.Cursor; +import com.yahoo.slime.JsonFormat; import com.yahoo.slime.Slime; -import com.yahoo.vespa.config.SlimeUtils; +import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.History; @@ -81,7 +81,7 @@ class NodesResponse extends HttpResponse { @Override public void render(OutputStream stream) throws IOException { - stream.write(toJson()); + new JsonFormat(true).encode(stream, slime); } @Override @@ -89,10 +89,6 @@ class NodesResponse extends HttpResponse { return "application/json"; } - private byte[] toJson() throws IOException { - return SlimeUtils.toJsonBytes(slime); - } - private void statesToSlime(Cursor root) { Cursor states = root.setObject("states"); for (Node.State state : Node.State.values()) 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 110d0ca94d0..bc8772af952 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 @@ -9,31 +9,34 @@ package com.yahoo.vespa.hosted.provision.testutils; */ public class ContainerConfig { - public static String servicesXmlV2(int port) { - return "<jdisc version='1.0'>\n" + - " <config name=\"container.handler.threadpool\">\n" + - " <maxthreads>10</maxthreads>\n" + - " </config> \n" + - " <component id='com.yahoo.test.ManualClock'/>\n" + - " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDeployer'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.MockProvisioner'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDuperModel'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.lb.LoadBalancerServiceMock'/>\n" + - " <component id='com.yahoo.vespa.hosted.provision.maintenance.NodeRepositoryMaintenance'/>\n" + - " <component id='com.yahoo.config.provision.Zone'/>\n" + - " <handler id='com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler'>\n" + - " <binding>http://*/nodes/v2/*</binding>\n" + - " </handler>\n" + - " <http>\n" + - " <server id='myServer' port='" + port + "'/>\n" + - " </http>\n" + - "</jdisc>"; - } + public static String servicesXmlV2(int port) { + return "<jdisc version='1.0'>\n" + + " <config name=\"container.handler.threadpool\">\n" + + " <maxthreads>10</maxthreads>\n" + + " </config> \n" + + " <component id='com.yahoo.test.ManualClock'/>\n" + + " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDeployer'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockProvisioner'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.TestHostLivenessTracker'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.ServiceMonitorStub'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockDuperModel'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.testutils.MockNodeRepository'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.lb.LoadBalancerServiceMock'/>\n" + + " <component id='com.yahoo.vespa.hosted.provision.maintenance.NodeRepositoryMaintenance'/>\n" + + " <component id='com.yahoo.config.provision.Zone'/>\n" + + " <handler id='com.yahoo.vespa.hosted.provision.restapi.v2.NodesApiHandler'>\n" + + " <binding>http://*/nodes/v2/*</binding>\n" + + " </handler>\n" + + " <handler id='com.yahoo.vespa.hosted.provision.restapi.v2.LoadBalancersApiHandler'>\n" + + " <binding>http://*/loadbalancers/v1/*</binding>\n" + + " </handler>\n" + + " <http>\n" + + " <server id='myServer' port='" + port + "'/>\n" + + " </http>\n" + + "</jdisc>"; + } } 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 1b8ae58a97d..183255db06b 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 @@ -18,6 +18,7 @@ import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.flag.FlagId; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerServiceMock; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.Status; @@ -111,6 +112,8 @@ public class MockNodeRepository extends NodeRepository { dirtyRecursively("host55.yahoo.com", Agent.system, getClass().getSimpleName()); ApplicationId zoneApp = ApplicationId.from(TenantName.from("zoneapp"), ApplicationName.from("zoneapp"), InstanceName.from("zoneapp")); + // TODO: Remove this once feature flag is removed + this.flags().setEnabled(FlagId.exclusiveLoadBalancer, zoneApp, true); ClusterSpec zoneCluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin"), Version.fromString("6.42"), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java index ae7f3f14975..ec09805ff5d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java @@ -706,6 +706,13 @@ public class RestApiTest { } } + @Test + public void test_load_balancers() throws Exception { + assertFile(new Request("http://localhost:8080/loadbalancers/v1/"), "load-balancers.json"); + assertFile(new Request("http://localhost:8080/loadbalancers/v1/?application=zoneapp.zoneapp.zoneapp"), "load-balancers.json"); + assertResponse(new Request("http://localhost:8080/loadbalancers/v1/?application=tenant.nonexistent.default"), "{\"loadBalancers\":[]}"); + } + private String asDockerNodeJson(String hostname, String parentHostname, int additionalIpCount, String... ipAddress) { return "{\"hostname\":\"" + hostname + "\", \"parentHostname\":\"" + parentHostname + "\"," + createIpAddresses(ipAddress) + diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json index f27545f6094..33bf1e1cf42 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags1.json @@ -4,7 +4,7 @@ "id": "exclusive-load-balancer", "enabled": false, "enabledHostnames": [], - "enabledApplications": [] + "enabledApplications": ["zoneapp:zoneapp:zoneapp"] }, { "id": "new-cache-counting", diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json index a0e9954bec4..8d6859f550a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/flags2.json @@ -7,6 +7,7 @@ "host1" ], "enabledApplications": [ + "zoneapp:zoneapp:zoneapp", "foo:bar:default" ] }, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json new file mode 100644 index 00000000000..c882f7652d8 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/load-balancers.json @@ -0,0 +1,43 @@ +{ + "loadBalancers": [ + { + "id": "zoneapp:zoneapp:zoneapp:node-admin", + "application": "zoneapp", + "tenant": "zoneapp", + "instance": "zoneapp", + "cluster": "node-admin", + "hostname": "lb-zoneapp.zoneapp.zoneapp-node-admin", + "ports": [ + 4443 + ], + "reals": [ + { + "hostname": "dockerhost4.yahoo.com", + "ipAddress": "127.0.0.1", + "port": 4443 + }, + { + "hostname": "dockerhost5.yahoo.com", + "ipAddress": "127.0.0.1", + "port": 4443 + }, + { + "hostname": "dockerhost2.yahoo.com", + "ipAddress": "127.0.0.1", + "port": 4443 + }, + { + "hostname": "dockerhost3.yahoo.com", + "ipAddress": "127.0.0.1", + "port": 4443 + }, + { + "hostname": "dockerhost1.yahoo.com", + "ipAddress": "127.0.0.1", + "port": 4443 + } + ], + "inactive": false + } + ] +} |