/** * Returns the canonical name of this flavor - which is the name which should be used as an interface to users. * The canonical name of this flavor is: * <ul> * <li>If it replaces one flavor, the canonical name of the flavor it replaces * <li>If it replaces multiple or no flavors - itself * </ul> * * The logic is that we can use this to capture the gritty details of configurations in exact flavor names * but also encourage users to refer to them by a common name by letting such flavor variants declare that they * replace the canonical name we want. However, if a node replaces multiple names, we have no basis for choosing one * of them as the canonical, so we return the current as canonical. */ public String canonicalName() { return isCanonical() ? name : replacesFlavors.get(0).canonicalName(); }
/** * Returns whether this flavor satisfies the requested flavor, either directly * (by being the same), or by directly or indirectly replacing it */ public boolean satisfies(Flavor flavor) { if (this.equals(flavor)) { return true; } if (this.retired) { return false; } for (Flavor replaces : replacesFlavors) if (replaces.satisfies(flavor)) return true; return false; }
static ResourceCapacity of(Flavor flavor) { ResourceCapacity capacity = new ResourceCapacity(); capacity.memory = flavor.getMinMainMemoryAvailableGb(); capacity.cpu = flavor.getMinCpuCores(); capacity.disk = flavor.getMinDiskAvailableGb(); return capacity; }
object.setString("flavor", node.flavor().name()); object.setString("canonicalFlavor", node.flavor().canonicalName()); object.setDouble("minDiskAvailableGb", node.flavor().getMinDiskAvailableGb()); object.setDouble("minMainMemoryAvailableGb", node.flavor().getMinMainMemoryAvailableGb()); if (node.flavor().getDescription() != null && ! node.flavor().getDescription().isEmpty()) object.setString("description", node.flavor().getDescription()); object.setDouble("minCpuCores", node.flavor().getMinCpuCores()); if (node.flavor().cost() > 0) object.setLong("cost", node.flavor().cost()); object.setBool("fastDisk", node.flavor().hasFastDisk()); object.setDouble("bandwidth", node.flavor().getBandwidth()); object.setString("environment", node.flavor().getType().name()); if (node.allocation().isPresent()) { toSlime(node.allocation().get().owner(), object.setObject("owner"));
private void setHwInfo(ProtonConfig.Builder builder) { builder.hwinfo.disk.size((long)nodeFlavor.getMinDiskAvailableGb() * GB); builder.hwinfo.disk.shared(nodeFlavor.getType().equals(Flavor.Type.DOCKER_CONTAINER)); builder.hwinfo.memory.size((long)nodeFlavor.getMinMainMemoryAvailableGb() * GB); builder.hwinfo.cpu.cores((int)nodeFlavor.getMinCpuCores()); }
private static Collection<Flavor> toFlavors(FlavorsConfig config) { Map<String, Flavor> flavors = new HashMap<>(); // First pass, create all flavors, but do not include flavorReplacesConfig. for (FlavorsConfig.Flavor flavorConfig : config.flavor()) { flavors.put(flavorConfig.name(), new Flavor(flavorConfig)); } // Second pass, set flavorReplacesConfig to point to correct flavor. for (FlavorsConfig.Flavor flavorConfig : config.flavor()) { Flavor flavor = flavors.get(flavorConfig.name()); for (FlavorsConfig.Flavor.Replaces flavorReplacesConfig : flavorConfig.replaces()) { if (! flavors.containsKey(flavorReplacesConfig.name())) { throw new IllegalStateException("Replaces for " + flavor.name() + " pointing to a non existing flavor: " + flavorReplacesConfig.name()); } flavor.replaces().add(flavors.get(flavorReplacesConfig.name())); } flavor.freeze(); } // Third pass, ensure that retired flavors have a replacement for (Flavor flavor : flavors.values()) { if (flavor.isRetired() && !hasReplacement(flavors.values(), flavor)) { throw new IllegalStateException( String.format("Flavor '%s' is retired, but has no replacement", flavor.name()) ); } } return flavors.values(); }
@Override public String toString() { return flavor.name() + " has " + numReady + " ready nodes and " + numActive + " active nodes"; } }
@Override public Optional<String> shouldRetire(Node node) { if (node.flavor().getType() == Flavor.Type.VIRTUAL_MACHINE) return Optional.empty(); boolean shouldRetire = node.ipAddresses().stream() .map(InetAddresses::forString) .allMatch(address -> address instanceof Inet4Address); return shouldRetire ? Optional.of("Node is IPv4-only") : Optional.empty(); } }
private void updateDockerMetrics(List<Node> nodes) { // Capacity flavors for docker DockerHostCapacity capacity = new DockerHostCapacity(nodes); metric.set("hostedVespa.docker.totalCapacityCpu", capacity.getCapacityTotal().getCpu(), null); metric.set("hostedVespa.docker.totalCapacityMem", capacity.getCapacityTotal().getMemory(), null); metric.set("hostedVespa.docker.totalCapacityDisk", capacity.getCapacityTotal().getDisk(), null); metric.set("hostedVespa.docker.freeCapacityCpu", capacity.getFreeCapacityTotal().getCpu(), null); metric.set("hostedVespa.docker.freeCapacityMem", capacity.getFreeCapacityTotal().getMemory(), null); metric.set("hostedVespa.docker.freeCapacityDisk", capacity.getFreeCapacityTotal().getDisk(), null); List<Flavor> dockerFlavors = nodeRepository().getAvailableFlavors().getFlavors().stream() .filter(f -> f.getType().equals(Flavor.Type.DOCKER_CONTAINER)) .collect(Collectors.toList()); for (Flavor flavor : dockerFlavors) { Metric.Context context = getContextAt("flavor", flavor.name()); metric.set("hostedVespa.docker.freeCapacityFlavor", capacity.freeCapacityInFlavorEquivalence(flavor), context); metric.set("hostedVespa.docker.hostsAvailableFlavor", capacity.getNofHostsAvailableFor(flavor), context); } } }
private void tuneSummaryCache(ProtonConfig.Summary.Cache.Builder builder) { long memoryLimitBytes = (long) ((nodeFlavor.getMinMainMemoryAvailableGb() * 0.05) * GB); builder.maxbytes(memoryLimitBytes); }
private void tuneDiskWriteSpeed(ProtonConfig.Builder builder) { if (!nodeFlavor.hasFastDisk()) { builder.hwinfo.disk.writespeed(40); } }
/** Returns a copy of this node which is retired */ public Node retire(Instant retiredAt) { if (flavor.isRetired() || status.wantToRetire()) return retire(Agent.system, retiredAt); else return retire(Agent.application, retiredAt); }
private static Set<Flavor> recursiveReplacements(Flavor flavor, Set<Flavor> replacements) { replacements.add(flavor); for (Flavor replaces : flavor.replaces()) { recursiveReplacements(replaces, replacements); } return replacements; }
@Override public boolean matchesExactly(Flavor flavor) { return flavor.equals(this.requestedFlavor); }
private void tuneFlushStrategyTlsSize(ProtonConfig.Flush.Memory.Builder builder) { long tlsSizeBytes = (long) ((nodeFlavor.getMinDiskAvailableGb() * 0.07) * GB); tlsSizeBytes = min(tlsSizeBytes, 100 * GB); builder.maxtlssize(tlsSizeBytes); }
@Inject public NodeFlavors(FlavorsConfig config) { ImmutableMap.Builder<String, Flavor> b = new ImmutableMap.Builder<>(); for (Flavor flavor : toFlavors(config)) b.put(flavor.name(), flavor); this.flavors = b.build(); }
private boolean isDocker() { Flavor flavor = getFlavor(requestedNodes); return (flavor != null) && flavor.getType().equals(Flavor.Type.DOCKER_CONTAINER); }
private void tuneDocumentStoreMaxFileSize(ProtonConfig.Summary.Log.Builder builder) { double memoryGb = nodeFlavor.getMinMainMemoryAvailableGb(); long fileSizeBytes = 4 * GB; if (memoryGb <= 12.0) { fileSizeBytes = 256 * MB; } else if (memoryGb < 24.0) { fileSizeBytes = 512 * MB; } else if (memoryGb <= 64.0) { fileSizeBytes = 1 * GB; } builder.maxfilesize(fileSizeBytes); }
private void tuneSummaryReadIo(ProtonConfig.Summary.Read.Builder builder) { if (nodeFlavor.hasFastDisk()) { builder.io(ProtonConfig.Summary.Read.Io.DIRECTIO); } }