aboutsummaryrefslogtreecommitdiffstats
path: root/config-provisioning/src/main/java/com/yahoo/config
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /config-provisioning/src/main/java/com/yahoo/config
Publish
Diffstat (limited to 'config-provisioning/src/main/java/com/yahoo/config')
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java174
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java58
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java69
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java124
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java165
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java25
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Deployment.java28
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Environment.java57
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/HostFilter.java105
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java83
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java54
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/OutOfCapacityException.java17
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java100
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionLogger.java15
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java53
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Quota.java23
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java55
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Rotation.java39
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java57
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Version.java173
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java115
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/package-info.java7
22 files changed, 1596 insertions, 0 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
new file mode 100644
index 00000000000..c326e6d25c1
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationId.java
@@ -0,0 +1,174 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import com.google.inject.Inject;
+import com.yahoo.cloud.config.ApplicationIdConfig;
+
+/**
+ * A complete, immutable identification of an application instance.
+ *
+ * @author lulf
+ * @author vegard
+ * @author bratseth
+ * @since 5.1
+ */
+public final class ApplicationId implements Comparable<ApplicationId> {
+
+ private final TenantName tenant;
+ private final ApplicationName application;
+ private final InstanceName instance;
+
+ private final String stringValue;
+ private final String serializedForm;
+
+ public static class Builder {
+
+ private TenantName tenant;
+ private ApplicationName application;
+ private InstanceName instance;
+
+ public Builder() {
+ this.tenant = TenantName.defaultName();
+ this.application = null;
+ this.instance = InstanceName.defaultName();
+ }
+
+ public Builder tenant(TenantName ten) { this.tenant = ten; return this; }
+ public Builder tenant(String ten) { return tenant(TenantName.from(ten)); }
+
+ public Builder applicationName(ApplicationName nam) { this.application = nam; return this; }
+ public Builder applicationName(String nam) { return applicationName(ApplicationName.from(nam)); }
+
+ public Builder instanceName(InstanceName ins) { this.instance = ins; return this; }
+ public Builder instanceName(String ins) { return instanceName(InstanceName.from(ins)); }
+
+ public ApplicationId build() {
+ if (application == null) {
+ throw new IllegalArgumentException("must set application name in builder");
+ }
+ return ApplicationId.from(tenant, application, instance);
+ }
+
+ }
+
+ public ApplicationId(ApplicationIdConfig config) {
+ this(TenantName.from(config.tenant()), ApplicationName.from(config.application()), InstanceName.from(config.instance()));
+ }
+
+ private ApplicationId(TenantName tenant, ApplicationName applicationName, InstanceName instanceName) {
+ this.tenant = tenant;
+ this.application = applicationName;
+ this.instance = instanceName;
+ this.stringValue = toStringValue();
+ this.serializedForm = toSerializedForm();
+ }
+
+ public static final TenantName HOSTED_VESPA_TENANT = TenantName.from("hosted-vespa");
+ // TODO: Remove references to routing application, or rename them to zone
+ // application, once everything (like Chef recipes) refers to the zone
+ // application name.
+ public static final ApplicationName ROUTING_APPLICATION = ApplicationName.from("routing");
+ public static final ApplicationName ZONE_APPLICATION = ApplicationName.from("zone");
+ public static final ApplicationId HOSTED_ZONE_APPLICATION_ID =
+ new ApplicationId.Builder()
+ .tenant(HOSTED_VESPA_TENANT)
+ .applicationName(ROUTING_APPLICATION)
+ .build();
+
+
+ public boolean isHostedVespaRoutingApplication() {
+ return HOSTED_VESPA_TENANT.equals(tenant) &&
+ (ROUTING_APPLICATION.equals(application) ||
+ ZONE_APPLICATION.equals(application));
+ }
+
+ public static ApplicationId from(TenantName tenant, ApplicationName application, InstanceName instanceName) {
+ return new ApplicationId(tenant, application, instanceName);
+ }
+
+ /** Creates an application id from a string on the form application:environment:region:instance */
+ public static ApplicationId fromSerializedForm(TenantName tenant, String idString) {
+ String[] parts = idString.split(":");
+ if (parts.length < 3) {
+ throw new IllegalArgumentException("Illegal id string '" + idString + "'. Id string must consist of at least three parts separated by ':'");
+ }
+
+ // Legacy id from 5.50 and backwards
+ if (parts.length < 4) {
+ return new Builder()
+ .tenant(parts[0])
+ .applicationName(parts[1])
+ .instanceName(parts[2])
+ .build();
+
+ } else {
+ return new Builder()
+ .applicationName(parts[0])
+ .instanceName(parts[3])
+ .tenant(tenant)
+ .build();
+ }
+ }
+
+ @Override
+ public int hashCode() { return stringValue.hashCode(); }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+
+ ApplicationId rhs = (ApplicationId) other;
+ return tenant.equals(rhs.tenant) &&
+ application.equals(rhs.application) &&
+ instance.equals(rhs.instance);
+ }
+
+ /** Returns a serialized form of the content of this: tenant:application:instance */
+ public String serializedForm() { return serializedForm; }
+
+ private String toStringValue() {
+ return "tenant '" + tenant + "', application '" + application + "', instance '" + instance + "'";
+ }
+
+ private String toSerializedForm() {
+ return tenant + ":" + application + ":" + instance;
+ }
+
+ @Override
+ public String toString() { return stringValue; }
+
+ public TenantName tenant() { return tenant; }
+ public ApplicationName application() { return application; }
+ public InstanceName instance() { return instance; }
+
+ @Override
+ public int compareTo(ApplicationId other) {
+ int diff;
+
+ diff = tenant.compareTo(other.tenant);
+ if (diff != 0) { return diff; }
+
+ diff = application.compareTo(other.application);
+ if (diff != 0) { return diff; }
+
+ diff = instance.compareTo(other.instance);
+ if (diff != 0) { return diff; }
+
+ return 0;
+ }
+
+ /** Returns an application id where all fields are "default" */
+ public static ApplicationId defaultId() {
+ return new ApplicationId(TenantName.defaultName(), ApplicationName.defaultName(), InstanceName.defaultName());
+ }
+
+ /** Returns an application id where all fields are "*" */
+ public static ApplicationId global() { // TODO: Sukk ... get rid of this
+ return new Builder().tenant("*")
+ .applicationName("*")
+ .instanceName("*")
+ .build();
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java
new file mode 100644
index 00000000000..a4dbb65e9ac
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ApplicationName.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+
+/**
+ * Represents an applications name, which may be any kind of string or default. This type is defined
+ * in order to provide a type safe API for defining environments.
+ *
+ * @author lulf
+ * @since 5.25
+ */
+public class ApplicationName implements Comparable<ApplicationName> {
+
+ private final String applicationName;
+
+ private ApplicationName(String applicationName) {
+ this.applicationName = applicationName;
+ }
+
+ @Override
+ public int hashCode() {
+ return applicationName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ApplicationName)) return false;
+ return Objects.equals(((ApplicationName) obj).applicationName, applicationName);
+ }
+
+ @Override
+ public String toString() {
+ return applicationName;
+ }
+
+ public static ApplicationName from(String name) {
+ return new ApplicationName(name);
+ }
+
+ public static ApplicationName defaultName() {
+ return new ApplicationName("default");
+ }
+
+ public boolean isDefault() {
+ return equals(ApplicationName.defaultName());
+ }
+
+ public String value() {
+ return applicationName;
+ }
+
+ @Override
+ public int compareTo(ApplicationName name) {
+ return this.applicationName.compareTo(name.applicationName);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
new file mode 100644
index 00000000000..5e02e1105ae
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Optional;
+
+/**
+ * A capacity request.
+ *
+ * @author lulf
+ * @author bratseth
+ */
+public final class Capacity {
+
+ private final int nodeCount;
+
+ private final boolean required;
+
+ private final Optional<String> flavor;
+
+ private Capacity(int nodeCount, boolean required, Optional<String> flavor) {
+ this.nodeCount = nodeCount;
+ this.flavor = flavor;
+ this.required = required;
+ }
+
+ /** Returns the number of nodes requested */
+ public int nodeCount() { return nodeCount; }
+
+ /** Returns whether the requested number of nodes must be met exactly for a request for this to succeed */
+ public boolean isRequired() { return required; }
+
+ /**
+ * The node flavor requested, or empty if no particular flavor is specified.
+ * This may be satisfied by the requested flavor or a suitable replacement
+ */
+ public Optional<String> flavor() { return flavor; }
+
+ @Override
+ public String toString() {
+ return nodeCount + " nodes " + ( flavor.isPresent() ? "of flavor " + flavor.get() : "(default flavor)" );
+ }
+
+ /** Creates this from a desired node count: The request may be satisfied with a smaller number of nodes. */
+ public static Capacity fromNodeCount(int capacity) {
+ return fromNodeCount(capacity, Optional.empty());
+ }
+ /** Creates this from a desired node count: The request may be satisfied with a smaller number of nodes. */
+ public static Capacity fromNodeCount(int nodeCount, String flavor) {
+ return fromNodeCount(nodeCount, Optional.of(flavor));
+ }
+ /** Creates this from a desired node count: The request may be satisfied with a smaller number of nodes. */
+ public static Capacity fromNodeCount(int nodeCount, Optional<String> flavor) {
+ return new Capacity(nodeCount, false, flavor);
+ }
+
+ /** Creates this from a required node count: Requests must fail unless the node count can be satisfied exactly */
+ public static Capacity fromRequiredNodeCount(int nodeCount) {
+ return fromRequiredNodeCount(nodeCount, Optional.empty());
+ }
+ /** Creates this from a required node count: Requests must fail unless the node count can be satisfied exactly */
+ public static Capacity fromRequiredNodeCount(int nodeCount, String flavor) {
+ return fromRequiredNodeCount(nodeCount, Optional.of(flavor));
+ }
+ /** Creates this from a required node count: Requests must fail unless the node count can be satisfied exactly */
+ public static Capacity fromRequiredNodeCount(int nodeCount, Optional<String> flavor) {
+ return new Capacity(nodeCount, true, flavor);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java
new file mode 100644
index 00000000000..1bf690749bb
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java
@@ -0,0 +1,124 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Optional;
+
+/**
+ * A node's membership in a cluster.
+ * This is a value object.
+ *
+ * @author bratseth
+ */
+public class ClusterMembership {
+
+ private ClusterSpec cluster; // final
+ private int index; // final
+ private boolean retired; // final
+ private String stringValue; // final
+
+ protected ClusterMembership() {}
+
+ private ClusterMembership(String stringValue, Optional<String> dockerImage) {
+ String restValue;
+ if (stringValue.endsWith("/retired")) {
+ retired = true;
+ restValue = stringValue.substring(0, stringValue.length() - "/retired".length());
+ }
+ else {
+ retired = false;
+ restValue = stringValue;
+ }
+
+ String[] components = restValue.split("/");
+
+ if ( components.length == 3)
+ initWithoutGroup(components, dockerImage);
+ else if (components.length == 4)
+ initWithGroup(components, dockerImage);
+ else
+ throw new RuntimeException("Could not parse '" + stringValue + "' to a cluster membership. " +
+ "Expected 'id/type.index[/group]'");
+
+ this.stringValue = toStringValue();
+ }
+
+ private ClusterMembership(ClusterSpec cluster, int index, boolean retired) {
+ this.cluster = cluster;
+ this.index = index;
+ this.retired = retired;
+ this.stringValue = toStringValue();
+ }
+
+ private void initWithoutGroup(String[] components, Optional<String> dockerImage) {
+ this.cluster = ClusterSpec.from(ClusterSpec.Type.valueOf(components[0]), ClusterSpec.Id.from(components[1]),
+ Optional.empty(), dockerImage);
+ this.index = Integer.parseInt(components[2]);
+ }
+
+ private void initWithGroup(String[] components, Optional<String> dockerImage) {
+ this.cluster = ClusterSpec.from(ClusterSpec.Type.valueOf(components[0]), ClusterSpec.Id.from(components[1]),
+ Optional.of(ClusterSpec.Group.from(components[2])), dockerImage);
+ this.index = Integer.parseInt(components[3]);
+ }
+
+ protected String toStringValue() {
+ return cluster.type().name() + "/" + cluster.id().value() +
+ ( cluster.group().isPresent() ? "/" + cluster.group().get().value() : "") + "/" + index +
+ ( retired ? "/retired" : "");
+ }
+
+ /** Returns the cluster this node is a member of */
+ public ClusterSpec cluster() { return cluster; }
+
+ /** Returns the index of this node within the cluster */
+ public int index() { return index; }
+
+ /** Returns whether the cluster should prepare for this node to be removed */
+ public boolean retired() { return retired; }
+
+ /** Returns a copy of this which is retired */
+ public ClusterMembership retire() {
+ return new ClusterMembership(cluster, index, true);
+ }
+
+ /** Returns a copy of this node which is not retired */
+ public ClusterMembership unretire() {
+ return new ClusterMembership(cluster, index, false);
+ }
+
+ public ClusterMembership changeCluster(ClusterSpec newCluster) {
+ return new ClusterMembership(newCluster, index, retired);
+ }
+
+ /**
+ * Returns all the information in this as a string which can be used to construct the same ClusterMembership
+ * instance using {@link #from}. This string is currently stored in ZooKeeper on running instances.
+ */
+ public String stringValue() { return stringValue; }
+
+ @Override
+ public int hashCode() { return stringValue().hashCode(); }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if ( ! (other instanceof ClusterMembership)) return false;
+ return ((ClusterMembership)other).stringValue().equals(stringValue());
+ }
+
+ @Override
+ public String toString() { return stringValue(); }
+
+ public static ClusterMembership from(String stringValue, Optional<String> dockerImage) {
+ return new ClusterMembership(stringValue, dockerImage);
+ }
+
+ public static ClusterMembership from(ClusterSpec cluster, int index) {
+ return new ClusterMembership(cluster, index, false);
+ }
+
+ public static ClusterMembership retiredFrom(ClusterSpec cluster, int index) {
+ return new ClusterMembership(cluster, index, true);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
new file mode 100644
index 00000000000..da6c6baa417
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
@@ -0,0 +1,165 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * A specification of a cluster - or group in a grouped cluster - to be run on a set of hosts.
+ * This is a value object.
+ *
+ * @author bratseth
+ */
+public final class ClusterSpec {
+
+ private final Type type;
+ private final Id id;
+ private final Optional<Group> groupId;
+ private final Optional<String> dockerImage;
+
+ private ClusterSpec(Type type, Id id, Optional<Group> groupId, Optional<String> dockerImage) {
+ this.type = type;
+ this.id = id;
+ this.groupId = groupId;
+ this.dockerImage = dockerImage;
+ }
+
+ /** Returns the cluster type */
+ public Type type() { return type; }
+
+ /** Returns the cluster id */
+ public Id id() { return id; }
+
+ public Optional<String> dockerImage() { return dockerImage; }
+
+ /** Returns the group within the cluster this specifies, or empty to specify the whole cluster */
+ public Optional<Group> group() { return groupId; }
+
+ public ClusterSpec changeGroup(Optional<Group> newGroup) { return new ClusterSpec(type, id, newGroup, dockerImage); }
+
+ public static ClusterSpec from(Type type, Id id) {
+ return new ClusterSpec(type, id, Optional.empty(), Optional.empty());
+ }
+
+ public static ClusterSpec from(Type type, Id id, Optional<Group> groupId) {
+ return new ClusterSpec(type, id, groupId, Optional.empty());
+ }
+
+ public static ClusterSpec from(Type type, Id id, Optional<Group> groupId, Optional<String> dockerImage) {
+ return new ClusterSpec(type, id, groupId, dockerImage);
+ }
+
+ @Override
+ public String toString() {
+ return type + " " + id + (groupId.isPresent() ? " " + groupId.get() : "");
+ }
+
+ @Override
+ public int hashCode() { return type.hashCode() + 17 * id.hashCode() + 31 * groupId.hashCode(); }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if ( ! (o instanceof ClusterSpec)) return false;
+ ClusterSpec other = (ClusterSpec)o;
+ if ( ! other.type.equals(this.type)) return false;
+ if ( ! other.id.equals(this.id)) return false;
+ if ( ! other.groupId.equals(this.groupId)) return false;
+ if ( ! other.dockerImage.equals(this.dockerImage)) return false;
+ return true;
+ }
+
+ /** Returns whether this is equal, disregarding the group value */
+ public boolean equalsIgnoringGroup(Object o) {
+ if (o == this) return true;
+ if ( ! (o instanceof ClusterSpec)) return false;
+ ClusterSpec other = (ClusterSpec)o;
+ if ( ! other.type.equals(this.type)) return false;
+ if ( ! other.id.equals(this.id)) return false;
+ if ( ! other.dockerImage.equals(this.dockerImage)) return false;
+ return true;
+ }
+
+ /** A cluster type */
+ public enum Type {
+
+ // These enum values are stored in ZooKeeper - do not change
+ admin,
+ container,
+ content;
+
+ public static Type from(String typeName) {
+ switch (typeName) {
+ case "admin" : return admin;
+ case "container" : return container;
+ case "content" : return content;
+ default: throw new IllegalArgumentException("Illegal cluster type '" + typeName + "'");
+ }
+ }
+
+ }
+
+ public static final class Id {
+
+ private final String id;
+
+ public Id(String id) {
+ Objects.requireNonNull(id, "Id cannot be null");
+ this.id = id;
+ }
+
+ public static Id from(String id) {
+ return new Id(id);
+ }
+
+ public String value() { return id; }
+
+ @Override
+ public String toString() { return "cluster '" + id + "'"; }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ return ((Id)o).id.equals(this.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ }
+
+ /** Identifier of a group within a cluster */
+ public static final class Group {
+
+ private final String id;
+
+ public Group(String id) {
+ Objects.requireNonNull(id, "Group id cannot be null");
+ this.id = id;
+ }
+
+ public static Group from(String id) {
+ return new Group(id);
+ }
+
+ public String value() { return id; }
+
+ @Override
+ public String toString() { return "group '" + id + "'"; }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ return ((Group)o).id.equals(this.id);
+ }
+
+ @Override
+ public int hashCode() { return id.hashCode(); }
+
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java
new file mode 100644
index 00000000000..dccce0a8d8d
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployer.java
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.time.Duration;
+import java.util.Optional;
+
+/**
+ * A deployer is used to deploy applications.
+ *
+ * @author bratseth
+ */
+public interface Deployer {
+
+ /**
+ * Creates a new deployment from the active application, if available.
+ *
+ * @param application the active application to be redeployed
+ * @param timeout the timeout to use for each individual deployment operation
+ * @return a new deployment from the local active, or empty if a local active application
+ * was not present for this id (meaning it either is not active or active on another
+ * node in the config server cluster)
+ */
+ public Optional<Deployment> deployFromLocalActive(ApplicationId application, Duration timeout);
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Deployment.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployment.java
new file mode 100644
index 00000000000..31718b235e7
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Deployment.java
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+/**
+ * A deployment of an application
+ *
+ * @author bratseth
+ */
+public interface Deployment {
+
+ /**
+ * Prepares activation of this deployment.
+ * This will do all validation and preparatory steps in the system such that a subsequent activation should
+ * be fast and error free.
+ */
+ void prepare();
+
+ /** Activates this deployment. This will prepare it if necessary. */
+ void activate();
+
+ /**
+ * Request a restart of services of this application on hosts matching the filter.
+ * This is sometimes needed after activation, but can also be requested without
+ * doing prepare and activate in the same session.
+ */
+ void restart(HostFilter filter);
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Environment.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Environment.java
new file mode 100644
index 00000000000..c7b2b84ca58
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Environment.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+/**
+ * Environments in hosted Vespa.
+ *
+ * @author bratseth
+ * @since 5.11
+ */
+public enum Environment {
+
+ /** The environment in which any external or internal applications serve actual requests */
+ prod,
+
+ /** Production-like environment which runs staging tests before an app is deployed to production */
+ staging,
+
+ /** Environment for running system tests before an app is deployed to staging */
+ test,
+
+ /** Environment used by individual developers to experiment */
+ dev,
+
+ /** Environment used to run performance and stability experiments */
+ perf;
+
+ /** Returns whether deployments to this environment are done manually */
+ public boolean isManuallyDeployed() { return this == dev || this == perf; }
+
+ /** Returns the prod environment. This is useful for non-hosted properties where we just need any consistent value */
+ public static Environment defaultEnvironment() { return prod; }
+
+ /** Returns the environment name from the string value returned by value() */
+ public static Environment from(String value) {
+ switch(value) {
+ case "prod" : return prod;
+ case "staging" : return staging;
+ case "test" : return test;
+ case "dev" : return dev;
+ case "perf" : return perf;
+ default : throw new IllegalStateException("'" + value + "' is not a valid environment identifier");
+ }
+ }
+
+ /** Returns a name of this which is used in external API's and stored in persistent stores */
+ public String value() {
+ switch(this) {
+ case prod : return "prod";
+ case staging : return "staging";
+ case test : return "test";
+ case dev : return "dev";
+ case perf : return "perf";
+ default : throw new IllegalStateException();
+ }
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/HostFilter.java b/config-provisioning/src/main/java/com/yahoo/config/provision/HostFilter.java
new file mode 100644
index 00000000000..8f3127f938f
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/HostFilter.java
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. 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.ImmutableSet;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A filter which matches a host depending on its properties.
+ *
+ * @author bratseth
+ */
+public class HostFilter {
+
+ // Filters. Empty to not filter on this property
+ private final Set<String> hostnames;
+ private final Set<String> flavors;
+ private final Set<ClusterSpec.Type> clusterTypes;
+ private final Set<ClusterSpec.Id> clusterIds;
+
+ private HostFilter(Set<String> hostnames,
+ Set<String> flavors,
+ Set<ClusterSpec.Type> clusterTypes,
+ Set<ClusterSpec.Id> clusterIds) {
+ Objects.requireNonNull(hostnames, "Hostnames cannot be null, use an empty list");
+ Objects.requireNonNull(flavors, "Flavors cannot be null, use an empty list");
+ Objects.requireNonNull(clusterTypes, "clusterTypes cannot be null, use an empty list");
+ Objects.requireNonNull(clusterIds, "clusterIds cannot be null, use an empty list");
+
+ this.hostnames = hostnames;
+ this.flavors = flavors;
+ this.clusterTypes = clusterTypes;
+ this.clusterIds = clusterIds;
+ }
+
+ /** Returns true if this filter matches the given host properties */
+ public boolean matches(String hostname, String flavor, Optional<ClusterMembership> membership) {
+ if ( ! hostnames.isEmpty() && ! hostnames.contains(hostname)) return false;
+ if ( ! flavors.isEmpty() && ! flavors.contains(flavor)) return false;
+ if ( ! clusterTypes.isEmpty() && ! (membership.isPresent() && clusterTypes.contains(membership.get().cluster().type()))) return false;
+ if ( ! clusterIds.isEmpty() && ! (membership.isPresent() && clusterIds.contains(membership.get().cluster().id()))) return false;
+ return true;
+ }
+
+ /** Returns a filter which matches all hosts */
+ public static HostFilter all() {
+ return new HostFilter(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
+ }
+
+ /** Returns a filter which matches a given host only */
+ public static HostFilter hostname(String hostname) {
+ return new HostFilter(Collections.singleton(hostname), Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
+ }
+
+ /** Returns a filter which matches a given flavor only */
+ public static HostFilter flavor(String flavor) {
+ return new HostFilter(Collections.emptySet(), Collections.singleton(flavor), Collections.emptySet(), Collections.emptySet());
+ }
+
+ /** Returns a filter which matches a given cluster type only */
+ public static HostFilter clusterType(ClusterSpec.Type clusterType) {
+ return new HostFilter(Collections.emptySet(), Collections.emptySet(), Collections.singleton(clusterType), Collections.emptySet());
+ }
+
+ /** Returns a filter which matches a given cluster id only */
+ public static HostFilter clusterId(ClusterSpec.Id clusterId) {
+ return new HostFilter(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.singleton(clusterId));
+ }
+
+ /** Returns a host filter from three optional conditions */
+ public static HostFilter from(Collection<String> hostNames,
+ Collection<String> flavors,
+ Collection<ClusterSpec.Type> clusterTypes,
+ Collection<ClusterSpec.Id> clusterIds) {
+ return new HostFilter(ImmutableSet.copyOf(hostNames),
+ ImmutableSet.copyOf(flavors),
+ ImmutableSet.copyOf(clusterTypes),
+ ImmutableSet.copyOf(clusterIds));
+ }
+
+ /** Returns a host filter from three comma and-or space separated string lists. The strings may be null or empty. */
+ public static HostFilter from(String hostNames, String flavors, String clusterTypes, String clusterIds) {
+ return new HostFilter(
+ split(hostNames),
+ split(flavors),
+ split(clusterTypes).stream().map(ClusterSpec.Type::from).collect(Collectors.toSet()),
+ split(clusterIds).stream().map(ClusterSpec.Id::from).collect(Collectors.toSet()));
+ }
+
+ /** Splits a string on space and comma */
+ public static Set<String> split(String s) {
+ if (s == null || s.isEmpty()) return Collections.emptySet();
+ ImmutableSet.Builder<String> b = new ImmutableSet.Builder<>();
+ for (String item : s.split("[\\s\\,]"))
+ if ( ! item.isEmpty())
+ b.add(item);
+ return b.build();
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java
new file mode 100644
index 00000000000..dd8ef7c4943
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/HostSpec.java
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. 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 java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A specification of a host and its role.
+ * The identity of a host is determined by its name.
+ *
+ * @author musum
+ */
+public class HostSpec implements Comparable<HostSpec> {
+
+ /** The name of this host */
+ private final String hostname;
+
+ /** Aliases of this host */
+ private final List<String> aliases;
+
+ /** The current membership role of this host in the cluster it belongs to */
+ private final Optional<ClusterMembership> membership;
+
+ public HostSpec(String hostname, Optional<ClusterMembership> membership) {
+ this(hostname, new ArrayList<>(), membership);
+ }
+
+ public HostSpec(String hostname, ClusterMembership membership) {
+ this(hostname, new ArrayList<>(), Optional.of(membership));
+ }
+
+ public HostSpec(String hostname, List<String> aliases) {
+ this(hostname, aliases, Optional.empty());
+ }
+
+ public HostSpec(String hostname, List<String> aliases, ClusterMembership membership) {
+ this(hostname, aliases, Optional.of(membership));
+ }
+
+ public HostSpec(String hostname, List<String> aliases, Optional<ClusterMembership> membership) {
+ if (hostname == null || hostname.isEmpty()) throw new IllegalArgumentException("Hostname must be specified");
+ this.hostname = hostname;
+ this.aliases = ImmutableList.copyOf(aliases);
+ this.membership = membership;
+ }
+
+ /** Returns the name identifying this host */
+ public String hostname() { return hostname; }
+
+ /** Returns the aliases of this host as an immutable list. This may be empty but never null. */
+ public List<String> aliases() { return aliases; }
+
+ /** Returns the membership of this host, or an empty value if not present */
+ public Optional<ClusterMembership> membership() { return membership; }
+
+ @Override
+ public String toString() {
+ return hostname +
+ (! aliases.isEmpty() ? " (aliases: " + aliases + ")" : "") +
+ (membership.isPresent() ? " (membership: " + membership.get() + ")" : " (no membership)");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( ! (o instanceof HostSpec)) return false;
+ HostSpec other = (HostSpec) o;
+ return this.hostname().equals(other.hostname());
+ }
+
+ @Override
+ public int hashCode() {
+ return hostname.hashCode();
+ }
+
+ @Override
+ public int compareTo(HostSpec other) {
+ return hostname.compareTo(other.hostname);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java
new file mode 100644
index 00000000000..d8696098b4b
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/InstanceName.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+
+/**
+ * Represents an applications instance name, which may be any kind of string or default. This type is defined
+ * in order to provide a type safe API for defining environments.
+ *
+ * @author lulf
+ * @since 5.25
+ */
+public class InstanceName implements Comparable<InstanceName> {
+ private final String instanceName;
+
+ private InstanceName(String instanceName) {
+ this.instanceName = instanceName;
+ }
+
+ @Override
+ public int hashCode() {
+ return instanceName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof InstanceName)) return false;
+ return Objects.equals(((InstanceName) obj).instanceName, instanceName);
+ }
+
+ @Override
+ public String toString() {
+ return instanceName;
+ }
+
+ public static InstanceName from(String name) {
+ return new InstanceName(name);
+ }
+
+ public static InstanceName defaultName() {
+ return new InstanceName("default");
+ }
+
+ public boolean isDefault() {
+ return equals(InstanceName.defaultName());
+ }
+
+ public String value() { return instanceName; }
+
+ @Override
+ public int compareTo(InstanceName instance) {
+ return instanceName.compareTo(instance.instanceName);
+ }
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/OutOfCapacityException.java b/config-provisioning/src/main/java/com/yahoo/config/provision/OutOfCapacityException.java
new file mode 100644
index 00000000000..4f37e54dca8
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/OutOfCapacityException.java
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+/**
+ *
+ * Exception thrown when we are unable to fulfill the request due to
+ * having too few nodes (of the specified flavor)
+ * @author musum
+ *
+ */
+public class OutOfCapacityException extends RuntimeException {
+
+ public OutOfCapacityException(String message) {
+ super(message);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java
new file mode 100644
index 00000000000..988819aae2d
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionInfo.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+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.*;
+import java.util.logging.Logger;
+
+/**
+ * Information about provisioned hosts, and (de)serialization (from)to JSON.
+ *
+ * @author lulf
+ * @since 5.12
+ */
+public class ProvisionInfo {
+
+ private static final String mappingKey = "mapping";
+ private static final String hostSpecKey = "hostSpec";
+ private static final String hostSpecHostName = "hostName";
+ private static final String hostSpecMembership = "membership";
+ private static final String dockerImage = "dockerImage";
+
+ private final Set<HostSpec> hosts = new LinkedHashSet<>();
+
+ private ProvisionInfo(Set<HostSpec> hosts) {
+ this.hosts.addAll(hosts);
+ }
+
+ public static ProvisionInfo withHosts(Set<HostSpec> hosts) {
+ return new ProvisionInfo(hosts);
+ }
+
+ private void toSlime(Cursor cursor) {
+ Cursor array = cursor.setArray(mappingKey);
+ for (HostSpec host : hosts) {
+ Cursor object = array.addObject();
+ serializeHostSpec(object.setObject(hostSpecKey), host);
+ }
+ }
+
+ private void serializeHostSpec(Cursor cursor, HostSpec host) {
+ cursor.setString(hostSpecHostName, host.hostname());
+ if (host.membership().isPresent()) {
+ cursor.setString(hostSpecMembership, host.membership().get().stringValue());
+ if (host.membership().get().cluster().dockerImage().isPresent())
+ cursor.setString(dockerImage, host.membership().get().cluster().dockerImage().get());
+ }
+ }
+
+ public Set<HostSpec> getHosts() {
+ return Collections.unmodifiableSet(hosts);
+ }
+
+ private static ProvisionInfo fromSlime(Inspector inspector) {
+ Inspector array = inspector.field(mappingKey);
+ final Set<HostSpec> hosts = new LinkedHashSet<>();
+ array.traverse(new ArrayTraverser() {
+ @Override
+ public void entry(int i, Inspector inspector) {
+ hosts.add(createHostSpec(inspector.field(hostSpecKey)));
+ }
+ });
+ return new ProvisionInfo(hosts);
+ }
+
+ private static HostSpec createHostSpec(Inspector object) {
+ Optional<ClusterMembership> membership =
+ object.field(hostSpecMembership).valid() ? Optional.of(readMembership(object)) : Optional.empty();
+ HostSpec h = new HostSpec(object.field(hostSpecHostName).asString(), Collections.<String>emptyList(), membership);
+ return h;
+ }
+
+ private static ClusterMembership readMembership(Inspector object) {
+ return ClusterMembership.from(object.field(hostSpecMembership).asString(),
+ object.field(dockerImage).valid() ? Optional.of(object.field(dockerImage).asString()) : Optional.empty());
+ }
+
+ public byte[] toJson() throws IOException {
+ Slime slime = new Slime();
+ toSlime(slime.setObject());
+ return SlimeUtils.toJsonBytes(slime);
+ }
+
+ public static ProvisionInfo fromJson(byte[] json) {
+ return fromSlime(SlimeUtils.jsonToSlime(json).get());
+ }
+
+ public ProvisionInfo merge(ProvisionInfo provisionInfo) {
+ Set<HostSpec> mergedSet = new LinkedHashSet<>();
+ mergedSet.addAll(this.hosts);
+ mergedSet.addAll(provisionInfo.getHosts());
+ return ProvisionInfo.withHosts(mergedSet);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionLogger.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionLogger.java
new file mode 100644
index 00000000000..1246daaacf9
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ProvisionLogger.java
@@ -0,0 +1,15 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.logging.Level;
+
+/**
+ * Allows messages to be logged during provision which will be directed back to the party initiating the request.
+ *
+ * @author bratseth
+ */
+public interface ProvisionLogger {
+
+ void log(Level level, String message);
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java
new file mode 100644
index 00000000000..2e84ac555f0
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import com.yahoo.transaction.NestedTransaction;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Interface used by the config system to acquire hosts.
+ *
+ * @author lulf
+ * @since 5.11
+ */
+public interface Provisioner {
+
+ /**
+ * Prepares allocation of a set of hosts with a given type, common id and the amount.
+ *
+ * @param applicationId the application requesting hosts
+ * @param cluster the specification of the cluster to allocate nodes for
+ * @param capacity the capacity requested
+ * @param groups the number of node groups to divide the requested capacity into
+ * @param logger a logger which receives messages which are returned to the requestor
+ * @return the specification of the hosts allocated
+ */
+ List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger);
+
+ /**
+ * Activates the allocation of nodes to this application captured in the hosts argument.
+ *
+ * @param transaction Transaction with operations to commit together with any operations done within the provisioner.
+ * @param application The {@link ApplicationId} that was activated.
+ * @param hosts a set of {@link HostSpec}.
+ */
+ public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts);
+
+ /**
+ * Notifies provisioner that an application has been removed.
+ *
+ * @param application The {@link ApplicationId} that was removed.
+ */
+ public void removed(ApplicationId application);
+
+ /**
+ * Requests a restart of the services of the given application
+ *
+ * @param application the application to restart
+ * @param filter a filter which matches the application nodes to restart
+ */
+ public void restart(ApplicationId application, HostFilter filter);
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Quota.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Quota.java
new file mode 100644
index 00000000000..d3a5e6b0b96
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Quota.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+/**
+ * @author musum
+ */
+public class Quota {
+
+ private final int numberOfHosts;
+
+ public Quota() {
+ this(Integer.MAX_VALUE);
+ }
+
+ public Quota(int numberOfHosts) {
+ this.numberOfHosts = numberOfHosts;
+ }
+
+ public int getNumberOfHosts() {
+ return numberOfHosts;
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java
new file mode 100644
index 00000000000..52721e7100e
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/RegionName.java
@@ -0,0 +1,55 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+
+/**
+ * Represents an applications region, which may be any kind of string or default. This type is defined
+ * in order to provide a type safe API for defining regions.
+ *
+ * @author lulf
+ * @since 5.11
+ */
+public class RegionName implements Comparable<RegionName> {
+
+ private final String region;
+
+ private RegionName(String region) {
+ this.region = region;
+ }
+
+ @Override
+ public int hashCode() {
+ return region.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RegionName)) return false;
+ return Objects.equals(((RegionName) obj).region, region);
+ }
+
+ @Override
+ public String toString() {
+ return region;
+ }
+
+ public static RegionName from(String region) {
+ return new RegionName(region);
+ }
+
+ public static RegionName defaultName() {
+ return new RegionName("default");
+ }
+
+ public boolean isDefault() {
+ return equals(RegionName.defaultName());
+ }
+
+ public String value() { return region; }
+
+ @Override
+ public int compareTo(RegionName region) {
+ return this.region.compareTo(region.region);
+ }
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Rotation.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Rotation.java
new file mode 100644
index 00000000000..e565ca41967
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Rotation.java
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+
+import java.util.Objects;
+
+/**
+ * A Brooklyn rotation, e.g. rotation-042.vespa.a02.yahoodns.net.
+ */
+public class Rotation {
+
+ private final String id;
+
+ public Rotation(String id) {
+ this.id = Objects.requireNonNull(id, "Rotation id cannot be null");
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Rotation)) {
+ return false;
+ }
+ final Rotation that = (Rotation) o;
+ return (this.id.equals(that.id));
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java
new file mode 100644
index 00000000000..81956919da5
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/TenantName.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+
+/**
+ * Represents a tenant in the provision API.
+ *
+ * @author lulf
+ * @since 5.12
+ */
+public class TenantName implements Comparable<TenantName> {
+
+ private final String name;
+
+ private TenantName(String name) {
+ this.name = name;
+ }
+
+ public String value() { return name; }
+
+ /**
+ * Create a {@link TenantName} with a given name.
+ *
+ * @param name Name of tenant.
+ * @return instance of {@link TenantName}.
+ */
+ public static TenantName from(String name) {
+ return new TenantName(name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TenantName)) return false;
+ return Objects.equals(((TenantName)obj).value(), value());
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ public static TenantName defaultName() {
+ return from("default");
+ }
+
+ @Override
+ public int compareTo(TenantName that) {
+ return this.name.compareTo(that.name);
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Version.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Version.java
new file mode 100644
index 00000000000..6dcb5fe207e
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Version.java
@@ -0,0 +1,173 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+/**
+ * The {@link Version} class is used in providing versioned config for applications.
+ *
+ * A {@link Version} object has three components:
+ *
+ * * Major version. A non-negative integer.
+ * * Minor version. A non-negative integer.
+ * * Micro version. A non-negative integer.
+ *
+ * @author Vegard Sjonfjell
+ * @since 5.39
+ * Loosely based on component/Version.java
+ * {@link Version} objects are immutable.
+ */
+public final class Version implements Comparable<Version> {
+
+ private final int major;
+ private final int minor;
+ private final int micro;
+ private final String stringValue;
+
+ /**
+ * @see #fromIntValues
+ */
+ private Version(int major, int minor, int micro) {
+ this.major = major;
+ this.minor = minor;
+ this.micro = micro;
+ stringValue = toSerializedForm();
+ verify();
+ }
+
+ /**
+ * @see #fromString
+ */
+ private Version(String versionString) {
+ try {
+ String[] components = versionString.split("\\.", 3);
+ assert (components.length == 3);
+ major = Integer.parseInt(components[0]);
+ minor = Integer.parseInt(components[1]);
+ micro = Integer.parseInt(components[2]);
+ stringValue = toSerializedForm();
+ verify();
+ } catch (AssertionError | ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
+ throw new IllegalArgumentException(String.format("Invalid version specification: \"%s\": %s", versionString, e.getMessage()));
+ }
+ }
+
+ /**
+ * Verifies that the numerical components in a version are legal.
+ * Must be called on construction after the component values are set
+ *
+ * @throws IllegalArgumentException if one of the numerical components are negative.
+ */
+ private void verify() {
+ if (major < 0)
+ throw new IllegalArgumentException("Negative major value");
+ if (minor < 0)
+ throw new IllegalArgumentException("Negative minor value");
+ if (micro < 0)
+ throw new IllegalArgumentException("Negative micro value");
+ }
+
+ public String toSerializedForm() {
+ return String.format("%d.%d.%d", major, minor, micro);
+ }
+
+ /**
+ * Creates a {@link Version} object from the specified components.
+ *
+ * @param major major component of the version identifier.
+ * @param minor minor component of the version identifier.
+ * @param micro micro component of the version identifier.
+ * @throws IllegalArgumentException if one of the numerical components are negative.
+ * @return {@link Version} identifier object constructed from integer components.
+ */
+ public static Version fromIntValues(int major, int minor, int micro) {
+ return new Version(major, minor, micro);
+ }
+
+ /**
+ * Creates a version object from the specified string. Version strings are in the format major.minor.micro
+ *
+ * @param versionString String representation of the version.
+ * @throws IllegalArgumentException If version string is improperly formatted.
+ * @return {@link Version} object constructed from string representation.
+ */
+ public static Version fromString(String versionString) {
+ return new Version(versionString);
+ }
+
+ /**
+ * Returns a string representation of this version identifier, encoded as major.minor.micro
+ */
+ public String toString() { return stringValue; }
+
+ /**
+ * Returns major version component
+ */
+ public int getMajor() { return major; }
+
+ /**
+ * Returns minor version component
+ */
+ public int getMinor() { return minor; }
+
+ /**
+ * Returns micro version component
+ */
+ public int getMicro() { return micro; }
+
+ @Override
+ public int hashCode() { return stringValue.hashCode(); }
+
+ /**
+ * Performs an equality test between this {@link Version} object and another.
+ *
+ * A version is considered to be equal to another version if the
+ * major, minor and micro components are equal.
+ *
+ * @param object The {@link Version} object to be compared to this version.
+ * @return <code>true</code> if object is a
+ * {@link Version} and is equal to this object;
+ * <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == null || object.getClass() != this.getClass()) {
+ return false;
+ }
+
+ Version other = (Version)object;
+ return this.major == other.major && this.minor == other.minor && this.micro == other.micro;
+ }
+
+ /**
+ * Compares this {@link Version} object to another.
+ *
+ * A version is considered to be less than another version if its
+ * major component is less than the other version's major component, or the
+ * major components are equal and its minor component is less than the other
+ * version's minor component, or the major and minor components are equal
+ * and its micro component is less than the other version's micro component.
+ *
+ * A version is considered to be equal to another version if the
+ * major, minor and micro components are equal.
+ *
+ * @param other the {@link Version} object to be compared to this version.
+ * @return A negative integer, zero, or a positive integer if this object is
+ * less than, equal to, or greater than the specified {@link Version} object.
+ * @throws ClassCastException if the specified object is not a {@link Version}.
+ */
+ @Override
+ public int compareTo(Version other) {
+ if (this == other) return 0;
+
+ int comparison = Integer.compare(getMajor(), other.getMajor());
+ if (comparison != 0) {
+ return comparison;
+ }
+
+ comparison = Integer.compare(getMinor(), other.getMinor());
+ if (comparison != 0) {
+ return comparison;
+ }
+
+ return Integer.compare(getMicro(), other.getMicro());
+ }
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java
new file mode 100644
index 00000000000..1b0962a8b26
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Zone.java
@@ -0,0 +1,115 @@
+// Copyright 2016 Yahoo Inc. 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.base.Strings;
+import com.google.inject.Inject;
+import com.yahoo.cloud.config.ConfigserverConfig;
+
+import java.util.Optional;
+
+/**
+ * The zone (environment + region) of this runtime.
+ * An injected instance of this will return the correct current environment and region.
+ * Components can use this to obtain information about which zone they are running in.
+ *
+ * @author bratseth
+ */
+public class Zone {
+
+ private final Environment environment;
+ private final RegionName region;
+ private final FlavorDefaults flavorDefaults;
+
+ @Inject
+ public Zone(ConfigserverConfig configserverConfig) {
+ this(Environment.from(configserverConfig.environment()), RegionName.from(configserverConfig.region()),
+ new FlavorDefaults(configserverConfig));
+ }
+
+ /** Create from environment and region */
+ public Zone(Environment environment, RegionName region) { this(environment, region, "default"); }
+
+ /** Create from environment and region. Useful for testing. */
+ public Zone(Environment environment, RegionName region, String defaultFlavor) {
+ this(environment, region, new FlavorDefaults(defaultFlavor));
+ }
+
+ private Zone(Environment environment, RegionName region, FlavorDefaults flavorDefaults) {
+ this.environment = environment;
+ this.region = region;
+ this.flavorDefaults = flavorDefaults;
+ }
+
+ /** Returns the current environment */
+ public Environment environment() { return environment; }
+
+ /** Returns the current region */
+ public RegionName region() { return region; }
+
+ /** Returns the default hardware flavor to assign in this zone */
+ public String defaultFlavor(ClusterSpec.Type clusterType) { return flavorDefaults.flavor(clusterType); }
+
+ /** Do not use */
+ public static Zone defaultZone() {
+ return new Zone(Environment.defaultEnvironment(), RegionName.defaultName());
+ }
+
+ @Override
+ public String toString() {
+ return "zone " + environment + "." + region;
+ }
+
+ private static class FlavorDefaults {
+
+ /** The default default flavor */
+ private final String defaultFlavor;
+
+ /** The default flavor for each cluster type, or empty to use defaultFlavor */
+ private final Optional<String> adminFlavor;
+ private final Optional<String> containerFlavor;
+ private final Optional<String> contentFlavor;
+
+ /** Creates this with a default flavor and all cluster type flavors empty */
+ public FlavorDefaults(String defaultFlavor) {
+ this(defaultFlavor, Optional.empty(), Optional.empty(), Optional.empty());
+ }
+
+ /** Creates this with a default flavor and all cluster type flavors empty */
+ public FlavorDefaults(String defaultFlavor,
+ Optional<String> adminFlavor, Optional<String> containerFlavor, Optional<String> contentFlavor) {
+ this.defaultFlavor = defaultFlavor;
+ this.adminFlavor = adminFlavor;
+ this.containerFlavor = containerFlavor;
+ this.contentFlavor = contentFlavor;
+ }
+
+ public FlavorDefaults(ConfigserverConfig config) {
+ this(config.defaultFlavor(),
+ emptyIfDefault(config.defaultAdminFlavor()),
+ emptyIfDefault(config.defaultContainerFlavor()),
+ emptyIfDefault(config.defaultContentFlavor()));
+ }
+
+ /** Map "default" to empty - this config cannot have missing values due to the need for supporting non-hosted */
+ private static Optional<String> emptyIfDefault(String value) {
+ if (Strings.isNullOrEmpty(value)) return Optional.empty();
+ if (value.equals("default")) return Optional.empty();
+ return Optional.of(value);
+ }
+
+ /**
+ * Returns the flavor default for a given cluster type.
+ * This may be "default" - which is an invalid value - but never null.
+ */
+ public String flavor(ClusterSpec.Type clusterType) {
+ switch (clusterType) {
+ case admin: return adminFlavor.orElse(defaultFlavor);
+ case container: return containerFlavor.orElse(defaultFlavor);
+ case content: return contentFlavor.orElse(defaultFlavor);
+ default: return defaultFlavor; // future cluster types
+ }
+ }
+
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/package-info.java b/config-provisioning/src/main/java/com/yahoo/config/provision/package-info.java
new file mode 100644
index 00000000000..fbf362aa292
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package com.yahoo.config.provision;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;