Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Support different node/group setup): Support different node/group setup #79

Merged
merged 7 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/main/java/com/vispana/api/model/apppackage/Content.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,20 @@ public class Content {
private String id;

@JacksonXmlProperty(localName = "group")
private TopGroup topGroup;
private Group group;

protected List<Group> getContentGroups() {
return topGroup != null ? topGroup.getGroups() : List.of();
@JacksonXmlProperty(localName = "nodes")
Nodes nodes;

public List<Group> getGroups() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 I thought it would be a lot more complicated, nice!

return group != null ? group.getGroups() : List.of();
}

public List<Node> getNodes() {
return nodes != null ? nodes.getNodes() : List.of();
}

public boolean hasNodes() {
return nodes != null && nodes.getNodes() != null && !nodes.getNodes().isEmpty();
}
}
12 changes: 11 additions & 1 deletion src/main/java/com/vispana/api/model/apppackage/Group.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@

public class Group {

public static final String FALLBACK_DISTRIBUTION_KEY = "-1";

@JacksonXmlProperty(localName = "name", isAttribute = true)
private String name;

@JacksonXmlProperty(localName = "distribution-key", isAttribute = true)
private String distributionKey;

@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "group")
List<Group> groups;

public List<Group> getGroups() {
return groups != null ? groups : List.of(this);
}

@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "node")
List<Node> nodes;
Expand All @@ -21,7 +31,7 @@ public String getName() {
}

public String getDistributionKey() {
return distributionKey;
return distributionKey != null ? distributionKey : FALLBACK_DISTRIBUTION_KEY;
}

public List<Node> getNodes() {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/vispana/api/model/apppackage/Hosts.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public static Hosts fromXml(String xml) {
XmlMapper xmlMapper = new XmlMapper();

try {
if (xml == null || xml.isEmpty()) {
return new Hosts();
}
return xmlMapper.readValue(xml, Hosts.class);
} catch (IOException e) {
throw new RuntimeException("Failed to parse Hosts xml", e);
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/vispana/api/model/apppackage/Nodes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.vispana.api.model.apppackage;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.List;

@JacksonXmlRootElement(localName = "nodes")
public class Nodes {

@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "node")
List<Node> nodes;

protected List<Node> getNodes() {
return nodes != null ? nodes : List.of();
}
}
8 changes: 5 additions & 3 deletions src/main/java/com/vispana/api/model/apppackage/Services.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.io.IOException;
import java.util.List;

@JacksonXmlRootElement(localName = "services")
public class Services {
Expand All @@ -25,7 +24,10 @@ public static Services fromXml(String xml) {
}
}

public List<Group> getContentGroups() {
return content != null ? content.getContentGroups() : List.of();
public Content getContent() {
if (content == null) {
throw new RuntimeException("No content found in services xml");
}
return content;
}
}
19 changes: 0 additions & 19 deletions src/main/java/com/vispana/api/model/apppackage/TopGroup.java

This file was deleted.

14 changes: 9 additions & 5 deletions src/main/java/com/vispana/vespa/state/VespaStateClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,30 @@ public VispanaRoot vespaState(String configHost) {
var appUrl = appUrlFork.get();

var appPackageScope = scope.fork(() -> AppPackageAssembler.assemble(appUrl));
var configFork = scope.fork(() -> ConfigNodesAssembler.assemble(configHost, vespaMetrics));
scope
.join()
.throwIfFailed(
throwable -> new RuntimeException("Failed to get app data from Vespa", throwable));
throwable -> new RuntimeException("Failed to get data from Vespa", throwable));
var appPackage = appPackageScope.get();
var configNodes = configFork.get();

// fetch and build Vispana components concurrently and block until tasks are done
var configFork = scope.fork(() -> ConfigNodesAssembler.assemble(configHost, vespaMetrics));
var containerFork = scope.fork(() -> ContainerAssembler.assemble(configHost, vespaMetrics));
var contentFork =
scope.fork(
() ->
ContentAssembler.assemble(
configHost, vespaVersion, vespaMetrics, appUrl, appPackage));
configHost,
vespaVersion,
vespaMetrics,
appUrl,
appPackage,
configNodes.clusters().getFirst().nodes().getFirst().host().hostname()));
scope
.join()
.throwIfFailed(
throwable -> new RuntimeException("Failed to get data from Vespa", throwable));

