aboutsummaryrefslogtreecommitdiffstats
path: root/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java
blob: 785077795851fc9ac3489026574aed86e661892e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// 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;

import com.google.common.collect.ImmutableList;
import com.yahoo.config.provisioning.FlavorsConfig;

import java.util.ArrayList;
import java.util.List;

/**
 * A host flavor (type). This is a value object where the identity is the name.
 * Use {@link NodeFlavors} to create a flavor.
 *
 * @author bratseth
 */
public class Flavor {

    private final String name;
    private final int cost;
    private final boolean isStock;
    private final Type type;
    private final double minCpuCores;
    private final double minMainMemoryAvailableGb;
    private final double minDiskAvailableGb;
    private final boolean fastDisk;
    private final String description;
    private final boolean retired;
    private List<Flavor> replacesFlavors;
    private int idealHeadroom; // Note: Not used after Vespa 6.282

    /**
     * Creates a Flavor, but does not set the replacesFlavors.
     * @param flavorConfig config to be used for Flavor.
     */
    public Flavor(FlavorsConfig.Flavor flavorConfig) {
        this.name = flavorConfig.name();
        this.replacesFlavors = new ArrayList<>();
        this.cost = flavorConfig.cost();
        this.isStock = flavorConfig.stock();
        this.type = Type.valueOf(flavorConfig.environment());
        this.minCpuCores = flavorConfig.minCpuCores();
        this.minMainMemoryAvailableGb = flavorConfig.minMainMemoryAvailableGb();
        this.minDiskAvailableGb = flavorConfig.minDiskAvailableGb();
        this.fastDisk = flavorConfig.fastDisk();
        this.description = flavorConfig.description();
        this.retired = flavorConfig.retired();
        this.idealHeadroom = flavorConfig.idealHeadroom();
    }

    /** Returns the unique identity of this flavor */
    public String name() { return name; }

    /**
     * Get the monthly cost (total cost of ownership) in USD for this flavor, typically total cost
     * divided by 36 months.
     * 
     * @return monthly cost in USD
     */
    public int cost() { return cost; }
    
    public boolean isStock() { return isStock; }

    public double getMinMainMemoryAvailableGb() { return minMainMemoryAvailableGb; }

    public double getMinDiskAvailableGb() { return minDiskAvailableGb; }

    public boolean hasFastDisk() { return fastDisk; }

    public double getMinCpuCores() { return minCpuCores; }

    public String getDescription() { return description; }

    /** Returns whether the flavor is retired */
    public boolean isRetired() {
        return retired;
    }

    public Type getType() { return type; }
    
    /** Convenience, returns getType() == Type.DOCKER_CONTAINER */
    public boolean isDocker() { return type == Type.DOCKER_CONTAINER; }

    /** The free capacity we would like to preserve for this flavor */
    public int getIdealHeadroom() {
        return idealHeadroom;
    }

    /**
     * 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 is a canonical flavor */
    public boolean isCanonical() {
        return replacesFlavors.size() != 1;
    }

    /**
     * The flavors this (directly) replaces.
     * This is immutable if this is frozen, and a mutable list otherwise.
     */
    public List<Flavor> replaces() { return replacesFlavors; }

    /**
     * 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;
    }

    /** Irreversibly freezes the content of this */
    public void freeze() {
        replacesFlavors = ImmutableList.copyOf(replacesFlavors);
    }
    
    /** Returns whether this flavor has at least as much as each hardware resource as the given flavor */
    public boolean isLargerThan(Flavor other) {
        return this.minCpuCores >= other.minCpuCores &&
               this.minDiskAvailableGb >= other.minDiskAvailableGb &&
               this.minMainMemoryAvailableGb >= other.minMainMemoryAvailableGb &&
               this.fastDisk || ! other.fastDisk;
    }

    @Override
    public int hashCode() { return name.hashCode(); }

    @Override
    public boolean equals(Object other) {
        if (other == this) return true;
        if ( ! (other instanceof Flavor)) return false;
        return ((Flavor)other).name.equals(this.name);
    }

    @Override
    public String toString() { return "flavor '" + name + "'"; }

    public enum Type {
        undefined, // Default value in config (flavors.def)
        BARE_METAL,
        VIRTUAL_MACHINE,
        DOCKER_CONTAINER
    }

}