diff options
Diffstat (limited to 'config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java')
-rw-r--r-- | config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java b/config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java new file mode 100644 index 00000000000..9e01718bfc6 --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/serialization/AllocatedHostsSerializer.java @@ -0,0 +1,185 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision.serialization; + +import com.yahoo.config.provision.AllocatedHosts; +import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.Flavor; +import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.NetworkPorts; +import com.yahoo.config.provision.NodeFlavors; +import com.yahoo.config.provision.NodeResources; +import com.yahoo.slime.ArrayTraverser; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Inspector; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.config.SlimeUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * The hosts allocated to an application. + * This can be serialized to/from JSON. + * This is immutable. + * + * @author Ulf Lilleengen + * @author bratseth + */ +public class AllocatedHostsSerializer { + + // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one + // (and rewrite all nodes on startup), changes to the serialized format must be made + // such that what is serialized on version N+1 can be read by version N: + // - ADDING FIELDS: Always ok + // - REMOVING FIELDS: Stop reading the field first. Stop writing it on a later version. + // - CHANGING THE FORMAT OF A FIELD: Don't do it bro. + + private static final String mappingKey = "mapping"; + private static final String hostSpecKey = "hostSpec"; + private static final String hostSpecHostNameKey = "hostName"; + private static final String aliasesKey = "aliases"; + private static final String hostSpecMembershipKey = "membership"; + + // Flavor can be removed when all allocated nodes are docker nodes + private static final String flavorKey = "flavor"; + + private static final String resourcesKey = "resources"; + private static final String vcpuKey = "vcpu"; + private static final String memoryKey = "memory"; + private static final String diskKey = "disk"; + private static final String diskSpeedKey = "diskSpeed"; + + /** Wanted version */ + private static final String hostSpecVespaVersionKey = "vespaVersion"; + + /** Current version */ + private static final String hostSpecCurrentVespaVersionKey = "currentVespaVersion"; + private static final String hostSpecNetworkPortsKey = "ports"; + + + public static byte[] toJson(AllocatedHosts allocatedHosts) throws IOException { + Slime slime = new Slime(); + toSlime(allocatedHosts, slime.setObject()); + return SlimeUtils.toJsonBytes(slime); + } + + public static void toSlime(AllocatedHosts allocatedHosts, Cursor cursor) { + Cursor array = cursor.setArray(mappingKey); + for (HostSpec host : allocatedHosts.getHosts()) + toSlime(host, array.addObject().setObject(hostSpecKey)); + } + + private static void toSlime(HostSpec host, Cursor cursor) { + cursor.setString(hostSpecHostNameKey, host.hostname()); + aliasesToSlime(host, cursor); + host.membership().ifPresent(membership -> { + cursor.setString(hostSpecMembershipKey, membership.stringValue()); + cursor.setString(hostSpecVespaVersionKey, membership.cluster().vespaVersion().toFullString()); + }); + host.flavor().ifPresent(flavor -> toSlime(flavor, cursor)); + host.version().ifPresent(version -> cursor.setString(hostSpecCurrentVespaVersionKey, version.toFullString())); + host.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, cursor.setArray(hostSpecNetworkPortsKey))); + } + + private static void aliasesToSlime(HostSpec spec, Cursor cursor) { + if (spec.aliases().isEmpty()) return; + Cursor aliases = cursor.setArray(aliasesKey); + for (String alias : spec.aliases()) + aliases.addString(alias); + } + + private static void toSlime(Flavor flavor, Cursor object) { + if (flavor.isConfigured()) { + object.setString(flavorKey, flavor.name()); + } + else { + NodeResources resources = flavor.resources(); + Cursor resourcesObject = object.setObject(resourcesKey); + resourcesObject.setDouble(vcpuKey, resources.vcpu()); + resourcesObject.setDouble(memoryKey, resources.memoryGb()); + resourcesObject.setDouble(diskKey, resources.diskGb()); + resourcesObject.setString(diskSpeedKey, diskSpeedToString(resources.diskSpeed())); + } + } + + + public static AllocatedHosts fromJson(byte[] json, Optional<NodeFlavors> nodeFlavors) { + return fromSlime(SlimeUtils.jsonToSlime(json).get(), nodeFlavors); + } + + public static AllocatedHosts fromSlime(Inspector inspector, Optional<NodeFlavors> nodeFlavors) { + Inspector array = inspector.field(mappingKey); + Set<HostSpec> hosts = new LinkedHashSet<>(); + array.traverse((ArrayTraverser)(i, host) -> hosts.add(hostFromSlime(host.field(hostSpecKey), nodeFlavors))); + return AllocatedHosts.withHosts(hosts); + } + + private static HostSpec hostFromSlime(Inspector object, Optional<NodeFlavors> nodeFlavors) { + Optional<ClusterMembership> membership = + object.field(hostSpecMembershipKey).valid() ? Optional.of(membershipFromSlime(object)) : Optional.empty(); + Optional<Flavor> flavor = flavorFromSlime(object, nodeFlavors); + Optional<com.yahoo.component.Version> version = + optionalString(object.field(hostSpecCurrentVespaVersionKey)).map(com.yahoo.component.Version::new); + Optional<NetworkPorts> networkPorts = + NetworkPortsSerializer.fromSlime(object.field(hostSpecNetworkPortsKey)); + return new HostSpec(object.field(hostSpecHostNameKey).asString(), aliasesFromSlime(object), flavor, membership, version, networkPorts); + } + + private static List<String> aliasesFromSlime(Inspector object) { + if ( ! object.field(aliasesKey).valid()) return Collections.emptyList(); + List<String> aliases = new ArrayList<>(); + object.field(aliasesKey).traverse((ArrayTraverser)(index, alias) -> aliases.add(alias.asString())); + return aliases; + } + + private static Optional<Flavor> flavorFromSlime(Inspector object, Optional<NodeFlavors> nodeFlavors) { + if (object.field(flavorKey).valid() && nodeFlavors.isPresent() && nodeFlavors.get().exists(object.field(flavorKey).asString())) { + return nodeFlavors.get().getFlavor(object.field(flavorKey).asString()); + } + else if (object.field(resourcesKey).valid()) { + Inspector resources = object.field(resourcesKey); + return Optional.of(new Flavor(new NodeResources(resources.field(vcpuKey).asDouble(), + resources.field(memoryKey).asDouble(), + resources.field(diskKey).asDouble(), + diskSpeedFromSlime(resources.field(diskSpeedKey))))); + } + else { + return Optional.empty(); + } + } + + private static NodeResources.DiskSpeed diskSpeedFromSlime(Inspector diskSpeed) { + if ( ! diskSpeed.valid()) return NodeResources.DiskSpeed.fast; // TODO: Remove this line after June 2019 + switch (diskSpeed.asString()) { + case "fast" : return NodeResources.DiskSpeed.fast; + case "slow" : return NodeResources.DiskSpeed.slow; + case "any" : return NodeResources.DiskSpeed.any; + default: throw new IllegalStateException("Illegal disk-speed value '" + diskSpeed.asString() + "'"); + } + } + + private static String diskSpeedToString(NodeResources.DiskSpeed diskSpeed) { + switch (diskSpeed) { + case fast : return "fast"; + case slow : return "slow"; + case any : return "any"; + default: throw new IllegalStateException("Illegal disk-speed value '" + diskSpeed + "'"); + } + + } + + private static ClusterMembership membershipFromSlime(Inspector object) { + return ClusterMembership.from(object.field(hostSpecMembershipKey).asString(), + com.yahoo.component.Version.fromString(object.field(hostSpecVespaVersionKey).asString())); + } + + private static Optional<String> optionalString(Inspector inspector) { + if ( ! inspector.valid()) return Optional.empty(); + return Optional.of(inspector.asString()); + } +} |