Skip to content

Commit 59b8f50

Browse files
authored
Merge pull request #36560 from vespa-engine/bratseth/availability-zones-2
Bratseth/availability zones 2
2 parents 337cf18 + 65e9023 commit 59b8f50

19 files changed

Lines changed: 277 additions & 85 deletions

File tree

config-model-api/abi-spec.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@
496496
"methods" : [
497497
"public void <init>(java.util.List)",
498498
"public java.util.List zones()",
499+
"public java.util.Optional zone(com.yahoo.config.provision.Zone)",
499500
"public java.util.List steps()",
500501
"public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
501502
"public java.time.Duration delay()",
@@ -565,6 +566,7 @@
565566
"public java.util.Optional hostTTL(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
566567
"public com.yahoo.config.provision.ZoneEndpoint zoneEndpoint(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Zone, com.yahoo.config.provision.ClusterSpec$Id, boolean)",
567568
"public com.yahoo.config.provision.ZoneEndpoint zoneEndpoint(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.zone.ZoneId, com.yahoo.config.provision.ClusterSpec$Id, boolean)",
569+
"public java.util.List availabilityZones(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Zone)",
568570
"public java.lang.String xmlForm()",
569571
"public java.util.Optional instance(com.yahoo.config.provision.InstanceName)",
570572
"public com.yahoo.config.application.api.DeploymentInstanceSpec requireInstance(java.lang.String)",

config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,13 @@ public ZoneEndpoint zoneEndpoint(InstanceName instance, ZoneId zone, ClusterSpec
312312
.orElse(ZoneEndpoint.defaultEndpoint);
313313
}
314314

315+
public List<AzName> availabilityZones(InstanceName instance, Zone zone) {
316+
var declaredInstance = instance(instance);
317+
if (declaredInstance.isEmpty()) return List.of();
318+
var declaredZone = declaredInstance.get().zone(zone);
319+
return declaredZone.map(DeclaredZone::availabilityZones).orElse(List.of());
320+
}
321+
315322
/** Returns the XML form of this spec, or null if it was not created by fromXml, nor is empty */
316323
public String xmlForm() { return xmlForm; }
317324

@@ -664,6 +671,15 @@ public List<DeclaredZone> zones() {
664671
.toList();
665672
}
666673

674+
/** Returns the declared zone matching the given zone spec in this instance, if any. */
675+
public Optional<DeclaredZone> zone(Zone zone) {
676+
return steps.stream()
677+
.filter(step -> step.concerns(zone.environment()))
678+
.flatMap(step -> step.zones().stream())
679+
.filter(declaredZone -> declaredZone.concerns(zone.environment(), Optional.of(zone.region())))
680+
.findAny();
681+
}
682+
667683
@Override
668684
public List<Step> steps() { return steps; }
669685

config-model/src/main/java/com/yahoo/config/model/ConfigModelContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import com.yahoo.config.model.deploy.DeployState;
1010
import com.yahoo.config.model.producer.AnyConfigProducer;
1111
import com.yahoo.config.model.producer.TreeConfigProducer;
12+
import com.yahoo.config.provision.AzName;
1213
import com.yahoo.config.provision.ClusterInfo;
1314
import com.yahoo.vespa.model.VespaModel;
1415

1516
import java.util.Comparator;
17+
import java.util.List;
1618
import java.util.stream.Stream;
1719

1820
/**
@@ -139,6 +141,10 @@ public static ApplicationType fromString(String value) {
139141
.orElse(DEFAULT);
140142

141143
}
144+
}
142145

146+
public List<AzName> availabilityZones() {
147+
return getDeployState().availabilityZones(properties().applicationId().instance());
143148
}
149+
144150
}

config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import com.yahoo.config.model.provision.HostsXmlProvisioner;
2828
import com.yahoo.config.model.provision.SingleNodeProvisioner;
2929
import com.yahoo.config.model.test.MockApplicationPackage;
30+
import com.yahoo.config.provision.AzName;
3031
import com.yahoo.config.provision.DockerImage;
32+
import com.yahoo.config.provision.InstanceName;
3133
import com.yahoo.config.provision.Zone;
3234
import com.yahoo.io.IOUtils;
3335
import com.yahoo.schema.Application;
@@ -304,6 +306,10 @@ public boolean isHostedTenantApplication(ApplicationType type) {
304306
&& !zone().system().isKubernetesLike();
305307
}
306308

309+
public List<AzName> availabilityZones(InstanceName instance) {
310+
return applicationPackage.getDeploymentSpec().availabilityZones(instance, zone);
311+
}
312+
307313
public static class Builder {
308314

309315
private ApplicationPackage applicationPackage = MockApplicationPackage.createEmpty();

config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.yahoo.component.Version;
66
import com.yahoo.config.application.api.DeployLogger;
77
import com.yahoo.config.model.ConfigModelContext;
8+
import com.yahoo.config.provision.AzName;
89
import com.yahoo.config.provision.Capacity;
910
import com.yahoo.config.provision.CloudAccount;
1011
import com.yahoo.config.provision.CloudResourceTags;
@@ -64,8 +65,10 @@ public class NodesSpecification {
6465
/** The cloud resource tags to apply to nodes in this spec */
6566
private final CloudResourceTags cloudResourceTags;
6667

67-
/* Whether the count attribute was present on the nodes element. */
68-
private final boolean hasCountAttribute;
68+
private final List<AzName> availabilityZones;
69+
70+
/** Whether the count attribute is present on the 'nodes' element. */
71+
private final boolean specifiesNodeCount;
6972

7073
private NodesSpecification(ClusterResources min,
7174
ClusterResources max,
@@ -75,7 +78,8 @@ private NodesSpecification(ClusterResources min,
7578
Optional<DockerImage> dockerImageRepo,
7679
Optional<CloudAccount> cloudAccount,
7780
CloudResourceTags cloudResourceTags,
78-
boolean hasCountAttribute) {
81+
List<AzName> availabilityZones,
82+
boolean specifiesNodeCount) {
7983
if (max.smallerThan(min))
8084
throw new IllegalArgumentException("Max resources must be larger or equal to min resources, but " +
8185
max + " is smaller than " + min);
@@ -101,13 +105,15 @@ private NodesSpecification(ClusterResources min,
101105
this.dockerImageRepo = dockerImageRepo;
102106
this.cloudAccount = cloudAccount;
103107
this.cloudResourceTags = cloudResourceTags;
104-
this.hasCountAttribute = hasCountAttribute;
108+
this.availabilityZones = List.copyOf(availabilityZones);
109+
this.specifiesNodeCount = specifiesNodeCount;
105110
}
106111

107112
static NodesSpecification create(boolean dedicated, boolean canFail, Version version,
108113
ModelElement nodesElement, Optional<DockerImage> dockerImageRepo,
109114
Optional<CloudAccount> cloudAccount,
110-
CloudResourceTags cloudResourceTags) {
115+
CloudResourceTags cloudResourceTags,
116+
List<AzName> availabilityZones) {
111117
var resolvedElement = resolveElement(nodesElement);
112118
var resourceConstraints = toResourceConstraints(resolvedElement);
113119
boolean hasCountAttribute = resolvedElement.stringAttribute("count") != null;
@@ -122,6 +128,7 @@ static NodesSpecification create(boolean dedicated, boolean canFail, Version ver
122128
dockerImageToUse(resolvedElement, dockerImageRepo),
123129
cloudAccount,
124130
cloudResourceTags,
131+
availabilityZones,
125132
hasCountAttribute);
126133
}
127134

@@ -140,7 +147,8 @@ private static ResourceConstraints toResourceConstraints(ModelElement nodesEleme
140147

141148
// Allow use of groups and group-size if count is not specified
142149
if (groupsAndGroupSizeButNoNodeCount(groups, groupSize, nodes))
143-
nodes = IntRange.of(groupSize.from().orElse(1) * groups.from().orElse(1), groupSize.to().orElse(1) * groups.to().orElse(1));
150+
nodes = IntRange.of(groupSize.from().orElse(1) * groups.from().orElse(1),
151+
groupSize.to().orElse(1) * groups.to().orElse(1));
144152

145153
var min = new ClusterResources(nodes.from().orElse(1), groups.from().orElse(defaultMinGroups), nodeResources(nodesElement).getFirst());
146154
var max = new ClusterResources(nodes.to().orElse(1), groups.to().orElse(defaultMaxGroups), nodeResources(nodesElement).getSecond());
@@ -170,7 +178,8 @@ public static NodesSpecification from(ModelElement nodesElement, ConfigModelCont
170178
nodesElement,
171179
context.getDeployState().getWantedDockerImageRepo(),
172180
context.getDeployState().getProperties().cloudAccount(),
173-
context.getDeployState().getProperties().cloudResourceTags());
181+
context.getDeployState().getProperties().cloudResourceTags(),
182+
context.availabilityZones());
174183
}
175184

176185
/**
@@ -189,7 +198,8 @@ public static Optional<NodesSpecification> optionalDedicatedFromParent(ModelElem
189198
nodesElement,
190199
context.getDeployState().getWantedDockerImageRepo(),
191200
context.getDeployState().getProperties().cloudAccount(),
192-
context.getDeployState().getProperties().cloudResourceTags()));
201+
context.getDeployState().getProperties().cloudResourceTags(),
202+
context.availabilityZones()));
193203
}
194204

195205
/**
@@ -207,6 +217,7 @@ public static NodesSpecification nonDedicated(int count, ConfigModelContext cont
207217
context.getDeployState().getWantedDockerImageRepo(),
208218
context.getDeployState().getProperties().cloudAccount(),
209219
context.getDeployState().getProperties().cloudResourceTags(),
220+
context.availabilityZones(),
210221
false);
211222
}
212223

@@ -223,6 +234,7 @@ public static NodesSpecification dedicated(int count, ConfigModelContext context
223234
context.getDeployState().getWantedDockerImageRepo(),
224235
context.getDeployState().getProperties().cloudAccount(),
225236
context.getDeployState().getProperties().cloudResourceTags(),
237+
context.availabilityZones(),
226238
false);
227239
}
228240

@@ -250,6 +262,7 @@ public static NodesSpecification requiredFromSharedParents(int count, NodeResour
250262
context.getDeployState().getWantedDockerImageRepo(),
251263
context.getDeployState().getProperties().cloudAccount(),
252264
context.getDeployState().getProperties().cloudResourceTags(),
265+
context.availabilityZones(),
253266
false);
254267
}
255268

@@ -271,8 +284,8 @@ public static NodesSpecification requiredFromSharedParents(int count, NodeResour
271284
public boolean isExclusive() { return exclusive; }
272285

273286
/** Returns whether the count attribute was present on the {@code <nodes>} element. */
274-
public boolean hasCountAttribute() {
275-
return hasCountAttribute;
287+
public boolean specifiesNodeCount() {
288+
return specifiesNodeCount;
276289
}
277290

278291
public Map<HostResource, ClusterMembership> provision(HostSystem hostSystem,
@@ -299,6 +312,7 @@ public Map<HostResource, ClusterMembership> provision(HostSystem hostSystem,
299312
.loadBalancerSettings(zoneEndpoint)
300313
.stateful(stateful)
301314
.sidecars(sidecars)
315+
.availabilityZones(availabilityZones)
302316
.build();
303317

304318
return hostSystem.allocateHosts(cluster, Capacity.from(min, max, groupSize, required, canFail, cloudAccount, cloudResourceTags, info), logger);

config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1277,11 +1277,14 @@ private List<ApplicationContainer> createNodesFromNodeCount(ApplicationContainer
12771277
}
12781278
}
12791279

1280-
private List<ApplicationContainer> createNodesFromNodeType(ApplicationContainerCluster cluster, Element nodesElement, ConfigModelContext context) {
1280+
private List<ApplicationContainer> createNodesFromNodeType(ApplicationContainerCluster cluster,
1281+
Element nodesElement,
1282+
ConfigModelContext context) {
12811283
NodeType type = NodeType.valueOf(nodesElement.getAttribute("type"));
12821284
ClusterSpec clusterSpec = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from(cluster.getName()))
12831285
.vespaVersion(context.getDeployState().getWantedNodeVespaVersion())
12841286
.dockerImageRepository(context.getDeployState().getWantedDockerImageRepo())
1287+
.availabilityZones(context.availabilityZones())
12851288
.build();
12861289
Map<HostResource, ClusterMembership> hosts =
12871290
cluster.getRoot().hostSystem().allocateHosts(clusterSpec, Capacity.fromRequiredNodeType(type), deployLogger);

config-model/src/main/java/com/yahoo/vespa/model/content/StorageGroup.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@ private void validateRedundancyAndGroups(Environment environment) {
247247
if (nodesElement == null) return;
248248
var nodesSpec = NodesSpecification.from(nodesElement, context);
249249

250-
// Allow dev deployment of self-hosted app (w/o count attribute): absent count => 1 node
251-
if (!nodesSpec.hasCountAttribute() && environment == Environment.dev) return;
250+
// Allow dev deployment of self-hosted apps which do not specify node count by defaulting to 1 node
251+
if (!nodesSpec.specifiesNodeCount() && environment == Environment.dev) return;
252252

253253
int minNodesPerGroup = (int) Math.ceil((double) nodesSpec.minResources().nodes() / nodesSpec.minResources().groups());
254254

config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecificationTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.w3c.dom.Document;
1111
import com.yahoo.component.Version;
1212

13+
import java.util.List;
1314
import java.util.Optional;
1415

1516
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -230,7 +231,7 @@ private NodesSpecification nodesSpecification(String nodesElement) {
230231
return NodesSpecification.create(false, false, Version.emptyVersion,
231232
new ModelElement(nodesXml.getDocumentElement()),
232233
Optional.empty(), Optional.empty(),
233-
CloudResourceTags.empty());
234+
CloudResourceTags.empty(), List.of());
234235

235236
}
236237

config-provisioning/src/main/java/com/yahoo/config/provision/AzName.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@
1414
public class AzName extends PatternedStringWrapper<AzName> {
1515

1616
private static final Pattern pattern = Pattern.compile("[a-z]([a-z0-9-]*[a-z0-9])*");
17+
private static final AzName defaultName = from("default");
1718

1819
private AzName(String az) {
1920
super(az, pattern, "availability zone name");
2021
}
2122

23+
/** The special name which means to use the zone's default az. */
24+
public static AzName defaultName() {
25+
return defaultName;
26+
}
27+
2228
public static AzName from(String az) {
2329
return new AzName(az);
2430
}

config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public final class Capacity {
2525
private final Optional<CloudAccount> cloudAccount;
2626
private final CloudResourceTags cloudResourceTags;
2727
private final ClusterInfo clusterInfo;
28-
private final List<AzName> availabilityZones;
2928

3029
private Capacity(ClusterResources min,
3130
ClusterResources max,
@@ -35,8 +34,7 @@ private Capacity(ClusterResources min,
3534
NodeType type,
3635
Optional<CloudAccount> cloudAccount,
3736
CloudResourceTags cloudResourceTags,
38-
ClusterInfo clusterInfo,
39-
List<AzName> availabilityZones) {
37+
ClusterInfo clusterInfo) {
4038
validate(min);
4139
validate(max);
4240
if (max.smallerThan(min))
@@ -53,7 +51,6 @@ private Capacity(ClusterResources min,
5351
this.cloudAccount = Objects.requireNonNull(cloudAccount);
5452
this.cloudResourceTags = requireNonNull(cloudResourceTags);
5553
this.clusterInfo = clusterInfo;
56-
this.availabilityZones = List.copyOf(Objects.requireNonNull(availabilityZones));
5754
}
5855

5956
private static void validate(ClusterResources resources) {
@@ -96,18 +93,12 @@ public CloudResourceTags cloudResourceTags() {
9693

9794
public ClusterInfo clusterInfo() { return clusterInfo; }
9895

99-
/**
100-
* Returns the availability zones this cluster should be provisioned across.
101-
* An empty list means the zone-default placement (historically: a single availability zone).
102-
*/
103-
public List<AzName> availabilityZones() { return availabilityZones; }
104-
10596
public Capacity withLimits(ClusterResources min, ClusterResources max) {
10697
return withLimits(min, max, IntRange.empty());
10798
}
10899

109100
public Capacity withLimits(ClusterResources min, ClusterResources max, IntRange groupSize) {
110-
return new Capacity(min, max, groupSize, required, canFail, type, cloudAccount, cloudResourceTags, clusterInfo, availabilityZones);
101+
return new Capacity(min, max, groupSize, required, canFail, type, cloudAccount, cloudResourceTags, clusterInfo);
111102
}
112103

113104
@Override
@@ -146,13 +137,7 @@ public static Capacity from(ClusterResources min, ClusterResources max, IntRange
146137
public static Capacity from(ClusterResources min, ClusterResources max, IntRange groupSize, boolean required, boolean canFail,
147138
Optional<CloudAccount> cloudAccount, CloudResourceTags cloudResourceTags,
148139
ClusterInfo clusterInfo) {
149-
return from(min, max, groupSize, required, canFail, cloudAccount, cloudResourceTags, clusterInfo, List.of());
150-
}
151-
152-
public static Capacity from(ClusterResources min, ClusterResources max, IntRange groupSize, boolean required, boolean canFail,
153-
Optional<CloudAccount> cloudAccount, CloudResourceTags cloudResourceTags,
154-
ClusterInfo clusterInfo, List<AzName> availabilityZones) {
155-
return new Capacity(min, max, groupSize, required, canFail, NodeType.tenant, cloudAccount, cloudResourceTags, clusterInfo, availabilityZones);
140+
return new Capacity(min, max, groupSize, required, canFail, NodeType.tenant, cloudAccount, cloudResourceTags, clusterInfo);
156141
}
157142

158143
/** Creates this from a node type */
@@ -161,7 +146,8 @@ public static Capacity fromRequiredNodeType(NodeType type) {
161146
}
162147

163148
private static Capacity from(ClusterResources resources, boolean required, boolean canFail, NodeType type, Duration hostTTL) {
164-
return new Capacity(resources, resources, IntRange.empty(), required, canFail, type, Optional.empty(), CloudResourceTags.empty(), new ClusterInfo.Builder().hostTTL(hostTTL).build(), List.of());
149+
return new Capacity(resources, resources, IntRange.empty(), required, canFail, type, Optional.empty(),
150+
CloudResourceTags.empty(), new ClusterInfo.Builder().hostTTL(hostTTL).build());
165151
}
166152

167153
}

0 commit comments

Comments
 (0)