All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.vespa.hosted.provision.persistence.NodeSerializer Maven / Gradle / Ivy

The newest version!
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.persistence;

import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeFlavors;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.WireguardKey;
import com.yahoo.config.provision.WireguardKeyWithTimestamp;
import com.yahoo.config.provision.host.FlavorOverrides;
import com.yahoo.config.provision.serialization.NetworkPortsSerializer;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Allocation;
import com.yahoo.vespa.hosted.provision.node.Generation;
import com.yahoo.vespa.hosted.provision.node.History;
import com.yahoo.vespa.hosted.provision.node.IP;
import com.yahoo.vespa.hosted.provision.node.OsVersion;
import com.yahoo.vespa.hosted.provision.node.Reports;
import com.yahoo.vespa.hosted.provision.node.Status;
import com.yahoo.vespa.hosted.provision.node.TrustStoreItem;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * Serializes a node to/from JSON.
 * Instances of this are multithread safe and can be reused
 *
 * @author bratseth
 */
public class NodeSerializer {

    // 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.

    /** The configured node flavors */
    private final NodeFlavors flavors;

    // Node fields
    private static final String stateKey = "state";
    private static final String hostnameKey = "hostname";
    private static final String ipAddressesKey = "ipAddresses";
    private static final String ipAddressPoolKey = "additionalIpAddresses";
    private static final String containersKey = "containers";
    private static final String containerHostnameKey = "hostname";
    private static final String idKey = "openStackId";
    private static final String extraIdKey = "extraId";
    private static final String parentHostnameKey = "parentHostname";
    private static final String historyKey = "history";
    private static final String logKey = "log";
    private static final String instanceKey = "instance"; // legacy name, TODO: change to allocation with backwards compat
    private static final String rebootGenerationKey = "rebootGeneration";
    private static final String currentRebootGenerationKey = "currentRebootGeneration";
    private static final String vespaVersionKey = "vespaVersion";
    private static final String currentContainerImageKey = "currentDockerImage";
    private static final String failCountKey = "failCount";
    private static final String nodeTypeKey = "type";
    private static final String wantToRetireKey = "wantToRetire";
    private static final String wantToDeprovisionKey = "wantToDeprovision";
    private static final String wantToRebuildKey = "wantToRebuild";
    private static final String preferToRetireKey = "preferToRetire";
    private static final String wantToFailKey = "wantToFailKey"; // TODO: This should be changed to 'wantToFail'
    private static final String wantToUpgradeFlavorKey = "wantToUpgradeFlavor";
    private static final String osVersionKey = "osVersion";
    private static final String wantedOsVersionKey = "wantedOsVersion";
    private static final String firmwareCheckKey = "firmwareCheck";
    private static final String reportsKey = "reports";
    private static final String modelNameKey = "modelName";
    private static final String reservedToKey = "reservedTo";
    private static final String exclusiveToApplicationIdKey = "exclusiveTo";
    private static final String provisionedForApplicationIdKey = "provisionedFor";
    private static final String hostTTLKey = "hostTTL";
    private static final String hostEmptyAtKey = "hostEmptyAt";
    private static final String exclusiveToClusterTypeKey = "exclusiveToClusterType";
    private static final String switchHostnameKey = "switchHostname";
    private static final String trustedCertificatesKey = "trustedCertificates";
    private static final String cloudAccountKey = "cloudAccount";
    private static final String wireguardPubKeyKey = "wireguardPubkey";
    private static final String wireguardKeyTimestampKey = "wireguardKeyTimestamp";

    // Node resource fields
    private static final String flavorKey = "flavor";
    private static final String resourcesKey = "resources";
    private static final String diskKey = "disk";

    // Allocation fields
    private static final String tenantIdKey = "tenantId";
    private static final String applicationIdKey = "applicationId";
    private static final String instanceIdKey = "instanceId";
    private static final String serviceIdKey = "serviceId"; // legacy name, TODO: change to membership with backwards compat
    private static final String requestedResourcesKey = "requestedResources";
    private static final String restartGenerationKey = "restartGeneration";
    private static final String currentRestartGenerationKey = "currentRestartGeneration";
    private static final String removableKey = "removable";
    private static final String reusableKey = "reusable";
    // Saved as part of allocation instead of serviceId, since serviceId serialized form is not easily extendable.
    private static final String wantedVespaVersionKey = "wantedVespaVersion";
    private static final String wantedContainerImageRepoKey = "wantedDockerImageRepo";