var configNodes = configFork.get();
var containerNodes = containerFork.get();
var contentNodes = contentFork.get();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public static ContentNodes assemble(
VespaVersion vespaVersion,
Map<String, MetricsNode> vespaMetrics,
String appUrl,
ApplicationPackage appPackage) {
ApplicationPackage appPackage,
String configHostName) {
var contentDistributionUrl = configHost + "/config/v1/vespa.config.content.distribution/";

var contentClusters =
Expand All @@ -51,7 +52,8 @@ public static ContentNodes assemble(
.map(
clusterName -> {
var dispatcher =
fetchDispatcherData(configHost, clusterName, vespaVersion, appPackage);
fetchDispatcherData(
configHost, clusterName, vespaVersion, appPackage, configHostName);
var schemas = fetchSchemas(configHost, clusterName);
var contentDistribution = fetchContentDistributionData(configHost, clusterName);
var distribution =
Expand Down Expand Up @@ -155,7 +157,9 @@ private static List<Node> fetchDispatcherData(
String configHost,
String clusterName,
VespaVersion vespaVersion,
final ApplicationPackage appPackage) {
final ApplicationPackage appPackage,
final String configHostName) {

if (vespaVersion.major() == 7) {
var dispatcherUrl =
configHost + "/config/v1/vespa.config.search.dispatch/" + clusterName + "/search";
Expand All @@ -168,7 +172,7 @@ private static List<Node> fetchDispatcherData(
+ "/search";
return requestGet(dispatcherUrl, SearchDispatchNodesSchema.class).getNode();
} else {
return contentNodesFromAppPackage(appPackage);
return contentNodesFromAppPackage(appPackage, configHostName);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,69 @@
package com.vispana.vespa.state.helpers;

import com.vispana.api.model.apppackage.ApplicationPackage;
import com.vispana.api.model.apppackage.Group;
import com.vispana.api.model.apppackage.Hosts;
import com.vispana.api.model.apppackage.Services;
import com.vispana.client.vespa.model.content.Node;
import java.util.List;
import java.util.stream.Collectors;

public class ContentNodesExtractor {

public static final long DEFAULT_RPC_ADMIN_PORT = 19103L;
public static final String UNKNOWN = "unknown";

public static List<Node> contentNodesFromAppPackage(final ApplicationPackage appPackage) {
public static List<Node> contentNodesFromAppPackage(
final ApplicationPackage appPackage, final String configHostName) {
Services services = Services.fromXml(appPackage.servicesContent());
Hosts hosts = Hosts.fromXml(appPackage.hostsContent());

return services.getContentGroups().stream()
.flatMap(group -> group.getNodes().stream().map(n -> createNode(group, n, hosts)))
if (services.getContent() == null) {
throw new RuntimeException("No content found in services xml");
}

// content can not have both groups and nodes
if (services.getContent().hasNodes()) {
// is single node vespa setup
if (services.getContent().getNodes().size() == 1) {
return List.of(
createNode(
new Group(), services.getContent().getNodes().get(0), hosts, configHostName));
}
return services.getContent().getNodes().stream()
.map(n -> createNode(new Group(), n, hosts, configHostName))
.collect(Collectors.toList());
}

return services.getContent().getGroups().stream()
.flatMap(
group ->
group.getNodes().stream().map(n -> createNode(group, n, hosts, configHostName)))
.collect(Collectors.toList());
}

private static Node createNode(
final com.vispana.api.model.apppackage.Group group,
final com.vispana.api.model.apppackage.Node appPackNode,
final Hosts hosts) {
final Hosts hosts,
final String singleNodeHostName) {
Node node = new Node();
node.setPort(DEFAULT_RPC_ADMIN_PORT);
node.setKey(Long.parseLong(appPackNode.getDistributionKey()));
node.setGroup(Long.parseLong(group.getDistributionKey()));

String hostAlias = appPackNode.getHostAlias();
String hostName =
hosts.getHosts().stream()
.filter(host -> hostAlias.equals(host.getAlias()))
.map(com.vispana.api.model.apppackage.Host::getName)
.findFirst()
.orElseThrow(() -> new RuntimeException("Failed to find host for alias: " + hostAlias));

String hostName = singleNodeHostName;
if (hosts != null && !hosts.getHosts().isEmpty()) {
hostName =
hosts.getHosts().stream()
.filter(host -> hostAlias.equals(host.getAlias()))
.map(com.vispana.api.model.apppackage.Host::getName)
.findFirst()
.orElseThrow(
() -> new RuntimeException("Failed to find host for alias: " + hostAlias));
}
node.setHost(hostName);
return node;
}
Expand Down
6 changes: 5 additions & 1 deletion src/test/java/com/vispana/Helper.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ public static String defaultHostsXmlString() {
}

public static String defaultServicesXmlString() {
return servicesXmlString("xml/services.xml");
}

public static String servicesXmlString(String path) {
ClassLoader loader = Helper.class.getClassLoader();
File file = new File(loader.getResource("xml/services.xml").getFile());
File file = new File(loader.getResource(path).getFile());
try {
return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
} catch (IOException e) {
Expand Down
50 changes: 48 additions & 2 deletions src/test/java/com/vispana/api/model/apppackage/ServicesTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.vispana.api.model.apppackage;

import static com.vispana.Helper.defaultServicesXmlString;
import static com.vispana.Helper.servicesXmlString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

Expand All @@ -10,13 +11,14 @@
class ServicesTest {

@Test
void fromXml() {
void fromXmlForMultiGroup() {
String servicesXmlString = defaultServicesXmlString();

Services services = Services.fromXml(servicesXmlString);
List<Group> groups = services.getContentGroups();
List<Group> groups = services.getContent().getGroups();
assertNotNull(services);
assertEquals(1, groups.size());
assertEquals(0, services.getContent().getNodes().size());
assertEquals("group-0-0", groups.get(0).getName());
assertEquals("0", groups.get(0).getDistributionKey());

Expand All @@ -26,4 +28,48 @@ void fromXml() {
assertEquals("1", groups.get(0).getNodes().get(1).getDistributionKey());
assertEquals("content-0-1", groups.get(0).getNodes().get(1).getHostAlias());
}

@Test
void fromXmlForNoGroups() {
String servicesXmlString = servicesXmlString("xml/services-no-group.xml");
Services services = Services.fromXml(servicesXmlString);
assertNotNull(services);
assertEquals(0, services.getContent().getGroups().size());
assertEquals(2, services.getContent().getNodes().size());
assertEquals("0", services.getContent().getNodes().get(0).getDistributionKey());
assertEquals("content-0-0", services.getContent().getNodes().get(0).getHostAlias());
assertEquals("1", services.getContent().getNodes().get(1).getDistributionKey());
assertEquals("content-0-1", services.getContent().getNodes().get(1).getHostAlias());
}

@Test
void fromXmlForSingleHost() {
String servicesXmlString = servicesXmlString("xml/services-single-host.xml");

Services services = Services.fromXml(servicesXmlString);
assertNotNull(services);
assertEquals(0, services.getContent().getGroups().size());
assertEquals(1, services.getContent().getNodes().size());
assertEquals("0", services.getContent().getNodes().get(0).getDistributionKey());
assertEquals("content-0-0", services.getContent().getNodes().get(0).getHostAlias());
}

@Test
void fromXmlForSingleGroup() {
String servicesXmlString = servicesXmlString("xml/services-single-group.xml");

Services services = Services.fromXml(servicesXmlString);
List<Group> groups = services.getContent().getGroups();
assertNotNull(services);
assertEquals(1, services.getContent().getGroups().size());
assertEquals(0, services.getContent().getNodes().size());
assertEquals("top-group", groups.get(0).getName());
assertEquals("-1", groups.get(0).getDistributionKey());

assertEquals(2, groups.get(0).getNodes().size());
assertEquals("0", groups.get(0).getNodes().get(0).getDistributionKey());
assertEquals("content-0-0", groups.get(0).getNodes().get(0).getHostAlias());
assertEquals("1", groups.get(0).getNodes().get(1).getDistributionKey());
assertEquals("content-0-1", groups.get(0).getNodes().get(1).getHostAlias());
}
}
Loading
Loading