    // History event fields
    private static final String historyEventTypeKey = "type";
    private static final String atKey = "at";
    private static final String agentKey = "agent"; // retired events only

    // Network port fields
    private static final String networkPortsKey = "networkPorts";

    // Trusted certificates fields
    private static final String fingerprintKey = "fingerprint";
    private static final String expiresKey = "expires";

    // ---------------- Serialization ----------------------------------------------------

    public NodeSerializer(NodeFlavors flavors) {
        this.flavors = flavors;
    }

    public byte[] toJson(Node node) {
        try {
            Slime slime = new Slime();
            toSlime(node, slime.setObject());
            return SlimeUtils.toJsonBytes(slime);
        }
        catch (IOException e) {
            throw new RuntimeException("Serialization of " + node + " to json failed", e);
        }
    }

    private void toSlime(Node node, Cursor object) {
        object.setString(hostnameKey, node.hostname());
        object.setString(stateKey, toString(node.state()));
        toSlime(node.ipConfig().primary(), object.setArray(ipAddressesKey), true);
        toSlime(node.ipConfig().pool().ips(), object.setArray(ipAddressPoolKey), true);
        toSlime(node.ipConfig().pool().hostnames(), object);
        object.setString(idKey, node.id());
        node.extraId().ifPresent(id -> object.setString(extraIdKey, id));
        node.parentHostname().ifPresent(hostname -> object.setString(parentHostnameKey, hostname));
        toSlime(node.flavor(), object);
        object.setLong(rebootGenerationKey, node.status().reboot().wanted());
        object.setLong(currentRebootGenerationKey, node.status().reboot().current());
        node.status().vespaVersion().ifPresent(version -> object.setString(vespaVersionKey, version.toString()));
        node.status().containerImage().ifPresent(image -> object.setString(currentContainerImageKey, image.asString()));
        object.setLong(failCountKey, node.status().failCount());
        object.setBool(wantToRetireKey, node.status().wantToRetire());
        object.setBool(preferToRetireKey, node.status().preferToRetire());
        object.setBool(wantToDeprovisionKey, node.status().wantToDeprovision());
        object.setBool(wantToFailKey, node.status().wantToFail());
        object.setBool(wantToRebuildKey, node.status().wantToRebuild());
        object.setBool(wantToUpgradeFlavorKey, node.status().wantToUpgradeFlavor());
        node.allocation().ifPresent(allocation -> toSlime(allocation, object.setObject(instanceKey)));
        toSlime(node.history().events(), object.setArray(historyKey));
        toSlime(node.history().log(), object.setArray(logKey));
        object.setString(nodeTypeKey, toString(node.type()));
        node.status().osVersion().current().ifPresent(version -> object.setString(osVersionKey, version.toString()));
        node.status().osVersion().wanted().ifPresent(version -> object.setString(wantedOsVersionKey, version.toFullString()));
        node.status().firmwareVerifiedAt().ifPresent(instant -> object.setLong(firmwareCheckKey, instant.toEpochMilli()));
        node.switchHostname().ifPresent(switchHostname -> object.setString(switchHostnameKey, switchHostname));
        node.reports().toSlime(object, reportsKey);
        node.modelName().ifPresent(modelName -> object.setString(modelNameKey, modelName));
        node.reservedTo().ifPresent(tenant -> object.setString(reservedToKey, tenant.value()));
        node.exclusiveToApplicationId().ifPresent(applicationId -> object.setString(exclusiveToApplicationIdKey, applicationId.serializedForm()));
        node.provisionedForApplicationId().ifPresent(applicationId -> object.setString(provisionedForApplicationIdKey, applicationId.serializedForm()));
        node.hostTTL().ifPresent(hostTTL -> object.setLong(hostTTLKey, hostTTL.toMillis()));
        node.hostEmptyAt().ifPresent(emptyAt -> object.setLong(hostEmptyAtKey, emptyAt.toEpochMilli()));
        node.exclusiveToClusterType().ifPresent(clusterType -> object.setString(exclusiveToClusterTypeKey, clusterType.name()));
        trustedCertificatesToSlime(node.trustedCertificates(), object.setArray(trustedCertificatesKey));
        if (!node.cloudAccount().isUnspecified()) {
            object.setString(cloudAccountKey, node.cloudAccount().value());
        }
        node.wireguardPubKey().ifPresent(pubKey -> {
            object.setString(wireguardPubKeyKey, pubKey.key().value());
            object.setLong(wireguardKeyTimestampKey, pubKey.timestamp().toEpochMilli());
        });
    }

    private void toSlime(Flavor flavor, Cursor object) {
        if (flavor.isConfigured()) {
            object.setString(flavorKey, flavor.name());
            if (flavor.flavorOverrides().isPresent()) {
                Cursor resourcesObject = object.setObject(resourcesKey);
                flavor.flavorOverrides().get().diskGb().ifPresent(diskGb -> resourcesObject.setDouble(diskKey, diskGb));
            }
        }
        else {
            NodeResourcesSerializer.toSlime(flavor.resources(), object.setObject(resourcesKey));
        }
    }

    private void toSlime(Allocation allocation, Cursor object) {
        NodeResourcesSerializer.toSlime(allocation.requestedResources(), object.setObject(requestedResourcesKey));
        object.setString(tenantIdKey, allocation.owner().tenant().value());
        object.setString(applicationIdKey, allocation.owner().application().value());
        object.setString(instanceIdKey, allocation.owner().instance().value());
        object.setString(serviceIdKey, allocation.membership().stringValue());
        object.setLong(restartGenerationKey, allocation.restartGeneration().wanted());
        object.setLong(currentRestartGenerationKey, allocation.restartGeneration().current());
        object.setBool(removableKey, allocation.removable());
        object.setBool(reusableKey, allocation.reusable());
        object.setString(wantedVespaVersionKey, allocation.membership().cluster().vespaVersion().toString());
        allocation.membership().cluster().dockerImageRepo().ifPresent(repo -> object.setString(wantedContainerImageRepoKey, repo.untagged()));
        allocation.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, object.setArray(networkPortsKey)));
    }

    private void toSlime(Collection events, Cursor array) {
        for (History.Event event : events)
            toSlime(event, array.addObject());
    }

    private void toSlime(History.Event event, Cursor object) {
        object.setString(historyEventTypeKey, toString(event.type()));
        object.setLong(atKey, event.at().toEpochMilli());
        object.setString(agentKey, toString(event.agent()));
    }

    private void toSlime(List addresses, Cursor array, boolean dummyDueToErasure) {
        // Validating IP address string literals is expensive, so we do it at serialization time instead of Node
        // construction time
        addresses.stream().map(IP::parse).map(IP::asString).forEach(array::addString);
    }

    private void toSlime(List hostnames, Cursor object) {
        if (hostnames.isEmpty()) return;
        Cursor containersArray = object.setArray(containersKey);
        hostnames.forEach(hostname -> {
            containersArray.addObject().setString(containerHostnameKey, hostname.value());
        });
    }

    private void trustedCertificatesToSlime(List trustStoreItems, Cursor array) {
        trustStoreItems.forEach(cert -> {
            Cursor object = array.addObject();
            object.setString(fingerprintKey, cert.fingerprint());
            object.setLong(expiresKey, cert.expiry().toEpochMilli());
        });
    }

    // ---------------- Deserialization --------------------------------------------------

    public Node fromJson(byte[] data) {
        return nodeFromSlime(SlimeUtils.jsonToSlime(data).get());
    }

    private Node nodeFromSlime(Inspector object) {
        Flavor flavor = flavorFromSlime(object);
        return new Node(object.field(idKey).asString(),
                        SlimeUtils.optionalString(object.field(extraIdKey)),
                        IP.Config.of(ipAddressesFromSlime(object, ipAddressesKey),
                                     ipAddressesFromSlime(object, ipAddressPoolKey),
                                     hostnamesFromSlime(object)),
                        object.field(hostnameKey).asString(),
                        SlimeUtils.optionalString(object.field(parentHostnameKey)),
                        flavor,
                        statusFromSlime(object),
                        nodeStateFromString(object.field(stateKey).asString()),
                        allocationFromSlime(flavor.resources(), object.field(instanceKey)),
                        historyFromSlime(object),
                        nodeTypeFromString(object.field(nodeTypeKey).asString()),
                        Reports.fromSlime(object.field(reportsKey)),
                        SlimeUtils.optionalString(object.field(modelNameKey)),
                        SlimeUtils.optionalString(object.field(reservedToKey)).map(TenantName::from),
                        SlimeUtils.optionalString(object.field(exclusiveToApplicationIdKey)).map(ApplicationId::fromSerializedForm),
                        SlimeUtils.optionalString(object.field(provisionedForApplicationIdKey)).map(ApplicationId::fromSerializedForm),
                        SlimeUtils.optionalDuration(object.field(hostTTLKey)),
                        SlimeUtils.optionalInstant(object.field(hostEmptyAtKey)),
                        SlimeUtils.optionalString(object.field(exclusiveToClusterTypeKey)).map(ClusterSpec.Type::from),
                        SlimeUtils.optionalString(object.field(switchHostnameKey)),
                        trustedCertificatesFromSlime(object),
                        SlimeUtils.optionalString(object.field(cloudAccountKey)).map(CloudAccount::from).orElse(CloudAccount.empty),
                        wireguardKeyWithTimestampFromSlime(object.field(wireguardPubKeyKey), object.field(wireguardKeyTimestampKey)));
    }

    private Status statusFromSlime(Inspector object) {
        return new Status(generationFromSlime(object, rebootGenerationKey, currentRebootGenerationKey),
                          versionFromSlime(object.field(vespaVersionKey)),
                          containerImageFromSlime(object.field(currentContainerImageKey)),
                          (int) object.field(failCountKey).asLong(),
                          object.field(wantToRetireKey).asBool(),
                          object.field(wantToDeprovisionKey).asBool(),
                          object.field(wantToRebuildKey).asBool(),
                          object.field(preferToRetireKey).asBool(),
                          object.field(wantToFailKey).asBool(),
                          object.field(wantToUpgradeFlavorKey).asBool(),
                          new OsVersion(versionFromSlime(object.field(osVersionKey)),
                                                       versionFromSlime(object.field(wantedOsVersionKey))),
                          SlimeUtils.optionalInstant(object.field(firmwareCheckKey)));
    }

    private Flavor flavorFromSlime(Inspector object) {
        Inspector resources = object.field(resourcesKey);

        if (object.field(flavorKey).valid()) {
            Flavor flavor = flavors.getFlavorOrThrow(object.field(flavorKey).asString());
            if (!resources.valid()) return flavor;
            return flavor.with(FlavorOverrides.ofDisk(resources.field(diskKey).asDouble()));
        }
        else {
            return new Flavor(NodeResourcesSerializer.resourcesFromSlime(resources));
        }
    }

    private Optional allocationFromSlime(NodeResources assignedResources, Inspector object) {
        if ( ! object.valid()) return Optional.empty();
        return Optional.of(new Allocation(applicationIdFromSlime(object),
                                          clusterMembershipFromSlime(object),
                                          NodeResourcesSerializer.optionalResourcesFromSlime(object.field(requestedResourcesKey))
                                                                 .orElse(assignedResources),
                                          generationFromSlime(object, restartGenerationKey, currentRestartGenerationKey),
                                          object.field(removableKey).asBool(),
                                          object.field(reusableKey).asBool(),
                                          NetworkPortsSerializer.fromSlime(object.field(networkPortsKey))));
    }

    private ApplicationId applicationIdFromSlime(Inspector object) {
        return ApplicationId.from(TenantName.from(object.field(tenantIdKey).asString()),
                                  ApplicationName.from(object.field(applicationIdKey).asString()),
                                  InstanceName.from(object.field(instanceIdKey).asString()));
    }

    private History historyFromSlime(Inspector object) {
        return new History(eventsFromSlime(object.field(historyKey)),
                           eventsFromSlime(object.field(logKey)));
    }

    private List eventsFromSlime(Inspector array) {
        if (!array.valid()) return List.of();
        List events = new ArrayList<>();
        array.traverse((ArrayTraverser) (int i, Inspector item) -> {
            History.Event event = eventFromSlime(item);
            if (event != null)
                events.add(event);
        });
        return events;
    }

    private History.Event eventFromSlime(Inspector object) {
        History.Event.Type type = eventTypeFromString(object.field(historyEventTypeKey).asString());
        if (type == null) return null;
        Instant at = Instant.ofEpochMilli(object.field(atKey).asLong());
        Agent agent = eventAgentFromSlime(object.field(agentKey));
        return new History.Event(type, agent, at);
    }

    private Generation generationFromSlime(Inspector object, String wantedField, String currentField) {
        Inspector current = object.field(currentField);
        return new Generation(object.field(wantedField).asLong(), current.asLong());
    }

    private ClusterMembership clusterMembershipFromSlime(Inspector object) {
        return ClusterMembership.from(object.field(serviceIdKey).asString(),
                                      versionFromSlime(object.field(wantedVespaVersionKey)).get(),
                                      containerImageFromSlime(object.field(wantedContainerImageRepoKey)));
    }

    private Optional versionFromSlime(Inspector object) {
        return object.valid() ? Optional.of(Version.fromString(object.asString())) : Optional.empty();
    }

    private Optional containerImageFromSlime(Inspector object) {
        return SlimeUtils.optionalString(object).map(DockerImage::fromString);
    }

    private List ipAddressesFromSlime(Inspector object, String key) {
        var ipAddresses = new ArrayList();
        object.field(key).traverse((ArrayTraverser) (i, item) -> ipAddresses.add(item.asString()));
        return ipAddresses;
    }

    private List hostnamesFromSlime(Inspector object) {
        return SlimeUtils.entriesStream(object.field(containersKey))
                         .map(elem -> HostName.of(elem.field(containerHostnameKey).asString()))
                         .toList();
    }

    private List trustedCertificatesFromSlime(Inspector object) {
        return SlimeUtils.entriesStream(object.field(trustedCertificatesKey))
                         .map(elem -> new TrustStoreItem(elem.field(fingerprintKey).asString(),
                                                         Instant.ofEpochMilli(elem.field(expiresKey).asLong())))
                         .toList();
    }

    private Optional wireguardKeyWithTimestampFromSlime(Inspector keyObject, Inspector timestampObject) {
        if ( ! keyObject.valid()) return Optional.empty();
        return SlimeUtils.optionalString(keyObject).map(
                key -> new WireguardKeyWithTimestamp(WireguardKey.from(key),
                                                     SlimeUtils.optionalInstant(timestampObject).orElse(null)));
    }

    // ----------------- Enum <-> string mappings ----------------------------------------
    
    /** Returns the event type, or null if this event type should be ignored */
    private History.Event.Type eventTypeFromString(String eventTypeString) {
        return switch (eventTypeString) {
            case "provisioned" -> History.Event.Type.provisioned;
            case "deprovisioned" -> History.Event.Type.deprovisioned;
            case "readied" -> History.Event.Type.readied;
            case "reserved" -> History.Event.Type.reserved;
            case "activated" -> History.Event.Type.activated;
            case "wantToRetire" -> History.Event.Type.wantToRetire;
            case "wantToFail" -> History.Event.Type.wantToFail;
            case "retired" -> History.Event.Type.retired;
            case "deactivated" -> History.Event.Type.deactivated;
            case "parked" -> History.Event.Type.parked;
            case "failed" -> History.Event.Type.failed;
            case "deallocated" -> History.Event.Type.deallocated;
            case "down" -> History.Event.Type.down;
            case "up" -> History.Event.Type.up;
            case "suspended" -> History.Event.Type.suspended;
            case "resumed" -> History.Event.Type.resumed;
            case "resized" -> History.Event.Type.resized;
            case "rebooted" -> History.Event.Type.rebooted;
            case "osUpgraded" -> History.Event.Type.osUpgraded;
            case "firmwareVerified" -> History.Event.Type.firmwareVerified;
            case "breakfixed" -> History.Event.Type.breakfixed;
            case "preferToRetire" -> History.Event.Type.preferToRetire;
            default -> throw new IllegalArgumentException("Unknown node event type '" + eventTypeString + "'");
        };
    }

    private String toString(History.Event.Type nodeEventType) {
        return switch (nodeEventType) {
            case provisioned -> "provisioned";
            case deprovisioned -> "deprovisioned";
            case readied -> "readied";
            case reserved -> "reserved";
            case activated -> "activated";
            case wantToRetire -> "wantToRetire";
            case wantToFail -> "wantToFail";
            case retired -> "retired";
            case deactivated -> "deactivated";
            case parked -> "parked";
            case failed -> "failed";
            case deallocated -> "deallocated";
            case down -> "down";
            case up -> "up";
            case suspended -> "suspended";
            case resumed -> "resumed";
            case resized -> "resized";
            case rebooted -> "rebooted";
            case osUpgraded -> "osUpgraded";
            case firmwareVerified -> "firmwareVerified";
            case breakfixed -> "breakfixed";
            case preferToRetire -> "preferToRetire";
        };
    }

    private Agent eventAgentFromSlime(Inspector eventAgentField) {
        return switch (eventAgentField.asString()) {
            case "operator" -> Agent.operator;
            case "application" -> Agent.application;
            case "system" -> Agent.system;
            case "nodeAdmin" -> Agent.nodeAdmin;
            case "DirtyExpirer" -> Agent.DirtyExpirer;
            case "DynamicProvisioningMaintainer", "HostCapacityMaintainer" -> Agent.HostCapacityMaintainer;
            case "HostResumeProvisioner" -> Agent.HostResumeProvisioner;
            case "FailedExpirer" -> Agent.FailedExpirer;
            case "InactiveExpirer" -> Agent.InactiveExpirer;
            case "NodeFailer" -> Agent.NodeFailer;
            case "NodeHealthTracker" -> Agent.NodeHealthTracker;
            case "ProvisionedExpirer" -> Agent.ProvisionedExpirer;
            case "Rebalancer" -> Agent.Rebalancer;
            case "ReservationExpirer" -> Agent.ReservationExpirer;
            case "RetiringUpgrader" -> Agent.RetiringOsUpgrader;
            case "RebuildingOsUpgrader" -> Agent.RebuildingOsUpgrader;
            case "SpareCapacityMaintainer" -> Agent.SpareCapacityMaintainer;
            case "SwitchRebalancer" -> Agent.SwitchRebalancer;
            case "HostEncrypter" -> Agent.HostEncrypter;
            case "ParkedExpirer" -> Agent.ParkedExpirer;
            case "HostFlavorUpgrader" -> Agent.HostFlavorUpgrader;
            default -> throw new IllegalArgumentException("Unknown node event agent '" + eventAgentField.asString() + "'");
        };
    }
    private String toString(Agent agent) {
        return switch (agent) {
            case operator -> "operator";
            case application -> "application";
            case system -> "system";
            case nodeAdmin -> "nodeAdmin";
            case DirtyExpirer -> "DirtyExpirer";
            case HostCapacityMaintainer -> "DynamicProvisioningMaintainer";
            case HostResumeProvisioner -> "HostResumeProvisioner";
            case FailedExpirer -> "FailedExpirer";
            case InactiveExpirer -> "InactiveExpirer";
            case NodeFailer -> "NodeFailer";
            case NodeHealthTracker -> "NodeHealthTracker";
            case ProvisionedExpirer -> "ProvisionedExpirer";
            case Rebalancer -> "Rebalancer";
            case ReservationExpirer -> "ReservationExpirer";
            case RetiringOsUpgrader -> "RetiringUpgrader";
            case RebuildingOsUpgrader -> "RebuildingOsUpgrader";
            case SpareCapacityMaintainer -> "SpareCapacityMaintainer";
            case SwitchRebalancer -> "SwitchRebalancer";
            case HostEncrypter -> "HostEncrypter";
            case ParkedExpirer -> "ParkedExpirer";
            case HostFlavorUpgrader -> "HostFlavorUpgrader";
        };
    }

    static NodeType nodeTypeFromString(String typeString) {
        return switch (typeString) {
            case "tenant" -> NodeType.tenant;
            case "host" -> NodeType.host;
            case "proxy" -> NodeType.proxy;
            case "proxyhost" -> NodeType.proxyhost;
            case "config" -> NodeType.config;
            case "confighost" -> NodeType.confighost;
            case "controller" -> NodeType.controller;
            case "controllerhost" -> NodeType.controllerhost;
            default -> throw new IllegalArgumentException("Unknown node type '" + typeString + "'");
        };
    }

    static String toString(NodeType type) {
        return switch (type) {
            case tenant -> "tenant";
            case host -> "host";
            case proxy -> "proxy";
            case proxyhost -> "proxyhost";
            case config -> "config";
            case confighost -> "confighost";
            case controller -> "controller";
            case controllerhost -> "controllerhost";
        };
    }

    static Node.State nodeStateFromString(String state) {
        return switch (state) {
            case "active" -> Node.State.active;
            case "dirty" -> Node.State.dirty;
            case "failed" -> Node.State.failed;
            case "inactive" -> Node.State.inactive;
            case "parked" -> Node.State.parked;
            case "provisioned" -> Node.State.provisioned;
            case "ready" -> Node.State.ready;
            case "reserved" -> Node.State.reserved;
            case "deprovisioned" -> Node.State.deprovisioned;
            case "breakfixed" -> Node.State.breakfixed;
            default -> throw new IllegalArgumentException("Unknown node state '" + state + "'");
        };
    }

    static String toString(Node.State state) {
        return switch (state) {
            case active -> "active";
            case dirty -> "dirty";
            case failed -> "failed";
            case inactive -> "inactive";
            case parked -> "parked";
            case provisioned -> "provisioned";
            case ready -> "ready";
            case reserved -> "reserved";
            case deprovisioned -> "deprovisioned";
            case breakfixed -> "breakfixed";
        };
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy