summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@yahooinc.com>2022-06-14 15:36:45 +0200
committerØyvind Grønnesby <oyving@yahooinc.com>2022-06-14 15:36:45 +0200
commit82cc6b485145067f281a2cf68a0e823b8cde6e13 (patch)
treec37f10c8fade516a37dc721ea01b36bfcac9661d /controller-server
parente5486a7ad29d64215c593b0062d9f622be9ce394 (diff)
parentaaafe503173878c081aadaa91665cef162726a24 (diff)
Merge remote-tracking branch 'origin/master' into ogronnesby/persist-scaling-events
Conflicts: controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/pom.xml10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java13
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java28
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java62
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java18
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java49
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java32
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java48
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTracker.java77
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java13
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java33
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java258
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/DeploymentStatistics.java46
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java69
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersion.java35
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionTarget.java53
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersionTarget.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java24
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java23
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java23
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java63
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java118
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTrackerTest.java145
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java19
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainerTest.java53
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java87
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java54
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java85
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg87
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/running-test.svg93
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java18
-rw-r--r--controller-server/src/test/resources/application-packages/changed-deployment-xml.zipbin760 -> 1476 bytes
-rw-r--r--controller-server/src/test/resources/application-packages/changed-services-xml.zipbin719 -> 1446 bytes
-rw-r--r--controller-server/src/test/resources/application-packages/include-absolute.zipbin740 -> 1488 bytes
-rw-r--r--controller-server/src/test/resources/application-packages/include-parent.zipbin741 -> 1490 bytes
-rw-r--r--controller-server/src/test/resources/application-packages/original.zipbin720 -> 1448 bytes
-rw-r--r--controller-server/src/test/resources/application-packages/similar-deployment-xml.zipbin769 -> 1494 bytes
-rw-r--r--controller-server/src/test/resources/testConfig.json3
91 files changed, 1180 insertions, 1098 deletions
diff --git a/controller-server/pom.xml b/controller-server/pom.xml
index 5cf53929a98..773d63202b6 100644
--- a/controller-server/pom.xml
+++ b/controller-server/pom.xml
@@ -8,12 +8,12 @@
<parent>
<groupId>com.yahoo.vespa</groupId>
<artifactId>parent</artifactId>
- <version>7-SNAPSHOT</version>
+ <version>8-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>controller-server</artifactId>
<packaging>container-plugin</packaging>
- <version>7-SNAPSHOT</version>
+ <version>8-SNAPSHOT</version>
<dependencies>
@@ -102,12 +102,6 @@
</dependency>
<dependency>
- <groupId>javax.ws.rs</groupId>
- <artifactId>javax.ws.rs-api</artifactId>
- <scope>provided</scope>
- </dependency>
-
- <dependency>
<groupId>com.yahoo.vespa</groupId>
<artifactId>flags</artifactId>
<version>${project.version}</version>
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
index 1c4f0994f8c..af56666c6eb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
@@ -251,6 +251,19 @@ public class Controller extends AbstractComponent {
}
}
+ /** Clear the target OS version for given cloud in this system */
+ public void cancelOsUpgradeIn(CloudName cloudName) {
+ try (Mutex lock = curator.lockOsVersions()) {
+ Map<CloudName, OsVersionTarget> targets = curator.readOsVersionTargets().stream()
+ .collect(Collectors.toMap(t -> t.osVersion().cloud(),
+ Function.identity()));
+ if (targets.remove(cloudName) == null) {
+ throw new IllegalArgumentException("Cloud '" + cloudName.value() + " has no OS upgrade target");
+ }
+ curator.writeOsVersionTargets(new TreeSet<>(targets.values()));
+ }
+ }
+
/** Returns the current OS version status */
public OsVersionStatus osVersionStatus() {
return curator.readOsVersionStatus();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
index 7a0e60aacb4..4f58e87035b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
@@ -8,11 +8,11 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.security.KeyUtils;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.athenz.api.AthenzDomain;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -128,26 +128,26 @@ public abstract class LockedTenant {
private final BiMap<PublicKey, Principal> developerKeys;
private final TenantInfo info;
private final List<TenantSecretStore> tenantSecretStores;
- private final Optional<String> archiveAccessRole;
+ private final ArchiveAccess archiveAccess;
private Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
- List<TenantSecretStore> tenantSecretStores, Optional<String> archiveAccessRole) {
+ List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess) {
super(name, createdAt, lastLoginInfo);
this.developerKeys = ImmutableBiMap.copyOf(developerKeys);
this.creator = creator;
this.info = info;
this.tenantSecretStores = tenantSecretStores;
- this.archiveAccessRole = archiveAccessRole;
+ this.archiveAccess = archiveAccess;
}
private Cloud(CloudTenant tenant) {
- this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccessRole());
+ this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccess());
}
@Override
public CloudTenant get() {
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
public Cloud withDeveloperKey(PublicKey key, Principal principal) {
@@ -155,38 +155,38 @@ public abstract class LockedTenant {
if (keys.containsKey(key))
throw new IllegalArgumentException("Key " + KeyUtils.toPem(key) + " is already owned by " + keys.get(key));
keys.put(key, principal);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess);
}
public Cloud withoutDeveloperKey(PublicKey key) {
BiMap<PublicKey, Principal> keys = HashBiMap.create(developerKeys);
keys.remove(key);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess);
}
public Cloud withInfo(TenantInfo newInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccess);
}
@Override
public LockedTenant with(LastLoginInfo lastLoginInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
public Cloud withSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.add(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess);
}
public Cloud withoutSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.remove(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess);
}
- public Cloud withArchiveAccessRole(Optional<String> role) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, role);
+ public Cloud withArchiveAccess(ArchiveAccess archiveAccess) {
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
index c0829adc7ae..790f54b5e8c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java
@@ -213,7 +213,7 @@ public class RoutingController {
.map(com.yahoo.config.application.api.Endpoint.Target::region)
.distinct()
.map(region -> new DeploymentId(deployment.applicationId(), ZoneId.from(Environment.prod, region)))
- .collect(Collectors.toUnmodifiableList());
+ .toList();
TenantAndApplicationId application = TenantAndApplicationId.from(deployment.applicationId());
for (var targetDeployment : deploymentTargets) {
builders.add(Endpoint.of(application).targetApplication(EndpointId.defaultId(), targetDeployment));
@@ -413,19 +413,19 @@ public class RoutingController {
/** Create a common name based on a hash of given application. This must be less than 64 characters long. */
private static String commonNameHashOf(ApplicationId application, SystemName system) {
+ @SuppressWarnings("deprecation") // for Hashing.sha1()
HashCode sha1 = Hashing.sha1().hashString(application.serializedForm(), StandardCharsets.UTF_8);
String base32 = BaseEncoding.base32().omitPadding().lowerCase().encode(sha1.asBytes());
return 'v' + base32 + Endpoint.internalDnsSuffix(system);
}
private static String asString(Endpoint.Scope scope) {
- switch (scope) {
- case application: return "application";
- case global: return "global";
- case weighted: return "weighted";
- case zone: return "zone";
- }
- throw new IllegalArgumentException("Unknown scope " + scope);
+ return switch (scope) {
+ case application -> "application";
+ case global -> "global";
+ case weighted -> "weighted";
+ case zone -> "zone";
+ };
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
index 8de72893a7c..88366466289 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
@@ -103,7 +103,7 @@ public class Endpoint {
/** Returns the deployments(s) to which this routes traffic */
public List<DeploymentId> deployments() {
- return targets.stream().map(Target::deployment).collect(Collectors.toUnmodifiableList());
+ return targets.stream().map(Target::deployment).toList();
}
/** Returns the scope of this */
@@ -202,20 +202,19 @@ public class Endpoint {
private static String scopeSymbol(Scope scope, SystemName system) {
if (system.isPublic()) {
- switch (scope) {
- case zone: return "z";
- case weighted: return "w";
- case global: return "g";
- case application: return "r";
- }
- }
- switch (scope) {
- case zone: return "";
- case weighted: return "w";
- case global: return "global";
- case application: return "r";
+ return switch (scope) {
+ case zone -> "z";
+ case weighted -> "w";
+ case global -> "g";
+ case application -> "r";
+ };
}
- throw new IllegalArgumentException("No scope symbol defined for " + scope + " in " + system);
+ return switch (scope) {
+ case zone -> "";
+ case weighted -> "w";
+ case global -> "global";
+ case application -> "r";
+ };
}
private static String instancePart(Optional<InstanceName> instance, String separator) {
@@ -233,17 +232,19 @@ public class Endpoint {
/** Returns the DNS suffix used for endpoints in given system */
private static String dnsSuffix(SystemName system, boolean legacy) {
switch (system) {
- case cd:
- case main:
+ case cd, main -> {
if (legacy) return YAHOO_DNS_SUFFIX;
return OATH_DNS_SUFFIX;
- case Public:
+ }
+ case Public -> {
if (legacy) throw new IllegalArgumentException("No legacy DNS suffix declared for system " + system);
return PUBLIC_DNS_SUFFIX;
- case PublicCd:
+ }
+ case PublicCd -> {
if (legacy) throw new IllegalArgumentException("No legacy DNS suffix declared for system " + system);
return PUBLIC_CD_DNS_SUFFIX;
- default: throw new IllegalArgumentException("No DNS suffix declared for system " + system);
+ }
+ default -> throw new IllegalArgumentException("No DNS suffix declared for system " + system);
}
}
@@ -284,11 +285,12 @@ public class Endpoint {
/** Returns the given region without availability zone */
private static RegionName effectiveRegion(RegionName region) {
- if (region.value().isEmpty()) return region;
+ if (region.value().length() < 2) return region;
String value = region.value();
char lastChar = value.charAt(value.length() - 1);
if (lastChar >= 'a' && lastChar <= 'z') { // Remove availability zone
- value = value.substring(0, value.length() - 1);
+ int skip = value.charAt(value.length() - 2) == '-' ? 2 : 1;
+ value = value.substring(0, value.length() - skip);
}
return RegionName.from(value);
}
@@ -422,20 +424,6 @@ public class Endpoint {
return new EndpointBuilder(application, Optional.empty());
}
- /** Create an endpoint for given system application */
- public static Endpoint of(SystemApplication systemApplication, ZoneId zone, URI url) {
- if (!systemApplication.hasEndpoint()) throw new IllegalArgumentException(systemApplication + " has no endpoint");
- RoutingMethod routingMethod = RoutingMethod.exclusive;
- Port port = url.getPort() == -1 ? Port.tls() : Port.tls(url.getPort()); // System application endpoints are always TLS
- return new Endpoint(TenantAndApplicationId.from(systemApplication.id()),
- Optional.of(systemApplication.id().instance()),
- null,
- ClusterSpec.Id.from("admin"),
- url,
- List.of(new Target(new DeploymentId(systemApplication.id(), zone))),
- Scope.zone, port, false, routingMethod, false);
- }
-
/** A target of an endpoint */
public static class Target {
@@ -502,7 +490,7 @@ public class Endpoint {
public EndpointBuilder target(EndpointId endpointId, ClusterSpec.Id cluster, List<DeploymentId> deployments) {
this.endpointId = endpointId;
this.cluster = cluster;
- this.targets = deployments.stream().map(Target::new).collect(Collectors.toUnmodifiableList());
+ this.targets = deployments.stream().map(Target::new).toList();
this.scope = requireUnset(Scope.global);
return this;
}
@@ -538,7 +526,7 @@ public class Endpoint {
this.cluster = cluster;
this.targets = deployments.entrySet().stream()
.map(kv -> new Target(kv.getKey(), kv.getValue()))
- .collect(Collectors.toUnmodifiableList());
+ .toList();
this.scope = Scope.application;
return this;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java
index 613422b2749..f4a1001d9ff 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java
@@ -11,7 +11,6 @@ import com.yahoo.vespa.applicationmodel.InfrastructureApplication;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ServiceConvergence;
-import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import java.util.Arrays;
import java.util.EnumSet;
@@ -76,17 +75,6 @@ public enum SystemApplication {
return nodeType().isHost();
}
- /** Returns whether this has an endpoint */
- public boolean hasEndpoint() {
- return this == configServer;
- }
-
- /** Returns the endpoint of this, if any */
- public Optional<Endpoint> endpointIn(ZoneId zone, ZoneRegistry zoneRegistry) {
- if (!hasEndpoint()) return Optional.empty();
- return Optional.of(Endpoint.of(this, zone, zoneRegistry.getConfigServerVipUri(zone)));
- }
-
/** All system applications that are not the controller */
public static List<SystemApplication> notController() {
return List.copyOf(EnumSet.complementOf(EnumSet.of(SystemApplication.controllerHost)));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
index 7b3b90d1458..4bc9aeb00e4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
@@ -20,7 +20,6 @@ import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
-import com.yahoo.slime.Type;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.deployment.ZipBuilder;
import com.yahoo.yolean.Exceptions;
@@ -98,6 +97,7 @@ public class ApplicationPackage {
* it must not be further changed by the caller.
* If 'requireFiles' is true, files needed by deployment orchestration must be present.
*/
+ @SuppressWarnings("deprecation") // for Hashing.sha1()
public ApplicationPackage(byte[] zippedContent, boolean requireFiles) {
this.zippedContent = Objects.requireNonNull(zippedContent, "The application package content cannot be null");
this.contentHash = Hashing.sha1().hashBytes(zippedContent).toString();
@@ -240,6 +240,7 @@ public class ApplicationPackage {
}
// Hashes all files and settings that require a deployment to be forwarded to configservers
+ @SuppressWarnings("deprecation") // for Hashing.sha1()
private String calculateBundleHash(byte[] zippedContent) {
Predicate<String> entryMatcher = name -> ! name.endsWith(deploymentFile) && ! name.endsWith(buildMetaFile);
SortedMap<String, Long> crcByEntry = new TreeMap<>();
@@ -261,6 +262,7 @@ public class ApplicationPackage {
.hash().toString();
}
+ @SuppressWarnings("deprecation") // for Hashing.sha1()
public static String calculateHash(byte[] bytes) {
return Hashing.sha1().newHasher()
.putBytes(bytes)
@@ -274,14 +276,6 @@ public class ApplicationPackage {
/** Max size of each extracted file */
private static final int maxSize = 10 << 20; // 10 Mb
- // TODO: Vespa 8: Remove application/ directory support
- private static final String applicationDir = "application/";
-
- private static String withoutLegacyDir(String name) {
- if (name.startsWith(applicationDir)) return name.substring(applicationDir.length());
- return name;
- }
-
private final byte[] zip;
private final Map<Path, Optional<byte[]>> cache;
@@ -307,11 +301,11 @@ public class ApplicationPackage {
private Map<Path, Optional<byte[]>> read(Collection<String> names) {
var entries = ZipEntries.from(zip,
- name -> names.contains(withoutLegacyDir(name)),
+ name -> names.contains(name),
maxSize,
true)
.asList().stream()
- .collect(toMap(entry -> Paths.get(withoutLegacyDir(entry.name())).normalize(),
+ .collect(toMap(entry -> Paths.get(entry.name()).normalize(),
ZipEntries.ZipEntryWithContent::content));
names.stream().map(Paths::get).forEach(path -> entries.putIfAbsent(path.normalize(), Optional.empty()));
return entries;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
index ccad4fe92ad..8546eb5a971 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application.pkg;
+import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.Endpoint;
@@ -20,6 +21,7 @@ import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
+import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import java.time.Instant;
import java.util.ArrayList;
@@ -59,6 +61,21 @@ public class ApplicationPackageValidator {
validateEndpointChange(application, applicationPackage, instant);
validateCompactedEndpoint(applicationPackage);
validateSecurityClientsPem(applicationPackage);
+ validateDeprecatedElements(applicationPackage);
+ }
+
+ /** Verify that deployment spec does not use elements deprecated on a major version older than wanted major version */
+ private void validateDeprecatedElements(ApplicationPackage applicationPackage) {
+ int wantedMajor = applicationPackage.compileVersion().map(Version::getMajor)
+ .or(() -> applicationPackage.deploymentSpec().majorVersion())
+ .or(() -> controller.readVersionStatus().controllerVersion()
+ .map(VespaVersion::versionNumber)
+ .map(Version::getMajor))
+ .orElseThrow(() -> new IllegalArgumentException("Could not determine wanted major version"));
+ for (var deprecatedElement : applicationPackage.deploymentSpec().deprecatedElements()) {
+ if (deprecatedElement.majorVersion() >= wantedMajor) continue;
+ throw new IllegalArgumentException(deprecatedElement.humanReadableString());
+ }
}
/** Verify that we have the security/clients.pem file for public systems */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java
index 21914b87818..0a0adcfc252 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java
@@ -46,7 +46,7 @@ public class CuratorArchiveBucketDb {
return getBucketNameFromCache(zoneId, tenant)
.or(() -> findAndUpdateArchiveUriCache(zoneId, tenant, buckets(zoneId)))
.or(() -> createIfMissing ? Optional.of(assignToBucket(zoneId, tenant)) : Optional.empty())
- .map(bucketName -> URI.create(Text.format("s3://%s/%s/", bucketName, tenant.value())));
+ .map(bucketName -> archiveService.bucketURI(zoneId, bucketName, tenant));
}
private String assignToBucket(ZoneId zoneId, TenantName tenant) {
@@ -57,7 +57,7 @@ public class CuratorArchiveBucketDb {
.orElseGet(() -> {
// If not, find an existing bucket with space
Optional<ArchiveBucket> unfilledBucket = zoneBuckets.stream()
- .filter(bucket -> bucket.tenants().size() < tenantsPerBucket().orElse(Integer.MAX_VALUE))
+ .filter(bucket -> archiveService.canAddTenantToBucket(zoneId, bucket))
.findAny();
// And place the tenant in that bucket.
@@ -94,23 +94,6 @@ public class CuratorArchiveBucketDb {
return bucketName;
}
- private OptionalInt tenantsPerBucket() {
- if (system.isPublic()) {
- /*
- * Due to policy limits, we can't put data for more than this many tenants in a bucket.
- * Policy size limit is 20kb, about 550 bytes for non-tenant related policies. Each tenant
- * needs about 500 + len(role_arn) bytes, we limit role_arn to 100 characters, so we can
- * fit about (20k - 550) / 600 ~ 32 tenants per bucket.
- */
- return OptionalInt.of(30);
- } else {
- /*
- * The S3 policies in main/cd have a fixed size.
- */
- return OptionalInt.empty();
- }
- }
-
private Optional<String> getBucketNameFromCache(ZoneId zoneId, TenantName tenantName) {
return Optional.ofNullable(archiveUriCache.get(zoneId)).map(map -> map.get(tenantName));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
index e4869cc9bf4..3bc2aec95ff 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.restapi.RestApiException;
import com.yahoo.text.Text;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
@@ -33,7 +34,6 @@ import com.yahoo.vespa.hosted.controller.security.TenantSpec;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
-import javax.ws.rs.ForbiddenException;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
@@ -197,7 +197,7 @@ public class AthenzFacade implements AccessControl {
}
catch (ZmsClientException e) {
if (e.getErrorCode() == com.yahoo.jdisc.Response.Status.FORBIDDEN)
- throw new ForbiddenException("Not authorized to create application", e);
+ throw new RestApiException.Forbidden("Not authorized to create application", e);
else
throw e;
}
@@ -289,7 +289,7 @@ public class AthenzFacade implements AccessControl {
private void verifyIsDomainAdmin(AthenzIdentity identity, AthenzDomain domain) {
log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", identity);
if ( ! zmsClient.getMembership(new AthenzRole(domain, "admin"), identity))
- throw new ForbiddenException(
+ throw new RestApiException.Forbidden(
Text.format("The user '%s' is not admin in Athenz domain '%s'", identity.getFullName(), domain.getName()));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
index 996b53cc6f5..33aeda5e011 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.certificate;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.text.Text;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -11,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidator;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.GcpSecretStore;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.time.Clock;
@@ -60,6 +62,22 @@ public class EndpointCertificates {
Duration duration = Duration.between(start, clock.instant());
if (duration.toSeconds() > 30)
log.log(Level.INFO, Text.format("Getting endpoint certificate metadata for %s took %d seconds!", instance.id().serializedForm(), duration.toSeconds()));
+
+ if (controller.zoneRegistry().zones().ofCloud(CloudName.from("gcp")).ids().contains(zone)) { // Until CKMS is available from GCP
+ if(metadata.isPresent()) {
+ var m = metadata.get();
+ GcpSecretStore gcpSecretStore = controller.serviceRegistry().gcpSecretStore();
+ String mangledCertName = "endpointCert_" + m.certName().replace('.', '_') + "-v" + m.version(); // Google cloud does not accept dots in secrets, but they accept underscores
+ String mangledKeyName = "endpointCert_" + m.keyName().replace('.', '_') + "-v" + m.version(); // Google cloud does not accept dots in secrets, but they accept underscores
+ if (gcpSecretStore.getSecret(mangledCertName, m.version()) == null)
+ gcpSecretStore.createSecret(mangledCertName, controller.secretStore().getSecret(m.certName(), m.version()));
+ if (gcpSecretStore.getSecret(mangledKeyName, m.version()) == null)
+ gcpSecretStore.createSecret(mangledKeyName, controller.secretStore().getSecret(m.keyName(), m.version()));
+
+ return Optional.of(m.withVersion(1).withKeyName(mangledKeyName).withCertName(mangledCertName));
+ }
+ }
+
return metadata;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index be07a2b0cb1..6e2ae0da46d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -7,9 +7,7 @@ import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.InstanceName;
-import com.yahoo.text.Text;
import com.yahoo.transaction.Mutex;
-import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -148,9 +146,11 @@ public class DeploymentTrigger {
return;
}
- applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application ->
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
+ if (application.get().deploymentSpec().instance(id.instance()).isPresent())
applications().store(application.with(id.instance(),
- instance -> withRemainingChange(instance, instance.change(), jobs.deploymentStatus(application.get())))));
+ instance -> withRemainingChange(instance, instance.change(), jobs.deploymentStatus(application.get()))));
+ });
}
/**
@@ -248,7 +248,7 @@ public class DeploymentTrigger {
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
jobs.forEach((jobId, versionsList) -> {
- trigger(deploymentJob(instance, versionsList.get(0).versions(), jobId.type(), status.jobs().get(jobId).get(), clock.instant()), reason);
+ trigger(deploymentJob(application.require(job.application().instance()), versionsList.get(0).versions(), jobId.type(), status.jobs().get(jobId).get(), clock.instant()), reason);
});
return List.copyOf(jobs.keySet());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index fb7a3fc4ba5..813e3454e80 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -157,7 +157,7 @@ public class InternalStepRunner implements StepRunner {
}
private Optional<RunStatus> deployInitialReal(RunId id, DualLogger logger) {
- Versions versions = controller.jobController().run(id).get().versions();
+ Versions versions = controller.jobController().run(id).versions();
logger.log("Deploying platform version " +
versions.sourcePlatform().orElse(versions.targetPlatform()) +
" and application " +
@@ -166,16 +166,16 @@ public class InternalStepRunner implements StepRunner {
}
private Optional<RunStatus> deployReal(RunId id, DualLogger logger) {
- Versions versions = controller.jobController().run(id).get().versions();
+ Versions versions = controller.jobController().run(id).versions();
logger.log("Deploying platform version " + versions.targetPlatform() +
" and application " + versions.targetRevision() + " ...");
return deployReal(id, false, logger);
}
private Optional<RunStatus> deployReal(RunId id, boolean setTheStage, DualLogger logger) {
- Optional<X509Certificate> testerCertificate = controller.jobController().run(id).get().testerCertificate();
+ Optional<X509Certificate> testerCertificate = controller.jobController().run(id).testerCertificate();
return deploy(() -> controller.applications().deploy(id.job(), setTheStage),
- controller.jobController().run(id).get()
+ controller.jobController().run(id)
.stepInfo(setTheStage ? deployInitialReal : deployReal).get()
.startTime().get(),
logger)
@@ -184,8 +184,8 @@ public class InternalStepRunner implements StepRunner {
if ( ! useTesterCertificate(id) || result != running)
return true;
// If tester cert, ensure real is deployed with the tester cert whose key was successfully deployed.
- return controller.jobController().run(id).get().stepStatus(deployTester).get() == succeeded
- && testerCertificate.equals(controller.jobController().run(id).get().testerCertificate());
+ return controller.jobController().run(id).stepStatus(deployTester).get() == succeeded
+ && testerCertificate.equals(controller.jobController().run(id).testerCertificate());
});
}
@@ -196,7 +196,7 @@ public class InternalStepRunner implements StepRunner {
testerPackage(id),
id.type().zone(),
platform),
- controller.jobController().run(id).get()
+ controller.jobController().run(id)
.stepInfo(deployTester).get()
.startTime().get(),
logger);
@@ -287,10 +287,10 @@ public class InternalStepRunner implements StepRunner {
return Optional.of(installationFailed);
}
- Versions versions = controller.jobController().run(id).get().versions();
+ Versions versions = controller.jobController().run(id).versions();
Version platform = setTheStage ? versions.sourcePlatform().orElse(versions.targetPlatform()) : versions.targetPlatform();
- Run run = controller.jobController().run(id).get();
+ Run run = controller.jobController().run(id);
Optional<ServiceConvergence> services = controller.serviceRegistry().configServer().serviceConvergence(new DeploymentId(id.application(), id.type().zone()),
Optional.of(platform));
if (services.isEmpty()) {
@@ -409,13 +409,14 @@ public class InternalStepRunner implements StepRunner {
}
private Version testerPlatformVersion(RunId id) {
- return application(id.application()).change().isPinned()
- ? controller.jobController().run(id).get().versions().targetPlatform()
- : controller.readSystemVersion();
+ Version targetPlatform = controller.jobController().run(id).versions().targetPlatform();
+ Version systemVersion = controller.readSystemVersion();
+ boolean incompatible = controller.applications().versionCompatibility(id.application()).refuse(targetPlatform, systemVersion);
+ return incompatible || application(id.application()).change().isPinned() ? targetPlatform : systemVersion;
}
private Optional<RunStatus> installTester(RunId id, DualLogger logger) {
- Run run = controller.jobController().run(id).get();
+ Run run = controller.jobController().run(id);
Version platform = testerPlatformVersion(id);
ZoneId zone = id.type().zone();
ApplicationId testerId = id.tester().id();
@@ -607,6 +608,9 @@ public class InternalStepRunner implements StepRunner {
byte[] config = testConfigSerializer.configJson(id.application(),
id.type(),
true,
+ deployment.get().version(),
+ deployment.get().revision(),
+ deployment.get().at(),
endpoints,
controller.applications().reachableContentClustersByZone(deployments));
controller.jobController().cloud().startTests(getTesterDeploymentId(id), suite, config);
@@ -621,7 +625,7 @@ public class InternalStepRunner implements StepRunner {
return Optional.of(error);
}
- Optional<X509Certificate> testerCertificate = controller.jobController().run(id).get().testerCertificate();
+ Optional<X509Certificate> testerCertificate = controller.jobController().run(id).testerCertificate();
if (testerCertificate.isPresent()) {
try {
testerCertificate.get().checkValidity(Date.from(controller.clock().instant()));
@@ -662,7 +666,10 @@ public class InternalStepRunner implements StepRunner {
"or a Java test bundle under 'components/' with at least one test with the annotation " +
"for this suite. See docs.vespa.ai/en/testing.html for details.");
controller.jobController().updateTestReport(id);
- return Optional.of(noTests);
+
+ DeploymentSpec spec = controller.applications().requireApplication(TenantAndApplicationId.from(id.application())).deploymentSpec();
+ boolean requireTests = spec.steps().stream().anyMatch(step -> step.concerns(id.type().environment()));
+ return Optional.of(requireTests ? testFailure : noTests);
}
case SUCCESS:
logger.log("Tests completed successfully.");
@@ -680,7 +687,7 @@ public class InternalStepRunner implements StepRunner {
}
// Hitting a config server which doesn't have this particular app loaded causes a 404.
catch (ConfigServerException e) {
- Instant doom = controller.jobController().run(id).get().stepInfo(copyVespaLogs).get().startTime().get()
+ Instant doom = controller.jobController().run(id).stepInfo(copyVespaLogs).get().startTime().get()
.plus(Duration.ofMinutes(3));
if (e.code() == ConfigServerException.ErrorCode.NOT_FOUND && controller.clock().instant().isBefore(doom)) {
logger.log(INFO, "Found no logs, but will retry");
@@ -706,7 +713,7 @@ public class InternalStepRunner implements StepRunner {
}
catch (RuntimeException e) {
logger.log(WARNING, "Failed deleting application " + id.application(), e);
- Instant startTime = controller.jobController().run(id).get().stepInfo(deactivateReal).get().startTime().get();
+ Instant startTime = controller.jobController().run(id).stepInfo(deactivateReal).get().startTime().get();
return startTime.isBefore(controller.clock().instant().minus(Duration.ofHours(1)))
? Optional.of(error)
: Optional.empty();
@@ -721,7 +728,7 @@ public class InternalStepRunner implements StepRunner {
}
catch (RuntimeException e) {
logger.log(WARNING, "Failed deleting tester of " + id.application(), e);
- Instant startTime = controller.jobController().run(id).get().stepInfo(deactivateTester).get().startTime().get();
+ Instant startTime = controller.jobController().run(id).stepInfo(deactivateTester).get().startTime().get();
return startTime.isBefore(controller.clock().instant().minus(Duration.ofHours(1)))
? Optional.of(error)
: Optional.empty();
@@ -745,7 +752,7 @@ public class InternalStepRunner implements StepRunner {
return Optional.of(error);
}
catch (RuntimeException e) {
- Instant start = controller.jobController().run(id).get().stepInfo(report).get().startTime().get();
+ Instant start = controller.jobController().run(id).stepInfo(report).get().startTime().get();
return (controller.clock().instant().isAfter(start.plusSeconds(180)))
? Optional.empty()
: Optional.of(error);
@@ -867,7 +874,7 @@ public class InternalStepRunner implements StepRunner {
private boolean timedOut(RunId id, Deployment deployment, Duration defaultTimeout) {
// TODO jonmv: This is a workaround for new deployment writes not yet being visible in spite of Curator locking.
// TODO Investigate what's going on here, and remove this workaround.
- Run run = controller.jobController().run(id).get();
+ Run run = controller.jobController().run(id);
if ( ! controller.system().isCd() && run.start().isAfter(deployment.at()))
return false;
@@ -883,7 +890,7 @@ public class InternalStepRunner implements StepRunner {
/** Returns the application package for the tester application, assembled from a generated config, fat-jar and services.xml. */
private ApplicationPackage testerPackage(RunId id) {
- RevisionId revision = controller.jobController().run(id).get().versions().targetRevision();
+ RevisionId revision = controller.jobController().run(id).versions().targetRevision();
DeploymentSpec spec = controller.applications().requireApplication(TenantAndApplicationId.from(id.application())).deploymentSpec();
byte[] testZip = controller.applications().applicationStore().getTester(id.application().tenant(),
id.application().application(), revision);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 30f16acf77d..3d4a2f40303 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -47,6 +47,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
+import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
@@ -69,8 +70,12 @@ import static com.yahoo.vespa.hosted.controller.deployment.Step.copyVespaLogs;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deactivateTester;
import static com.yahoo.vespa.hosted.controller.deployment.Step.endStagingSetup;
import static com.yahoo.vespa.hosted.controller.deployment.Step.endTests;
+import static com.yahoo.vespa.hosted.controller.deployment.Step.installInitialReal;
+import static com.yahoo.vespa.hosted.controller.deployment.Step.installReal;
+import static com.yahoo.vespa.hosted.controller.deployment.Step.installTester;
import static com.yahoo.vespa.hosted.controller.deployment.Step.report;
import static java.time.temporal.ChronoUnit.SECONDS;
+import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.function.Predicate.not;
import static java.util.logging.Level.INFO;
@@ -179,11 +184,27 @@ public class JobController {
if (deployment.isEmpty() || deployment.get().at().isBefore(run.start()))
return run;
- Instant from = run.lastVespaLogTimestamp().isAfter(deployment.get().at()) ? run.lastVespaLogTimestamp() : deployment.get().at();
+ Instant deployedAt = run.stepInfo(installInitialReal).or(() -> run.stepInfo(installReal)).flatMap(StepInfo::startTime).orElseThrow();
+ Instant from = run.lastVespaLogTimestamp().isAfter(run.start()) ? run.lastVespaLogTimestamp() : deployedAt.minusSeconds(10);
List<LogEntry> log = LogEntry.parseVespaLog(controller.serviceRegistry().configServer()
.getLogs(new DeploymentId(id.application(), zone),
Map.of("from", Long.toString(from.toEpochMilli()))),
from);
+
+ if (run.hasStep(installTester) && run.versions().targetPlatform().isAfter(new Version("7.590"))) { // todo jonmv: remove
+ deployedAt = run.stepInfo(installTester).flatMap(StepInfo::startTime).orElseThrow();
+ from = run.lastVespaLogTimestamp().isAfter(run.start()) ? run.lastVespaLogTimestamp() : deployedAt.minusSeconds(10);
+ List<LogEntry> testerLog = LogEntry.parseVespaLog(controller.serviceRegistry().configServer()
+ .getLogs(new DeploymentId(id.tester().id(), zone),
+ Map.of("from", Long.toString(from.toEpochMilli()))),
+ from);
+
+ Instant justNow = controller.clock().instant().minusSeconds(2);
+ log = Stream.concat(log.stream(), testerLog.stream())
+ .filter(entry -> entry.at().isBefore(justNow))
+ .sorted(comparing(LogEntry::at))
+ .collect(toUnmodifiableList());
+ }
if (log.isEmpty())
return run;
@@ -278,11 +299,12 @@ public class JobController {
return runs.build();
}
- /** Returns the run with the given id, if it exists. */
- public Optional<Run> run(RunId id) {
+ /** Returns the run with the given id, or throws if no such run exists. */
+ public Run run(RunId id) {
return runs(id.application(), id.type()).values().stream()
.filter(run -> run.id().equals(id))
- .findAny();
+ .findAny()
+ .orElseThrow(() -> new NoSuchElementException("no run with id '" + id + "' exists"));
}
/** Returns the last run of the given type, for the given application, if one has been run. */
@@ -392,7 +414,7 @@ public class JobController {
Deque<Mutex> locks = new ArrayDeque<>();
try {
// Ensure no step is still running before we finish the run — report depends transitively on all the other steps.
- Run unlockedRun = run(id).get();
+ Run unlockedRun = run(id);
locks.push(curator.lock(id.application(), id.type(), report));
for (Step step : report.allPrerequisites(unlockedRun.steps().keySet()))
locks.push(curator.lock(id.application(), id.type(), step));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java
index 03cc6c6ba8d..7ffaaabb1a7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java
@@ -45,8 +45,9 @@ public class Run {
// For deserialisation only -- do not use!
public Run(RunId id, Map<Step, StepInfo> steps, Versions versions, boolean isRedeployment, Instant start, Optional<Instant> end,
- Optional<Instant> sleepUntil, RunStatus status, long lastTestRecord, Instant lastVespaLogTimestamp, Optional<Instant> noNodesDownSince,
- Optional<ConvergenceSummary> convergenceSummary, Optional<X509Certificate> testerCertificate, boolean dryRun, Optional<String> reason) {
+ Optional<Instant> sleepUntil, RunStatus status, long lastTestRecord, Instant lastVespaLogTimestamp,
+ Optional<Instant> noNodesDownSince, Optional<ConvergenceSummary> convergenceSummary,
+ Optional<X509Certificate> testerCertificate, boolean dryRun, Optional<String> reason) {
this.id = id;
this.steps = Collections.unmodifiableMap(new EnumMap<>(steps));
this.versions = versions;
@@ -67,8 +68,9 @@ public class Run {
public static Run initial(RunId id, Versions versions, boolean isRedeployment, Instant now, JobProfile profile, Optional<String> triggeredBy) {
EnumMap<Step, StepInfo> steps = new EnumMap<>(Step.class);
profile.steps().forEach(step -> steps.put(step, StepInfo.initial(step)));
- return new Run(id, steps, requireNonNull(versions), isRedeployment, requireNonNull(now), Optional.empty(), Optional.empty(), running,
- -1, Instant.EPOCH, Optional.empty(), Optional.empty(), Optional.empty(), profile == JobProfile.developmentDryRun, triggeredBy);
+ return new Run(id, steps, requireNonNull(versions), isRedeployment, requireNonNull(now), Optional.empty(),
+ Optional.empty(), running, -1, Instant.EPOCH, Optional.empty(), Optional.empty(),
+ Optional.empty(), profile == JobProfile.developmentDryRun, triggeredBy);
}
/** Returns a new Run with the status of the given completed step set accordingly. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java
index 379cc9c4f0a..ec4d138c44f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java
@@ -65,7 +65,7 @@ public enum Step {
deactivateReal(true, deployInitialReal, deployReal, endTests, copyVespaLogs),
/** Deactivate the tester. */
- deactivateTester(true, deployTester, endTests),
+ deactivateTester(true, deployTester, endTests, copyVespaLogs),
/** Report completion to the deployment orchestration machinery. */
report(true, installReal, deactivateReal, deactivateTester);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java
index 1680e064234..2394f293170 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
@@ -8,10 +9,12 @@ import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -31,6 +34,9 @@ public class TestConfigSerializer {
public Slime configSlime(ApplicationId id,
JobType type,
boolean isCI,
+ Version platform,
+ RevisionId revision,
+ Instant deployedAt,
Map<ZoneId, List<Endpoint>> deployments,
Map<ZoneId, List<String>> clusters) {
Slime slime = new Slime();
@@ -40,6 +46,9 @@ public class TestConfigSerializer {
root.setString("zone", type.zone().value());
root.setString("system", system.value());
root.setBool("isCI", isCI);
+ root.setString("platform", platform.toFullString());
+ root.setLong("revision", revision.number());
+ root.setLong("deployedAt", deployedAt.toEpochMilli());
// TODO jvenstad: remove when clients can be updated
Cursor endpointsObject = root.setObject("endpoints");
@@ -72,10 +81,13 @@ public class TestConfigSerializer {
public byte[] configJson(ApplicationId id,
JobType type,
boolean isCI,
+ Version platform,
+ RevisionId revision,
+ Instant deployedAt,
Map<ZoneId, List<Endpoint>> deployments,
Map<ZoneId, List<String>> clusters) {
try {
- return SlimeUtils.toJsonBytes(configSlime(id, type, isCI, deployments, clusters));
+ return SlimeUtils.toJsonBytes(configSlime(id, type, isCI, platform, revision, deployedAt, deployments, clusters));
}
catch (IOException e) {
throw new UncheckedIOException(e);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java
index bd69ea41b05..788360996ff 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java
@@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.vespa.hosted.controller.archive.CuratorArchiveBucketDb;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
@@ -45,47 +46,34 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer {
@Override
protected double maintain() {
- // Count buckets - so we can alert if we get close to the account limit of 1000
- zoneRegistry.zonesIncludingSystem().all().ids().forEach(zoneId ->
- metric.set(bucketCountMetricName, archiveBucketDb.buckets(zoneId).size(),
- metric.createContext(Map.of("zone", zoneId.value()))));
+ // Count buckets - so we can alert if we get close to the AWS account limit of 1000
+ zoneRegistry.zonesIncludingSystem().all().zones().forEach(z ->
+ metric.set(bucketCountMetricName, archiveBucketDb.buckets(z.getVirtualId()).size(),
+ metric.createContext(Map.of(
+ "zone", z.getVirtualId().value(),
+ "cloud", z.getCloudName().value()))));
zoneRegistry.zonesIncludingSystem().controllerUpgraded().zones().forEach(z -> {
- ZoneId zoneId = z.getVirtualId();
- try {
- var tenantArchiveAccessRoles = cloudTenantArchiveExternalAccessRoles();
- archiveBucketDb.buckets(zoneId).forEach(archiveBucket ->
- archiveService.updateBucketPolicy(zoneId, archiveBucket,
- Maps.filterEntries(tenantArchiveAccessRoles,
- entry -> archiveBucket.tenants().contains(entry.getKey())))
- );
- Map<String, List<ArchiveBucket>> bucketsPerKey = archiveBucketDb.buckets(zoneId).stream()
- .collect(groupingBy(ArchiveBucket::keyArn));
- bucketsPerKey.forEach((keyArn, buckets) -> {
- Set<String> authorizedIamRolesForKey = buckets.stream()
- .flatMap(b -> b.tenants().stream())
- .filter(tenantArchiveAccessRoles::containsKey)
- .map(tenantArchiveAccessRoles::get)
- .collect(Collectors.toSet());
- archiveService.updateKeyPolicy(zoneId, keyArn, authorizedIamRolesForKey);
- });
- } catch (Exception e) {
- throw new RuntimeException("Failed to maintain archive access in " + zoneId.value(), e);
- }
- }
- );
+ ZoneId zoneId = z.getVirtualId();
+ try {
+ var tenantArchiveAccessRoles = cloudTenantArchiveExternalAccessRoles();
+ var buckets = archiveBucketDb.buckets(zoneId);
+ archiveService.updatePolicies(zoneId, buckets, tenantArchiveAccessRoles);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to maintain archive access in " + zoneId.value(), e);
+ }
+ });
return 1.0;
}
- private Map<TenantName, String> cloudTenantArchiveExternalAccessRoles() {
+ private Map<TenantName, ArchiveAccess> cloudTenantArchiveExternalAccessRoles() {
List<Tenant> tenants = controller().tenants().asList();
return tenants.stream()
.filter(t -> t instanceof CloudTenant)
.map(t -> (CloudTenant) t)
- .filter(t -> t.archiveAccessRole().isPresent())
.collect(Collectors.toUnmodifiableMap(
- Tenant::name, cloudTenant -> cloudTenant.archiveAccessRole().orElseThrow()));
+ Tenant::name, cloudTenant -> cloudTenant.archiveAccess()));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
index 36ab2e6f384..42821ea8fe2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
@@ -40,15 +40,13 @@ public class ArchiveUriUpdater extends ControllerMaintainer {
protected double maintain() {
Map<ZoneId, Set<TenantName>> tenantsByZone = new HashMap<>();
- tenantsByZone.put(controller().zoneRegistry().systemZone().getVirtualId(),
- new HashSet<>(INFRASTRUCTURE_TENANTS));
+ controller().zoneRegistry().zonesIncludingSystem().reachable().zones().forEach(
+ z -> tenantsByZone.put(z.getVirtualId(), new HashSet<>(INFRASTRUCTURE_TENANTS)));
for (var application : applications.asList()) {
for (var instance : application.instances().values()) {
for (var deployment : instance.deployments().values()) {
- tenantsByZone
- .computeIfAbsent(deployment.zone(), zone -> new HashSet<>(INFRASTRUCTURE_TENANTS))
- .add(instance.id().tenant());
+ tenantsByZone.get(deployment.zone()).add(instance.id().tenant());
}
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTracker.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTracker.java
deleted file mode 100644
index 021c02fb6a0..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTracker.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.maintenance;
-
-import com.yahoo.config.provision.CloudName;
-import com.yahoo.config.provision.zone.ZoneApi;
-import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.integration.aws.CloudEvent;
-import com.yahoo.vespa.hosted.controller.api.integration.aws.CloudEventFetcher;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeFilter;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
-
-import java.time.Duration;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-/**
- * This tracks maintenance events from cloud providers and deprovisions any affected hosts.
- *
- * @author mgimle
- */
-public class CloudEventTracker extends ControllerMaintainer {
-
- private static final Logger log = Logger.getLogger(CloudEventTracker.class.getName());
-
- private final CloudEventFetcher eventFetcher;
- private final Map<String, List<ZoneApi>> zonesByCloudNativeRegion;
- private final NodeRepository nodeRepository;
-
- CloudEventTracker(Controller controller, Duration interval) {
- super(controller, interval);
- this.eventFetcher = controller.serviceRegistry().eventFetcherService();
- this.nodeRepository = controller.serviceRegistry().configServer().nodeRepository();
- this.zonesByCloudNativeRegion = supportedZonesByRegion();
- }
-
- @Override
- protected double maintain() {
- for (var region : zonesByCloudNativeRegion.keySet()) {
- for (var event : eventFetcher.getEvents(region)) {
- deprovisionAffectedHosts(region, event);
- }
- }
- return 1.0;
- }
-
- /** Deprovision any host affected by given event */
- private void deprovisionAffectedHosts(String region, CloudEvent event) {
- for (var zone : zonesByCloudNativeRegion.get(region)) {
- for (var node : nodeRepository.list(zone.getId(), NodeFilter.all())) {
- if (!deprovision(node, event)) continue;
- log.info("Retiring and deprovisioning " + node.hostname().value() + " in " + zone.getId() +
- ": Affected by maintenance event " + event.instanceEventId);
- nodeRepository.retire(zone.getId(), node.hostname().value(), true, true);
- }
- }
- }
-
- private static boolean deprovision(Node node, CloudEvent event) {
- if (!node.type().isHost()) return false; // Non-hosts are never affected
- if (node.wantToRetire() && node.wantToDeprovision()) return false; // Already deprovisioning
- return event.affectedInstances.stream()
- .anyMatch(instance -> node.hostname().value().contains(instance));
- }
-
- /** Returns zones supported by this, grouped by their native region name */
- private Map<String, List<ZoneApi>> supportedZonesByRegion() {
- return controller().zoneRegistry().zones()
- .ofCloud(CloudName.from("aws"))
- .reachable()
- .zones().stream()
- .collect(Collectors.groupingBy(ZoneApi::getCloudNativeRegionName));
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
index d482dedfdaa..b1b7e80e9a0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java
@@ -28,7 +28,8 @@ import java.util.stream.Collectors;
public class CloudTrialExpirer extends ControllerMaintainer {
private static final Logger log = Logger.getLogger(CloudTrialExpirer.class.getName());
- private static final Duration loginExpiry = Duration.ofDays(14);
+ private static final Duration nonePlanAfter = Duration.ofDays(14);
+ private static final Duration tombstoneAfter = Duration.ofDays(365);
private final ListFlag<String> extendedTrialTenants;
public CloudTrialExpirer(Controller controller, Duration interval) {
@@ -38,24 +39,20 @@ public class CloudTrialExpirer extends ControllerMaintainer {
@Override
protected double maintain() {
- if (controller().system().equals(SystemName.PublicCd)) {
- tombstoneNonePlanTenants();
- }
+ tombstoneNonePlanTenants();
moveInactiveTenantsToNonePlan();
return 1.0;
}
private void moveInactiveTenantsToNonePlan() {
- var predicate = tenantReadersNotLoggedIn(loginExpiry)
+ var predicate = tenantReadersNotLoggedIn(nonePlanAfter)
.and(this::tenantHasTrialPlan);
forTenant("'none' plan", predicate, this::setPlanNone);
}
private void tombstoneNonePlanTenants() {
- // tombstone tenants that are inactive 14 days after being set as 'none'
- var expiry = loginExpiry.plus(loginExpiry);
- var predicate = tenantReadersNotLoggedIn(expiry).and(this::tenantHasNonePlan);
+ var predicate = tenantReadersNotLoggedIn(tombstoneAfter).and(this::tenantHasNonePlan);
forTenant("tombstoned", predicate, this::tombstoneTenants);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index 3beb4149938..8c52ae0560a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
@@ -1,8 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
-import com.yahoo.component.annotation.Inject;
import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.annotation.Inject;
import com.yahoo.concurrent.maintenance.Maintainer;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneApi;
@@ -59,9 +59,7 @@ public class ControllerMaintenance extends AbstractComponent {
maintainers.add(new NameServiceDispatcher(controller, intervals.nameServiceDispatcher));
maintainers.add(new CostReportMaintainer(controller, intervals.costReportMaintainer, controller.serviceRegistry().costReportConsumer()));
maintainers.add(new ResourceMeterMaintainer(controller, intervals.resourceMeterMaintainer, metric, controller.serviceRegistry().resourceDatabase()));
- maintainers.add(new CloudEventTracker(controller, intervals.cloudEventReporter));
maintainers.add(new ResourceTagMaintainer(controller, intervals.resourceTagMaintainer, controller.serviceRegistry().resourceTagger()));
- maintainers.add(new SystemRoutingPolicyMaintainer(controller, intervals.systemRoutingPolicyMaintainer));
maintainers.add(new ApplicationMetaDataGarbageCollector(controller, intervals.applicationMetaDataGarbageCollector));
maintainers.add(new ArtifactExpirer(controller, intervals.containerImageExpirer));
maintainers.add(new HostInfoUpdater(controller, intervals.hostInfoUpdater));
@@ -118,9 +116,7 @@ public class ControllerMaintenance extends AbstractComponent {
private final Duration nameServiceDispatcher;
private final Duration costReportMaintainer;
private final Duration resourceMeterMaintainer;
- private final Duration cloudEventReporter;
private final Duration resourceTagMaintainer;
- private final Duration systemRoutingPolicyMaintainer;
private final Duration applicationMetaDataGarbageCollector;
private final Duration containerImageExpirer;
private final Duration hostInfoUpdater;
@@ -153,9 +149,7 @@ public class ControllerMaintenance extends AbstractComponent {
this.nameServiceDispatcher = duration(10, SECONDS);
this.costReportMaintainer = duration(2, HOURS);
this.resourceMeterMaintainer = duration(3, MINUTES);
- this.cloudEventReporter = duration(30, MINUTES);
this.resourceTagMaintainer = duration(30, MINUTES);
- this.systemRoutingPolicyMaintainer = duration(15, MINUTES);
this.applicationMetaDataGarbageCollector = duration(12, HOURS);
this.containerImageExpirer = duration(12, HOURS);
this.hostInfoUpdater = duration(12, HOURS);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
index 369699eb3a3..94ec4129744 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
@@ -81,9 +81,8 @@ public class JobRunner extends ControllerMaintainer {
&& controller().clock().instant().isAfter(run.sleepUntil().orElse(run.start()).plus(jobTimeout)))
executors.execute(() -> {
jobs.abort(run.id(), "job timeout of " + jobTimeout + " reached");
- advance(jobs.run(run.id()).get());
+ advance(jobs.run(run.id()));
});
-
else if (run.readySteps().isEmpty())
executors.execute(() -> finish(run.id()));
else if (run.hasFailed() || run.sleepUntil().map(sleepUntil -> ! sleepUntil.isAfter(controller().clock().instant())).orElse(true))
@@ -93,7 +92,8 @@ public class JobRunner extends ControllerMaintainer {
private void finish(RunId id) {
try {
jobs.finish(id);
- controller().applications().deploymentTrigger().notifyOfCompletion(id.application());
+ if ( ! id.type().environment().isManuallyDeployed())
+ controller().applications().deploymentTrigger().notifyOfCompletion(id.application());
}
catch (TimeoutException e) {
// One of the steps are still being run — that's ok, we'll try to finish the run again later.
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
deleted file mode 100644
index 5acb21917eb..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainer.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.maintenance;
-
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.application.SystemApplication;
-import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
-
-import java.time.Duration;
-
-/**
- * This maintains {@link RoutingPolicy}'s for {@link SystemApplication}s. In contrast to regular applications, this
- * refreshes policies at an interval, not on deployment.
- *
- * @author mpolden
- */
-public class SystemRoutingPolicyMaintainer extends ControllerMaintainer {
-
- public SystemRoutingPolicyMaintainer(Controller controller, Duration interval) {
- super(controller, interval);
- }
-
- @Override
- protected double maintain() {
- for (var zone : controller().zoneRegistry().zones().reachable().ids()) {
- for (var application : SystemApplication.values()) {
- if (!application.hasEndpoint()) continue;
- DeploymentId deployment = new DeploymentId(application.id(), zone);
- controller().routing().of(deployment).configure(DeploymentSpec.empty);
- }
- }
- return 1.0;
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java
index 36a254f5c4b..49c819548fe 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.notification;
+import com.google.common.annotations.VisibleForTesting;
import com.yahoo.config.provision.Environment;
import com.yahoo.text.Text;
import com.yahoo.vespa.flags.FetchVector;
@@ -19,6 +20,7 @@ import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@@ -34,6 +36,9 @@ public class Notifier {
private static final Logger log = Logger.getLogger(Notifier.class.getName());
+ // Minimal url pattern matcher to detect hardcoded URLs in Notification messages
+ private static final Pattern urlPattern = Pattern.compile("https://[\\w\\d./]+");
+
public Notifier(CuratorDb curatorDb, ZoneRegistry zoneRegistry, Mailer mailer, FlagSource flagSource) {
this.curatorDb = Objects.requireNonNull(curatorDb);
this.mailer = Objects.requireNonNull(mailer);
@@ -103,16 +108,31 @@ public class Notifier {
}
}
- private Mail mailOf(FormattedNotification content, Collection<String> recipients) {
+ public Mail mailOf(FormattedNotification content, Collection<String> recipients) {
var notification = content.notification();
var subject = Text.format("[%s] %s Vespa Notification for %s", notification.level().toString().toUpperCase(), content.prettyType(), applicationIdSource(notification.source()));
var body = new StringBuilder();
- body.append(content.messagePrefix()).append("\n\n")
+ body.append(content.messagePrefix()).append("\n")
.append(notification.messages().stream().map(m -> " * " + m).collect(Collectors.joining("\n"))).append("\n")
.append("\n")
.append("Vespa Console link:\n")
.append(content.uri().toString());
- return new Mail(recipients, subject, body.toString());
+ var html = new StringBuilder();
+ html.append(content.messagePrefix()).append("<br>\n")
+ .append("<ul>\n")
+ .append(notification.messages().stream()
+ .map(Notifier::linkify)
+ .map(m -> "<li>" + m + "</li>")
+ .collect(Collectors.joining("<br>\n")))
+ .append("</ul>\n")
+ .append("<br>\n")
+ .append("<a href=\"" + content.uri() + "\">Vespa Console</a>");
+ return new Mail(recipients, subject, body.toString(), html.toString());
+ }
+
+ @VisibleForTesting
+ static String linkify(String text) {
+ return urlPattern.matcher(text).replaceAll((res) -> String.format("<a href=\"%s\">%s</a>", res.group(), res.group()));
}
private String applicationIdSource(NotificationSource source) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java
index 269864ad641..a4c7c50085c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java
@@ -13,7 +13,6 @@ import java.util.stream.Collectors;
/**
* (de)serializes tenant/bucket mappings for a zone
- * <p>
*
* @author andreer
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
index dd28978d948..fcc6d99aec2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
@@ -214,7 +213,7 @@ class RunSerializer {
run.sleepUntil().ifPresent(end -> runObject.setLong(sleepingUntilField, end.toEpochMilli()));
runObject.setString(statusField, valueOf(run.status()));
runObject.setLong(lastTestRecordField, run.lastTestLogEntry());
- runObject.setLong(lastVespaLogTimestampField, Instant.EPOCH.until(run.lastVespaLogTimestamp(), ChronoUnit.MICROS));
+ if (run.lastVespaLogTimestamp().isAfter(Instant.EPOCH)) runObject.setLong(lastVespaLogTimestampField, Instant.EPOCH.until(run.lastVespaLogTimestamp(), ChronoUnit.MICROS));
run.noNodesDownSince().ifPresent(noNodesDownSince -> runObject.setLong(noNodesDownSinceField, noNodesDownSince.toEpochMilli()));
run.convergenceSummary().ifPresent(convergenceSummary -> toSlime(convergenceSummary, runObject.setArray(convergenceSummaryField)));
run.testerCertificate().ifPresent(certificate -> runObject.setString(testerCertificateField, X509CertificateUtils.toPem(certificate)));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index 00e38abcba7..e7cf0c34511 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.BillingInf
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -77,6 +78,10 @@ public class TenantSerializer {
private static final String lastLoginInfoField = "lastLoginInfo";
private static final String secretStoresField = "secretStores";
private static final String archiveAccessRoleField = "archiveAccessRole";
+ private static final String archiveAccessField = "archiveAccess";
+ private static final String awsArchiveAccessRoleField = "awsArchiveAccessRole";
+ private static final String gcpArchiveAccessMemberField = "gcpArchiveAccessMember";
+
private static final String awsIdField = "awsId";
private static final String roleField = "role";
@@ -117,7 +122,13 @@ public class TenantSerializer {
toSlime(legacyBillingInfo, root.setObject(billingInfoField));
toSlime(tenant.info(), root);
toSlime(tenant.tenantSecretStores(), root);
- tenant.archiveAccessRole().ifPresent(role -> root.setString(archiveAccessRoleField, role));
+ toSlime(tenant.archiveAccess(), root);
+ }
+
+ private void toSlime(ArchiveAccess archiveAccess, Cursor root) {
+ Cursor object = root.setObject(archiveAccessField);
+ archiveAccess.awsRole().ifPresent(role -> object.setString(awsArchiveAccessRoleField, role));
+ archiveAccess.gcpMember().ifPresent(member -> object.setString(gcpArchiveAccessMemberField, member));
}
private void toSlime(DeletedTenant tenant, Cursor root) {
@@ -175,8 +186,8 @@ public class TenantSerializer {
BiMap<PublicKey, Principal> developerKeys = developerKeysFromSlime(tenantObject.field(pemDeveloperKeysField));
TenantInfo info = tenantInfoFromSlime(tenantObject.field(tenantInfoField));
List<TenantSecretStore> tenantSecretStores = secretStoresFromSlime(tenantObject.field(secretStoresField));
- Optional<String> archiveAccessRole = SlimeUtils.optionalString(tenantObject.field(archiveAccessRoleField));
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
+ ArchiveAccess archiveAccess = archiveAccessFromSlime(tenantObject);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
private DeletedTenant deletedTenantFrom(Inspector tenantObject) {
@@ -195,6 +206,22 @@ public class TenantSerializer {
return keys.build();
}
+ ArchiveAccess archiveAccessFromSlime(Inspector tenantObject) {
+ // TODO(enygaard, 2022-05-24): Remove when all tenants have been rewritten to use ArchiveAccess object
+ Optional<String> archiveAccessRole = SlimeUtils.optionalString(tenantObject.field(archiveAccessRoleField));
+ if (archiveAccessRole.isPresent()) {
+ return new ArchiveAccess().withAWSRole(archiveAccessRole.get());
+ }
+ Inspector object = tenantObject.field(archiveAccessField);
+ if (!object.valid()) {
+ return new ArchiveAccess();
+ }
+ Optional<String> awsArchiveAccessRole = SlimeUtils.optionalString(object.field(awsArchiveAccessRoleField));
+ Optional<String> gcpArchiveAccessMember = SlimeUtils.optionalString(object.field(gcpArchiveAccessMemberField));
+ return new ArchiveAccess()
+ .withAWSRole(awsArchiveAccessRole)
+ .withGCPMember(gcpArchiveAccessMember);
+ }
TenantInfo tenantInfoFromSlime(Inspector infoObject) {
if (!infoObject.valid()) return TenantInfo.empty();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
index 886dc27b404..c57133d8efd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java
@@ -29,7 +29,9 @@ public class ProxyResponse extends HttpResponse {
super(statusResponse);
this.contentType = contentType;
- String configServerPrefix = HttpURL.from(configServer).withPath(Path.empty()).asURI().toString();
+ // Configserver always serves from 4443, therefore all responses will have port 4443 in them,
+ // but the request URI (loadbalancer/VIP) is not always 4443
+ String configServerPrefix = HttpURL.from(configServer).withPort(4443).withPath(Path.empty()).asURI().toString();
String controllerRequestPrefix = controllerRequest.getControllerPrefixUri().toString();
bodyResponseRewritten = bodyResponse.replace(configServerPrefix, controllerRequestPrefix);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 91b76ac8d05..63f33540721 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -35,6 +35,7 @@ import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.ResourceResponse;
+import com.yahoo.restapi.RestApiException;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.Cursor;
@@ -108,6 +109,7 @@ import com.yahoo.vespa.hosted.controller.routing.rotation.RotationStatus;
import com.yahoo.vespa.hosted.controller.security.AccessControlRequests;
import com.yahoo.vespa.hosted.controller.security.Credentials;
import com.yahoo.vespa.hosted.controller.support.access.SupportAccess;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -122,9 +124,6 @@ import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.InternalServerErrorException;
-import javax.ws.rs.NotAuthorizedException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -150,6 +149,7 @@ import java.util.Optional;
import java.util.OptionalInt;
import java.util.Scanner;
import java.util.StringJoiner;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -208,10 +208,10 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
}
}
- catch (ForbiddenException e) {
+ catch (RestApiException.Forbidden e) {
return ErrorResponse.forbidden(Exceptions.toMessageString(e));
}
- catch (NotAuthorizedException e) {
+ catch (RestApiException.Unauthorized e) {
return ErrorResponse.unauthorized(Exceptions.toMessageString(e));
}
catch (NotExistsException e) {
@@ -245,6 +245,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}")) return tenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/access/request/operator")) return accessRequests(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/info")) return tenantInfo(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/info/profile")) return withCloudTenant(path.get("tenant"), this::tenantInfoProfile);
+ if (path.matches("/application/v4/tenant/{tenant}/info/billing")) return withCloudTenant(path.get("tenant"), this::tenantInfoBilling);
+ if (path.matches("/application/v4/tenant/{tenant}/info/contacts")) return withCloudTenant(path.get("tenant"), this::tenantInfoContacts);
if (path.matches("/application/v4/tenant/{tenant}/notifications")) return notifications(request, Optional.of(path.get("tenant")), false);
if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}/validate")) return validateSecretStore(path.get("tenant"), path.get("name"), request);
if (path.matches("/application/v4/tenant/{tenant}/application")) return applications(path.get("tenant"), Optional.empty(), request);
@@ -298,7 +301,12 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/access/approve/operator")) return approveAccessRequest(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/access/managed/operator")) return addManagedAccess(path.get("tenant"));
if (path.matches("/application/v4/tenant/{tenant}/info")) return updateTenantInfo(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return allowArchiveAccess(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/info/profile")) return withCloudTenant(path.get("tenant"), request, this::putTenantInfoProfile);
+ if (path.matches("/application/v4/tenant/{tenant}/info/billing")) return withCloudTenant(path.get("tenant"), request, this::putTenantInfoBilling);
+ if (path.matches("/application/v4/tenant/{tenant}/info/contacts")) return withCloudTenant(path.get("tenant"), request, this::putTenantInfoContacts);
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return allowAwsArchiveAccess(path.get("tenant"), request); // TODO(enygaard, 2022-05-25) Remove when no longer used by console
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/aws")) return allowAwsArchiveAccess(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/gcp")) return allowGcpArchiveAccess(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}")) return addSecretStore(path.get("tenant"), path.get("name"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
@@ -346,7 +354,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/access/managed/operator")) return removeManagedAccess(path.get("tenant"));
if (path.matches("/application/v4/tenant/{tenant}/key")) return removeDeveloperKey(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return removeArchiveAccess(path.get("tenant"));
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return removeAwsArchiveAccess(path.get("tenant")); // TODO(enygaard, 2022-05-25) Remove when no longer used by console
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/aws")) return removeAwsArchiveAccess(path.get("tenant"));
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access/gcp")) return removeGcpArchiveAccess(path.get("tenant"));
if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}")) return deleteSecretStore(path.get("tenant"), path.get("name"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deployment")) return removeAllProdDeployments(path.get("tenant"), path.get("application"));
@@ -500,6 +510,13 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
.orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist or does not support this"));
}
+ private HttpResponse withCloudTenant(String tenantName, Function<CloudTenant, SlimeJsonResponse> handler) {
+ return controller.tenants().get(TenantName.from(tenantName))
+ .filter(tenant -> tenant.type() == Tenant.Type.cloud)
+ .map(tenant -> handler.apply((CloudTenant) tenant))
+ .orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist or does not support this"));
+ }
+
private SlimeJsonResponse tenantInfo(TenantInfo info, HttpRequest request) {
Slime slime = new Slime();
Cursor infoCursor = slime.setObject();
@@ -517,6 +534,141 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
+ private SlimeJsonResponse tenantInfoProfile(CloudTenant cloudTenant) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ var info = cloudTenant.info();
+
+ if (!info.isEmpty()) {
+ var contact = root.setObject("contact");
+ contact.setString("name", info.contact().name());
+ contact.setString("email", info.contact().email());
+
+ var tenant = root.setObject("tenant");
+ tenant.setString("company", info.name());
+ tenant.setString("website", info.website());
+
+ toSlime(info.address(), root); // will create "address" on the parent
+ }
+
+ return new SlimeJsonResponse(slime);
+ }
+
+ private SlimeJsonResponse withCloudTenant(String tenantName, HttpRequest request, BiFunction<CloudTenant, Inspector, SlimeJsonResponse> handler) {
+ return controller.tenants().get(tenantName)
+ .map(tenant -> handler.apply((CloudTenant) tenant, toSlime(request.getData()).get()))
+ .orElseGet(() -> ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist or does not support this"));
+ }
+
+ private SlimeJsonResponse putTenantInfoProfile(CloudTenant cloudTenant, Inspector inspector) {
+ var info = cloudTenant.info();
+
+ var mergedContact = TenantContact.empty()
+ .withName(getString(inspector.field("contact").field("name"), info.contact().name()))
+ .withEmail(getString(inspector.field("contact").field("email"), info.contact().email()));
+
+ var mergedAddress = updateTenantInfoAddress(inspector.field("address"), info.address());
+
+ var mergedInfo = info
+ .withName(getString(inspector.field("tenant").field("name"), info.name()))
+ .withWebsite(getString(inspector.field("tenant").field("website"), info.website()))
+ .withContact(mergedContact)
+ .withAddress(mergedAddress);
+
+ validateMergedTenantInfo(mergedInfo);
+
+ controller.tenants().lockOrThrow(cloudTenant.name(), LockedTenant.Cloud.class, lockedTenant -> {
+ lockedTenant = lockedTenant.withInfo(mergedInfo);
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("Tenant info updated");
+ }
+
+ private SlimeJsonResponse tenantInfoBilling(CloudTenant cloudTenant) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ var info = cloudTenant.info();
+
+ if (!info.isEmpty()) {
+ var billingContact = info.billingContact();
+
+ var contact = root.setObject("contact");
+ contact.setString("name", billingContact.contact().name());
+ contact.setString("email", billingContact.contact().email());
+ contact.setString("phone", billingContact.contact().phone());
+
+ toSlime(billingContact.address(), root); // will create "address" on the parent
+ }
+
+ return new SlimeJsonResponse(slime);
+ }
+
+ private SlimeJsonResponse putTenantInfoBilling(CloudTenant cloudTenant, Inspector inspector) {
+ var info = cloudTenant.info();
+ var contact = info.billingContact().contact();
+ var address = info.billingContact().address();
+
+ var mergedContact = updateTenantInfoContact(inspector.field("contact"), contact);
+ var mergedAddress = updateTenantInfoAddress(inspector.field("address"), info.billingContact().address());
+
+ var mergedBilling = info.billingContact()
+ .withContact(mergedContact)
+ .withAddress(mergedAddress);
+
+ var mergedInfo = info.withBilling(mergedBilling);
+
+ // Store changes
+ controller.tenants().lockOrThrow(cloudTenant.name(), LockedTenant.Cloud.class, lockedTenant -> {
+ lockedTenant = lockedTenant.withInfo(mergedInfo);
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("Tenant info updated");
+ }
+
+ private SlimeJsonResponse tenantInfoContacts(CloudTenant cloudTenant) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ toSlime(cloudTenant.info().contacts(), root);
+ return new SlimeJsonResponse(slime);
+ }
+
+ private SlimeJsonResponse putTenantInfoContacts(CloudTenant cloudTenant, Inspector inspector) {
+ var mergedInfo = cloudTenant.info()
+ .withContacts(updateTenantInfoContacts(inspector.field("contacts"), cloudTenant.info().contacts()));
+
+ // Store changes
+ controller.tenants().lockOrThrow(cloudTenant.name(), LockedTenant.Cloud.class, lockedTenant -> {
+ lockedTenant = lockedTenant.withInfo(mergedInfo);
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("Tenant info updated");
+ }
+
+ private void validateMergedTenantInfo(TenantInfo mergedInfo) {
+ // Assert that we have a valid tenant info
+ if (mergedInfo.contact().name().isBlank()) {
+ throw new IllegalArgumentException("'contactName' cannot be empty");
+ }
+ if (mergedInfo.contact().email().isBlank()) {
+ throw new IllegalArgumentException("'contactEmail' cannot be empty");
+ }
+ if (! mergedInfo.contact().email().contains("@")) {
+ // email address validation is notoriously hard - we should probably just try to send a
+ // verification email to this address. checking for @ is a simple best-effort.
+ throw new IllegalArgumentException("'contactEmail' needs to be an email address");
+ }
+ if (! mergedInfo.website().isBlank()) {
+ try {
+ new URL(mergedInfo.website());
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("'website' needs to be a valid address");
+ }
+ }
+ }
+
private void toSlime(TenantAddress address, Cursor parentCursor) {
if (address.isEmpty()) return;
@@ -602,25 +754,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
.withBilling(updateTenantInfoBillingContact(insp.field("billingContact"), oldInfo.billingContact()))
.withContacts(updateTenantInfoContacts(insp.field("contacts"), oldInfo.contacts()));
- // Assert that we have a valid tenant info
- if (mergedInfo.contact().name().isBlank()) {
- throw new IllegalArgumentException("'contactName' cannot be empty");
- }
- if (mergedInfo.contact().email().isBlank()) {
- throw new IllegalArgumentException("'contactEmail' cannot be empty");
- }
- if (! mergedInfo.contact().email().contains("@")) {
- // email address validation is notoriously hard - we should probably just try to send a
- // verification email to this address. checking for @ is a simple best-effort.
- throw new IllegalArgumentException("'contactEmail' needs to be an email address");
- }
- if (! mergedInfo.website().isBlank()) {
- try {
- new URL(mergedInfo.website());
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("'website' needs to be a valid address");
- }
- }
+ validateMergedTenantInfo(mergedInfo);
// Store changes
controller.tenants().lockOrThrow(tenant.name(), LockedTenant.Cloud.class, lockedTenant -> {
@@ -1029,7 +1163,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
- private HttpResponse allowArchiveAccess(String tenantName, HttpRequest request) {
+ private HttpResponse allowAwsArchiveAccess(String tenantName, HttpRequest request) {
if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
@@ -1037,27 +1171,62 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
var role = mandatory("role", data).asString();
if (role.isBlank()) {
- return ErrorResponse.badRequest("Archive access role can't be whitespace only");
+ return ErrorResponse.badRequest("AWS archive access role can't be whitespace only");
}
controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccessRole(Optional.of(role));
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.withAWSRole(role));
controller.tenants().store(lockedTenant);
});
- return new MessageResponse("Archive access role set to '" + role + "' for tenant " + tenantName + ".");
+ return new MessageResponse("AWS archive access role set to '" + role + "' for tenant " + tenantName + ".");
}
- private HttpResponse removeArchiveAccess(String tenantName) {
+ private HttpResponse removeAwsArchiveAccess(String tenantName) {
if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccessRole(Optional.empty());
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.removeAWSRole());
controller.tenants().store(lockedTenant);
});
- return new MessageResponse("Archive access role removed for tenant " + tenantName + ".");
+ return new MessageResponse("AWS archive access role removed for tenant " + tenantName + ".");
+ }
+
+ private HttpResponse allowGcpArchiveAccess(String tenantName, HttpRequest request) {
+ if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
+ throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
+
+ var data = toSlime(request.getData()).get();
+ var member = mandatory("member", data).asString();
+
+ if (member.isBlank()) {
+ return ErrorResponse.badRequest("GCP archive access role can't be whitespace only");
+ }
+
+ controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.withGCPMember(member));
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("GCP archive access member set to '" + member + "' for tenant " + tenantName + ".");
+ }
+
+ private HttpResponse removeGcpArchiveAccess(String tenantName) {
+ if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
+ throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
+
+ controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
+ var access = lockedTenant.get().archiveAccess();
+ lockedTenant = lockedTenant.withArchiveAccess(access.removeGCPMember());
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("GCP archive access member removed for tenant " + tenantName + ".");
}
private HttpResponse patchApplication(String tenantName, String applicationName, HttpRequest request) {
@@ -2176,16 +2345,24 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
.flatMap(instance -> instance.productionDeployments().keySet().stream())
.map(zone -> new DeploymentId(prodInstanceId, zone))
.collect(Collectors.toCollection(HashSet::new));
- ZoneId testedZone = type.zone();
+
// If a production job is specified, the production deployment of the orchestrated instance is the relevant one,
// as user instances should not exist in prod.
+ ApplicationId toTest = type.isProduction() ? prodInstanceId : id;
if ( ! type.isProduction())
- deployments.add(new DeploymentId(id, testedZone));
+ deployments.add(new DeploymentId(toTest, type.zone()));
+
+ Deployment deployment = application.require(toTest.instance()).deployments().get(type.zone());
+ if (deployment == null)
+ throw new NotExistsException(toTest + " is not deployed in " + type.zone());
return new SlimeJsonResponse(testConfigSerializer.configSlime(id,
type,
false,
+ deployment.version(),
+ deployment.revision(),
+ deployment.at(),
controller.routing().readTestRunnerEndpointsOf(deployments),
controller.applications().reachableContentClustersByZone(deployments)));
}
@@ -2355,7 +2532,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
log.warning(String.format("Failed to get quota for tenant %s: %s", tenant.name(), Exceptions.toMessageString(e)));
}
- cloudTenant.archiveAccessRole().ifPresent(role -> object.setString("archiveAccessRole", role));
+ // TODO(enygaard, 2022-05-25) Remove when console is using new archive access structure
+ cloudTenant.archiveAccess().awsRole().ifPresent(role -> object.setString("archiveAccessRole", role));
+ toSlime(cloudTenant.archiveAccess(), object.setObject("archiveAccess"));
break;
}
@@ -2386,6 +2565,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
tenantMetaDataToSlime(tenant, applications, object.setObject("metaData"));
}
+ private void toSlime(ArchiveAccess archiveAccess, Cursor object) {
+ archiveAccess.awsRole().ifPresent(role -> object.setString("awsRole", role));
+ archiveAccess.gcpMember().ifPresent(member -> object.setString("gcpMember", member));
+ }
+
private void toSlime(Quota quota, QuotaUsage usage, Cursor object) {
quota.budget().ifPresentOrElse(
budget -> object.setDouble("budget", budget.doubleValue()),
@@ -2531,7 +2715,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
private static Principal requireUserPrincipal(HttpRequest request) {
Principal principal = request.getJDiscRequest().getUserPrincipal();
- if (principal == null) throw new InternalServerErrorException("Expected a user principal");
+ if (principal == null) throw new RestApiException.InternalServerError("Expected a user principal");
return principal;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index 80425609aa6..8c49df43c30 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -112,8 +112,7 @@ class JobControllerApiHandlerHelper {
Slime slime = new Slime();
Cursor detailsObject = slime.setObject();
- Run run = jobController.run(runId)
- .orElseThrow(() -> new IllegalStateException("Unknown run '" + runId + "'"));
+ Run run = jobController.run(runId);
detailsObject.setBool("active", ! run.hasEnded());
detailsObject.setString("status", nameOf(run.status()));
try {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
index 206303adc80..d45e69f781b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
@@ -24,6 +24,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMetho
import com.yahoo.vespa.hosted.controller.api.integration.billing.InstrumentOwner;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PaymentInstrument;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistry;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
@@ -56,11 +57,13 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler {
private final BillingController billingController;
private final ApplicationController applicationController;
private final TenantController tenantController;
+ private final PlanRegistry planRegistry;
public BillingApiHandler(Executor executor,
Controller controller) {
super(executor);
this.billingController = controller.serviceRegistry().billingController();
+ this.planRegistry = controller.serviceRegistry().planRegistry();
this.applicationController = controller.applications();
this.tenantController = controller.tenants();
}
@@ -103,6 +106,7 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler {
if (path.matches("/billing/v1/billing")) return getBillingAllTenants(request.getProperty("until"));
if (path.matches("/billing/v1/invoice/export")) return getAllBills();
if (path.matches("/billing/v1/invoice/tenant/{tenant}/line-item")) return getLineItems(path.get("tenant"));
+ if (path.matches("/billing/v1/plans")) return getPlans();
return ErrorResponse.notFoundError("Nothing at " + path);
}
@@ -295,6 +299,18 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler {
}
}
+ private HttpResponse getPlans() {
+ var slime = new Slime();
+ var root = slime.setObject();
+ var plans = root.setArray("plans");
+ for (var plan : planRegistry.all()) {
+ var p = plans.addObject();
+ p.setString("id", plan.id().value());
+ p.setString("name", plan.displayName());
+ }
+ return new SlimeJsonResponse(slime);
+ }
+
private HttpResponse getLineItems(String tenant) {
var slimeResponse = new Slime();
var root = slimeResponse.setObject();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java
index c72d8ceb089..a3b77e22f1d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java
@@ -9,6 +9,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.Path;
+import com.yahoo.restapi.RestApiException;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
@@ -24,7 +25,6 @@ import com.yahoo.vespa.hosted.controller.maintenance.ChangeManagementAssessor;
import com.yahoo.vespa.hosted.controller.persistence.ChangeRequestSerializer;
import com.yahoo.yolean.Exceptions;
-import javax.ws.rs.BadRequestException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -96,13 +96,13 @@ public class ChangeManagementApiHandler extends AuditLoggingRequestHandler {
try {
return SlimeUtils.jsonToSlime(request.getData().readAllBytes()).get();
} catch (IOException e) {
- throw new BadRequestException("Failed to parse request body");
+ throw new RestApiException.BadRequest("Failed to parse request body");
}
}
private static Inspector getInspectorFieldOrThrow(Inspector inspector, String field) {
if (!inspector.field(field).valid())
- throw new BadRequestException("Field " + field + " cannot be null");
+ throw new RestApiException.BadRequest("Field " + field + " cannot be null");
return inspector.field(field);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
index 0c0680d9166..25ac90ac0ea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
@@ -12,6 +12,7 @@ import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.ResourceResponse;
+import com.yahoo.restapi.RestApiException;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.SlimeUtils;
@@ -27,7 +28,6 @@ import com.yahoo.vespa.hosted.controller.support.access.SupportAccess;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence;
import com.yahoo.yolean.Exceptions;
-import javax.ws.rs.InternalServerErrorException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
@@ -203,7 +203,7 @@ public class ControllerApiHandler extends AuditLoggingRequestHandler {
private static Principal requireUserPrincipal(HttpRequest request) {
Principal principal = request.getJDiscRequest().getUserPrincipal();
- if (principal == null) throw new InternalServerErrorException("Expected a user principal");
+ if (principal == null) throw new RestApiException.InternalServerError("Expected a user principal");
return principal;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
index 4aefb9ea7c2..e06c2c3ccbd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
@@ -139,8 +139,7 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase {
if ( identity.getDomain().equals(SCREWDRIVER_DOMAIN)
&& application.isPresent()
- && tenant.isPresent()
- && ! tenant.get().name().value().equals("sandbox"))
+ && tenant.isPresent())
futures.add(executor.submit(() -> {
if ( tenant.get().type() == Tenant.Type.athenz
&& hasDeployerAccess(identity, ((AthenzTenant) tenant.get()).domain(), application.get(), zone))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
index 88fd3a58d23..853739ee9c3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
@@ -19,6 +19,7 @@ import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
+import com.yahoo.slime.Type;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler;
import com.yahoo.vespa.hosted.controller.versions.OsVersionTarget;
@@ -32,6 +33,7 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
+import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -71,7 +73,7 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
private HttpResponse patch(HttpRequest request) {
Path path = new Path(request.getUri());
- if (path.matches("/os/v1/")) return new SlimeJsonResponse(setOsVersion(request));
+ if (path.matches("/os/v1/")) return setOsVersion(request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
@@ -130,36 +132,20 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
return zones.zones().stream().map(ZoneApi::getId).collect(Collectors.toList());
}
- private Slime setOsVersion(HttpRequest request) {
+ private HttpResponse setOsVersion(HttpRequest request) {
Slime requestData = toSlime(request.getData());
Inspector root = requestData.get();
- Inspector versionField = root.field("version");
- Inspector cloudField = root.field("cloud");
- Inspector upgradeBudgetField = root.field("upgradeBudget");
- boolean force = root.field("force").asBool();
- if (!versionField.valid() || !cloudField.valid() || !upgradeBudgetField.valid()) {
- throw new IllegalArgumentException("Fields 'version', 'cloud' and 'upgradeBudget' are required");
- }
-
- CloudName cloud = CloudName.from(cloudField.asString());
- Version target;
- try {
- target = Version.fromString(versionField.asString());
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Invalid version '" + versionField.asString() + "'", e);
- }
- Duration upgradeBudget;
- try {
- upgradeBudget = Duration.parse(upgradeBudgetField.asString());
- } catch (Exception e) {
- throw new IllegalArgumentException("Invalid duration '" + upgradeBudgetField.asString() + "'", e);
+ CloudName cloud = parseStringField("cloud", root, CloudName::from);
+ if (requireField("version", root).type() == Type.NIX) {
+ controller.cancelOsUpgradeIn(cloud);
+ return new MessageResponse("Cleared target OS version for cloud '" + cloud.value() + "'");
}
+ Version target = parseStringField("version", root, Version::fromString);
+ Duration upgradeBudget = parseStringField("upgradeBudget", root, Duration::parse);
+ boolean force = root.field("force").asBool();
controller.upgradeOsIn(cloud, target, upgradeBudget, force);
- Slime response = new Slime();
- Cursor cursor = response.setObject();
- cursor.setString("message", "Set target OS version for cloud '" + cloud.value() + "' to " +
- target.toFullString() + " with upgrade budget " + upgradeBudget);
- return response;
+ return new MessageResponse("Set target OS version for cloud '" + cloud.value() + "' to " +
+ target.toFullString() + " with upgrade budget " + upgradeBudget);
}
private Slime osVersions() {
@@ -196,4 +182,19 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
}
}
+ private static <T> T parseStringField(String name, Inspector root, Function<String, T> parser) {
+ String fieldValue = requireField(name, root).asString();
+ try {
+ return parser.apply(fieldValue);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid " + name + " '" + fieldValue + "'", e);
+ }
+ }
+
+ private static Inspector requireField(String name, Inspector root) {
+ Inspector field = root.field(name);
+ if (!field.valid()) throw new IllegalArgumentException("Field '" + name + "' is required");
+ return field;
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
index 1ccb3205816..fa6741ec8b8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
@@ -11,7 +11,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.Endpoint.Port;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
-import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import java.util.List;
import java.util.Objects;
@@ -87,11 +86,6 @@ public class RoutingPolicy {
/** Returns the zone endpoints of this */
public List<Endpoint> zoneEndpointsIn(SystemName system, RoutingMethod routingMethod, ZoneRegistry zoneRegistry) {
- Optional<Endpoint> infraEndpoint = SystemApplication.matching(id.owner())
- .flatMap(app -> app.endpointIn(id.zone(), zoneRegistry));
- if (infraEndpoint.isPresent()) {
- return List.of(infraEndpoint.get());
- }
DeploymentId deployment = new DeploymentId(id.owner(), id.zone());
return List.of(endpoint(routingMethod).target(id.cluster(), deployment).in(system));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
index 87691d2927a..81e9b1972b9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.component.annotation.Inject;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.restapi.RestApiException;
import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
@@ -22,7 +23,6 @@ import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
-import javax.ws.rs.ForbiddenException;
import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;
@@ -76,7 +76,7 @@ public class CloudAccessControl implements AccessControl {
var trialTenants = billingController.tenantsWithPlan(tenantNames, trialPlanId).size();
if (maxTrialTenants.value() >= 0 && maxTrialTenants.value() <= trialTenants) {
- throw new ForbiddenException("Too many tenants with trial plans, please contact the Vespa support team");
+ throw new RestApiException.Forbidden("Too many tenants with trial plans, please contact the Vespa support team");
}
}
@@ -84,11 +84,11 @@ public class CloudAccessControl implements AccessControl {
if (allowedByPrivilegedRole(auth0Credentials)) return;
if (!allowedByFeatureFlag(auth0Credentials)) {
- throw new ForbiddenException("You are not currently permitted to create tenants. Please contact the Vespa team to request access.");
+ throw new RestApiException.Forbidden("You are not currently permitted to create tenants. Please contact the Vespa team to request access.");
}
if(administeredTenants(auth0Credentials) >= 3) {
- throw new ForbiddenException("You are already administering 3 tenants. If you need more, please contact the Vespa team.");
+ throw new RestApiException.Forbidden("You are already administering 3 tenants. If you need more, please contact the Vespa team.");
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/DeploymentStatistics.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/DeploymentStatistics.java
index 179a64d9491..e3077cb232f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/DeploymentStatistics.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/DeploymentStatistics.java
@@ -18,25 +18,29 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Comparator.naturalOrder;
import static java.util.function.Function.identity;
/**
- * Statistics about deployments on a platform version. This is immutable.
+ * Statistics about deployments on a platform version.
+ *
+ * @param version the version these statistics are for
+ * @param failingUpgrades the runs on the version of this, for currently failing instances, where the failure may be because of the upgrade
+ * @param otherFailing all other failing runs on the version of this, for currently failing instances
+ * @param productionSuccesses the production runs where the last success was on the version of this
+ * @param runningUpgrade the currently running runs on the version of this, where an upgrade is attempted
+ * @param otherRunning all other currently running runs on the version on this
*
* @author jonmv
*/
-public class DeploymentStatistics {
-
- private final Version version;
- private final List<Run> failingUpgrades;
- private final List<Run> otherFailing;
- private final List<Run> productionSuccesses;
- private final List<Run> runningUpgrade;
- private final List<Run> otherRunning;
+public record DeploymentStatistics(Version version,
+ List<Run> failingUpgrades,
+ List<Run> otherFailing,
+ List<Run> productionSuccesses,
+ List<Run> runningUpgrade,
+ List<Run> otherRunning) {
public DeploymentStatistics(Version version, List<Run> failingUpgrades, List<Run> otherFailing,
List<Run> productionSuccesses, List<Run> runningUpgrade, List<Run> otherRunning) {
@@ -48,26 +52,7 @@ public class DeploymentStatistics {
this.otherRunning = List.copyOf(otherRunning);
}
- /** Returns the version these statistics are for. */
- public Version version() { return version; }
-
- /** Returns the runs on the version of this, for currently failing instances, where the failure may be because of the upgrade. */
- public List<Run> failingUpgrades() { return failingUpgrades; }
-
- /** Returns all other failing runs on the version of this, for currently failing instances. */
- public List<Run> otherFailing() { return otherFailing; }
-
- /** Returns the production runs where the last success was on the version of this. */
- public List<Run> productionSuccesses() { return productionSuccesses; }
-
- /** Returns the currently running runs on the version of this, where an upgrade is attempted. */
- public List<Run> runningUpgrade() { return runningUpgrade; }
-
- /** Returns all other currently running runs on the version on this. */
- public List<Run> otherRunning() { return otherRunning; }
-
public static List<DeploymentStatistics> compute(Collection<Version> infrastructureVersions, DeploymentStatusList statuses) {
-
Set<Version> allVersions = new HashSet<>(infrastructureVersions);
Map<Version, List<Run>> failingUpgrade = new HashMap<>();
Map<Version, List<Run>> otherFailing = new HashMap<>();
@@ -154,8 +139,7 @@ public class DeploymentStatistics {
productionSuccesses.getOrDefault(version, List.of()),
runningUpgrade.getOrDefault(version, List.of()),
otherRunning.getOrDefault(version, List.of())))
- .collect(Collectors.toUnmodifiableList());
-
+ .toList();
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java
index 69773597f37..04f5d9866f8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java
@@ -13,45 +13,20 @@ import java.util.Optional;
/**
* Version information for a node allocated to a {@link com.yahoo.vespa.hosted.controller.application.SystemApplication}.
*
- * This is immutable.
- *
* @author mpolden
*/
-public class NodeVersion {
-
- private final HostName hostname;
- private final ZoneId zone;
- private final Version currentVersion;
- private final Version wantedVersion;
- private final Optional<Instant> suspendedAt;
-
- public NodeVersion(HostName hostname, ZoneId zone, Version currentVersion, Version wantedVersion,
- Optional<Instant> suspendedAt) {
- this.hostname = Objects.requireNonNull(hostname, "hostname must be non-null");
- this.zone = Objects.requireNonNull(zone, "zone must be non-null");
- this.currentVersion = Objects.requireNonNull(currentVersion, "version must be non-null");
- this.wantedVersion = Objects.requireNonNull(wantedVersion, "wantedVersion must be non-null");
- this.suspendedAt = Objects.requireNonNull(suspendedAt, "suspendedAt must be non-null");
- }
-
- /** Hostname of this */
- public HostName hostname() {
- return hostname;
- }
-
- /** Zone of this */
- public ZoneId zone() {
- return zone;
- }
-
- /** Current version of this */
- public Version currentVersion() {
- return currentVersion;
- }
+public record NodeVersion(HostName hostname,
+ ZoneId zone,
+ Version currentVersion,
+ Version wantedVersion,
+ Optional<Instant> suspendedAt) {
- /** Wanted version of this */
- public Version wantedVersion() {
- return wantedVersion;
+ public NodeVersion {
+ Objects.requireNonNull(hostname, "hostname must be non-null");
+ Objects.requireNonNull(zone, "zone must be non-null");
+ Objects.requireNonNull(currentVersion, "version must be non-null");
+ Objects.requireNonNull(wantedVersion, "wantedVersion must be non-null");
+ Objects.requireNonNull(suspendedAt, "suspendedAt must be non-null");
}
/** Returns the duration of the change in this, measured relative to instant */
@@ -61,33 +36,11 @@ public class NodeVersion {
return Duration.between(suspendedAt.get(), instant).abs();
}
- /** The most recent time the node referenced in this suspended. This is empty if the node is not suspended. */
- public Optional<Instant> suspendedAt() {
- return suspendedAt;
- }
-
@Override
public String toString() {
return hostname + ": " + currentVersion + " -> " + wantedVersion + " [zone=" + zone + ", suspendedAt=" + suspendedAt.map(Instant::toString).orElse("<not suspended>") + "]";
}
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- NodeVersion that = (NodeVersion) o;
- return hostname.equals(that.hostname) &&
- zone.equals(that.zone) &&
- currentVersion.equals(that.currentVersion) &&
- wantedVersion.equals(that.wantedVersion) &&
- suspendedAt.equals(that.suspendedAt);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(hostname, zone, currentVersion, wantedVersion, suspendedAt);
- }
-
/** Returns whether this is upgrading */
private boolean upgrading() {
return currentVersion.isBefore(wantedVersion);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersion.java
index c240a7ddf35..30a88733ed3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersion.java
@@ -12,41 +12,14 @@ import java.util.Objects;
*
* @author mpolden
*/
-public class OsVersion implements Comparable<OsVersion> {
+public record OsVersion(Version version, CloudName cloud) implements Comparable<OsVersion> {
private static final Comparator<OsVersion> comparator = Comparator.comparing(OsVersion::cloud)
.thenComparing(OsVersion::version);
- private final Version version;
- private final CloudName cloud;
-
- public OsVersion(Version version, CloudName cloud) {
- this.version = Objects.requireNonNull(version, "version must be non-null");
- this.cloud = Objects.requireNonNull(cloud, "cloud must be non-null");
- }
-
- /** The version number of this */
- public Version version() {
- return version;
- }
-
- /** The cloud where this OS version is used */
- public CloudName cloud() {
- return cloud;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- OsVersion osVersion = (OsVersion) o;
- return Objects.equals(version, osVersion.version) &&
- Objects.equals(cloud, osVersion.cloud);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(version, cloud);
+ public OsVersion {
+ Objects.requireNonNull(version, "version must be non-null");
+ Objects.requireNonNull(cloud, "cloud must be non-null");
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
index fc7fbe45767..8ee891ae8a6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
@@ -26,22 +26,15 @@ import java.util.stream.Collectors;
*
* @author mpolden
*/
-public class OsVersionStatus {
+public record OsVersionStatus(Map<OsVersion, List<NodeVersion>> versions) {
public static final OsVersionStatus empty = new OsVersionStatus(ImmutableMap.of());
- private final Map<OsVersion, List<NodeVersion>> versions;
-
/** Public for serialization purpose only. Use {@link OsVersionStatus#compute(Controller)} for an up-to-date status */
public OsVersionStatus(Map<OsVersion, List<NodeVersion>> versions) {
this.versions = ImmutableMap.copyOf(Objects.requireNonNull(versions, "versions must be non-null"));
}
- /** All known OS versions and their nodes */
- public Map<OsVersion, List<NodeVersion>> versions() {
- return versions;
- }
-
/** Returns nodes eligible for OS upgrades that exist in given cloud */
public List<NodeVersion> nodesIn(CloudName cloud) {
return versions.entrySet().stream()
@@ -91,7 +84,7 @@ public class OsVersionStatus {
return controller.zoneRegistry().osUpgradePolicies().stream()
.flatMap(upgradePolicy -> upgradePolicy.steps().stream())
.flatMap(Collection::stream)
- .collect(Collectors.toUnmodifiableList());
+ .toList();
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionTarget.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionTarget.java
index 1c27058a6ef..0a13244ce5e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionTarget.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionTarget.java
@@ -12,54 +12,17 @@ import java.util.Objects;
*
* @author mpolden
*/
-public class OsVersionTarget implements VersionTarget, Comparable<OsVersionTarget> {
-
- // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one
- // (and rewrite all nodes on startup), changes to the serialized format must be made
- // such that what is serialized on version N+1 can be read by version N:
- // - ADDING FIELDS: Always ok
- // - REMOVING FIELDS: Stop reading the field first. Stop writing it on a later version.
- // - CHANGING THE FORMAT OF A FIELD: Don't do it bro.
-
- private final OsVersion osVersion;
- private final Duration upgradeBudget;
- private final Instant scheduledAt;
-
- public OsVersionTarget(OsVersion osVersion, Duration upgradeBudget, Instant scheduledAt) {
- this.osVersion = Objects.requireNonNull(osVersion);
- this.upgradeBudget = Objects.requireNonNull(upgradeBudget);
- this.scheduledAt = Objects.requireNonNull(scheduledAt);
+public record OsVersionTarget(OsVersion osVersion,
+ Duration upgradeBudget,
+ Instant scheduledAt) implements VersionTarget, Comparable<OsVersionTarget> {
+
+ public OsVersionTarget {
+ Objects.requireNonNull(osVersion);
+ Objects.requireNonNull(upgradeBudget);
+ Objects.requireNonNull(scheduledAt);
if (upgradeBudget.isNegative()) throw new IllegalArgumentException("upgradeBudget cannot be negative");
}
- /** The OS version contained in this target */
- public OsVersion osVersion() {
- return osVersion;
- }
-
- /** The total time budget across all zones for applying target, if any */
- public Duration upgradeBudget() {
- return upgradeBudget;
- }
-
- /** Returns when this target was scheduled */
- public Instant scheduledAt() {
- return scheduledAt;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- OsVersionTarget that = (OsVersionTarget) o;
- return osVersion.equals(that.osVersion) && upgradeBudget.equals(that.upgradeBudget) && scheduledAt.equals(that.scheduledAt);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(osVersion, upgradeBudget, scheduledAt);
- }
-
@Override
public int compareTo(OsVersionTarget o) {
return osVersion.compareTo(o.osVersion);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
index 117abd52193..e937e8af60d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
@@ -27,16 +27,12 @@ import java.util.stream.Collectors;
* The versions in use are the set of all versions running in current applications, versions
* of config servers in all zones, and the version of this controller itself.
*
- * This is immutable.
- *
* @author bratseth
* @author mpolden
*/
-public class VersionStatus {
+public record VersionStatus(List<VespaVersion> versions) {
private static final Logger log = Logger.getLogger(VersionStatus.class.getName());
-
- private final List<VespaVersion> versions;
/** Create a version status. DO NOT USE: Public for testing and serialization only */
public VersionStatus(List<VespaVersion> versions) {
@@ -172,7 +168,7 @@ public class VersionStatus {
var nodes = controller.serviceRegistry().configServer().nodeRepository()
.list(zone.getId(), NodeFilter.all().applications(application.id())).stream()
.filter(SystemUpgrader::eligibleForUpgrade)
- .collect(Collectors.toList());
+ .toList();
if (nodes.isEmpty()) continue;
boolean configConverged = application.configConvergedIn(zone.getId(), controller, Optional.empty());
if (!configConverged) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
index 162890ff74d..7f33f612cd0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
@@ -16,35 +16,17 @@ import static com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
/**
* Information about a particular Vespa version.
* VespaVersions are identified by their version number and ordered by increasing version numbers.
- *
- * This is immutable.
*
* @author bratseth
*/
-public class VespaVersion implements Comparable<VespaVersion> {
-
- private final Version version;
- private final String releaseCommit;
- private final Instant committedAt;
- private final boolean isControllerVersion;
- private final boolean isSystemVersion;
- private final boolean isReleased;
- private final List<NodeVersion> nodeVersions;
- private final Confidence confidence;
-
- public VespaVersion(Version version, String releaseCommit, Instant committedAt,
- boolean isControllerVersion, boolean isSystemVersion, boolean isReleased,
- List<NodeVersion> nodeVersions,
- Confidence confidence) {
- this.version = version;
- this.releaseCommit = releaseCommit;
- this.committedAt = committedAt;
- this.isControllerVersion = isControllerVersion;
- this.isSystemVersion = isSystemVersion;
- this.isReleased = isReleased;
- this.nodeVersions = nodeVersions;
- this.confidence = confidence;
- }
+public record VespaVersion(Version version,
+ String releaseCommit,
+ Instant committedAt,
+ boolean isControllerVersion,
+ boolean isSystemVersion,
+ boolean isReleased,
+ List<NodeVersion> nodeVersions,
+ Confidence confidence) implements Comparable<VespaVersion> {
public static Confidence confidenceFrom(DeploymentStatistics statistics, Controller controller) {
InstanceList all = InstanceList.from(controller.jobController().deploymentStatuses(ApplicationList.from(controller.applications().asList())
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersionTarget.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersionTarget.java
index 78d6d0ebf29..26890cfd8f8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersionTarget.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersionTarget.java
@@ -10,24 +10,10 @@ import java.util.Objects;
*
* @author mpolden
*/
-public class VespaVersionTarget implements VersionTarget {
+public record VespaVersionTarget(Version version, boolean downgrade) implements VersionTarget {
- private final Version version;
- private final boolean downgrade;
-
- public VespaVersionTarget(Version version, boolean downgrade) {
- this.version = Objects.requireNonNull(version);
- this.downgrade = downgrade;
- }
-
- @Override
- public Version version() {
- return version;
- }
-
- @Override
- public boolean downgrade() {
- return downgrade;
+ public VespaVersionTarget {
+ Objects.requireNonNull(version);
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index f4f50de59d7..53557bafcb0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -811,14 +811,6 @@ public class ControllerTest {
}
@Test
- public void testDeployApplicationPackageWithApplicationDir() {
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .region("us-west-1")
- .build(true);
- tester.newDeploymentContext().submit(applicationPackage);
- }
-
- @Test
public void testDeployApplicationWithWarnings() {
var context = tester.newDeploymentContext();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
@@ -1195,4 +1187,20 @@ public class ControllerTest {
assertEquals(cloudAccount, tester.controllerTester().configServer().cloudAccount(context.deploymentIdIn(zone)).get().value());
}
+ @Test
+ public void testSubmitWithElementDeprecatedOnPreviousMajor() {
+ DeploymentContext context = tester.newDeploymentContext();
+ var applicationPackage = new ApplicationPackageBuilder()
+ .compileVersion(Version.fromString("8.1"))
+ .region("us-west-1")
+ .globalServiceId("qrs")
+ .build();
+ try {
+ context.submit(applicationPackage).deploy();
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ assertTrue(e.getMessage().contains("Element 'prod' contains attribute 'global-service-id' deprecated since major version 7"));
+ }
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
index 35cca0e1f1f..81fc610d1f6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
@@ -241,9 +241,15 @@ public class EndpointTest {
var cluster = ClusterSpec.Id.from("default");
var prodZone = ZoneId.from("prod", "us-north-2");
Map<String, Endpoint> tests = Map.of(
- "https://a1.t1.us-north-1.w.vespa-app.cloud/",
+ "https://a1.t1.aws-us-north-1.w.vespa-app.cloud/",
Endpoint.of(instance1)
- .targetRegion(cluster, ZoneId.from("prod", "us-north-1a"))
+ .targetRegion(cluster, ZoneId.from("prod", "aws-us-north-1a"))
+ .routingMethod(RoutingMethod.exclusive)
+ .on(Port.tls())
+ .in(SystemName.Public),
+ "https://a1.t1.gcp-us-south1.w.vespa-app.cloud/",
+ Endpoint.of(instance1)
+ .targetRegion(cluster, ZoneId.from("prod", "gcp-us-south1-c"))
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.Public),
@@ -261,14 +267,13 @@ public class EndpointTest {
.in(SystemName.Public)
);
tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString()));
- Endpoint endpoint = Endpoint.of(instance1)
- .targetRegion(cluster, ZoneId.from("prod", "us-north-1a"))
- .routingMethod(RoutingMethod.exclusive)
- .on(Port.tls())
- .in(SystemName.main);
+
+ assertEquals("Availability zone is removed from region",
+ "aws-us-north-1",
+ tests.get("https://a1.t1.aws-us-north-1.w.vespa-app.cloud/").targets().get(0).deployment().zoneId().region().value());
assertEquals("Availability zone is removed from region",
- "us-north-1",
- endpoint.targets().get(0).deployment().zoneId().region().value());
+ "gcp-us-south1",
+ tests.get("https://a1.t1.gcp-us-south1.w.vespa-app.cloud/").targets().get(0).deployment().zoneId().region().value());
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
index 99e22302c73..0458f77fc00 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
@@ -81,23 +81,6 @@ public class ApplicationPackageTest {
}
@Test
- public void testMetaDataWithLegacyApplicationDirectory() {
- byte[] zip = ApplicationPackage.filesZip(Map.of("application/deployment.xml", deploymentXml.getBytes(UTF_8),
- "application/services.xml", servicesXml.getBytes(UTF_8),
- "application/jdisc.xml", jdiscXml.getBytes(UTF_8),
- "application/content/content.xml", contentXml.getBytes(UTF_8),
- "application/content/nodes.xml", nodesXml.getBytes(UTF_8),
- "application/gurba", "gurba".getBytes(UTF_8)));
-
- assertEquals(Map.of("deployment.xml", deploymentXml,
- "services.xml", servicesXml,
- "jdisc.xml", jdiscXml,
- "content/content.xml", contentXml,
- "content/nodes.xml", nodesXml),
- unzip(new ApplicationPackage(zip, false).metaDataZip()));
- }
-
- @Test
public void testMetaDataWithMissingFiles() {
byte[] zip = ApplicationPackage.filesZip(Map.of("services.xml", servicesXml.getBytes(UTF_8)));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java
index 1a052b6a578..4c3d76b1b17 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java
@@ -32,7 +32,7 @@ public class CuratorArchiveBucketDbTest {
assertEquals(Optional.of(URI.create("s3://existingBucket/default/")), bucketDb.archiveUriFor(ZoneId.defaultId(), TenantName.defaultName(), true));
// Assigns to existing bucket while there is space
- IntStream.range(0, 29).forEach(i ->
+ IntStream.range(0, 4).forEach(i ->
assertEquals(
Optional.of(URI.create("s3://existingBucket/tenant" + i + "/")), bucketDb
.archiveUriFor(ZoneId.defaultId(), TenantName.from("tenant" + i), true)));
@@ -47,7 +47,7 @@ public class CuratorArchiveBucketDbTest {
assertEquals(Optional.empty(), bucketDb.archiveUriFor(ZoneId.from("prod.us-east-3"), TenantName.from("newTenant"), false));
// Lists all buckets by zone
- Set<TenantName> existingBucketTenants = Streams.concat(Stream.of(TenantName.defaultName()), IntStream.range(0, 29).mapToObj(i -> TenantName.from("tenant" + i))).collect(Collectors.toUnmodifiableSet());
+ Set<TenantName> existingBucketTenants = Streams.concat(Stream.of(TenantName.defaultName()), IntStream.range(0, 4).mapToObj(i -> TenantName.from("tenant" + i))).collect(Collectors.toUnmodifiableSet());
assertEquals(
Set.of(
new ArchiveBucket("existingBucket", "keyArn").withTenants(existingBucketTenants),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
index 5d1a677bf51..ea8f4db0346 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
@@ -147,7 +147,10 @@ public class ApplicationPackageBuilder {
}
public ApplicationPackageBuilder region(String regionName) {
- return region(RegionName.from(regionName), true);
+ prodBody.append(" <region>")
+ .append(regionName)
+ .append("</region>\n");
+ return this;
}
public ApplicationPackageBuilder region(RegionName regionName, boolean active) {
@@ -334,23 +337,15 @@ public class ApplicationPackageBuilder {
}
public ApplicationPackage build() {
- return build(false);
- }
-
- public ApplicationPackage build(boolean useApplicationDir) {
- String dir = "";
- if (useApplicationDir) {
- dir = "application/";
- }
ByteArrayOutputStream zip = new ByteArrayOutputStream();
try (ZipOutputStream out = new ZipOutputStream(zip)) {
out.setLevel(Deflater.NO_COMPRESSION); // This is for testing purposes so we skip compression for performance
- writeZipEntry(out, dir + "deployment.xml", deploymentSpec());
- writeZipEntry(out, dir + "validation-overrides.xml", validationOverrides());
- writeZipEntry(out, dir + "search-definitions/test.sd", searchDefinition());
- writeZipEntry(out, dir + "build-meta.json", buildMeta(compileVersion));
+ writeZipEntry(out, "deployment.xml", deploymentSpec());
+ writeZipEntry(out, "validation-overrides.xml", validationOverrides());
+ writeZipEntry(out, "schemas/test.sd", searchDefinition());
+ writeZipEntry(out, "build-meta.json", buildMeta(compileVersion));
if (!trustedCertificates.isEmpty()) {
- writeZipEntry(out, dir + "security/clients.pem", X509CertificateUtils.toPem(trustedCertificates).getBytes(UTF_8));
+ writeZipEntry(out, "security/clients.pem", X509CertificateUtils.toPem(trustedCertificates).getBytes(UTF_8));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
index ae4b6259da1..b3db4a8b845 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
@@ -355,7 +355,7 @@ public class DeploymentContext {
var job = jobId(type);
RunId id = currentRun(job).id();
runner.advance(currentRun(job));
- Run run = jobs.run(id).get();
+ Run run = jobs.run(id);
assertTrue(run.hasFailed());
assertTrue(run.hasEnded());
if (messagePart.isPresent()) {
@@ -441,11 +441,10 @@ public class DeploymentContext {
RunId id = currentRun(job).id();
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endTests));
tester.cloud().set(noTests ? Status.NO_TESTS : Status.FAILURE);
runner.advance(currentRun(job));
- assertTrue(jobs.run(id).get().hasEnded());
- assertEquals(noTests, jobs.run(id).get().hasSucceeded());
+ assertTrue(jobs.run(id).hasEnded());
assertTrue(configServer().nodeRepository().list(job.type().zone(), NodeFilter.all().applications(TesterId.of(instanceId).id())).isEmpty());
return this;
@@ -482,7 +481,7 @@ public class DeploymentContext {
assertSame(RunStatus.aborted, run.status());
assertFalse(run.hasEnded());
runner.advance(run);
- assertTrue(jobs.run(run.id()).get().hasEnded());
+ assertTrue(jobs.run(run.id()).hasEnded());
return this;
}
@@ -494,8 +493,8 @@ public class DeploymentContext {
doDeploy(job);
tester.clock().advance(Timeouts.of(tester.controller().system()).noNodesDown().plusSeconds(1));
runner.advance(currentRun(job));
- assertTrue(jobs.run(id).get().hasFailed());
- assertTrue(jobs.run(id).get().hasEnded());
+ assertTrue(jobs.run(id).hasFailed());
+ assertTrue(jobs.run(id).hasEnded());
return this;
}
@@ -508,8 +507,8 @@ public class DeploymentContext {
doUpgrade(job);
tester.clock().advance(Timeouts.of(tester.controller().system()).noNodesDown().plusSeconds(1));
runner.advance(currentRun(job));
- assertTrue(jobs.run(id).get().hasFailed());
- assertTrue(jobs.run(id).get().hasEnded());
+ assertTrue(jobs.run(id).hasFailed());
+ assertTrue(jobs.run(id).hasEnded());
return this;
}
@@ -541,8 +540,8 @@ public class DeploymentContext {
configServer().convergeServices(instanceId, testZone);
configServer().convergeServices(testerId.id(), testZone);
runner.run();
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endTests));
- assertTrue(jobs.run(id).get().steps().get(Step.endTests).startTime().isPresent());
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endTests));
+ assertTrue(jobs.run(id).steps().get(Step.endTests).startTime().isPresent());
return id;
}
@@ -572,21 +571,21 @@ public class DeploymentContext {
doInstallTester(job);
if (job.type().equals(stagingTest)) { // Do the initial deployment and installation of the real application.
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installInitialReal));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installInitialReal));
tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), tester.configServer().application(job.application(), zone).get().version().get());
configServer().convergeServices(id.application(), zone);
runner.advance(currentRun(job));
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installInitialReal));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installInitialReal));
// All installation is complete and endpoints are ready, so setup may begin.
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installInitialReal));
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installTester));
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.startStagingSetup));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installInitialReal));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installTester));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.startStagingSetup));
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endStagingSetup));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endStagingSetup));
tester.cloud().set(Status.SUCCESS);
runner.advance(currentRun(job));
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.endStagingSetup));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.endStagingSetup));
}
}
@@ -596,7 +595,7 @@ public class DeploymentContext {
ZoneId zone = job.type().zone();
DeploymentId deployment = new DeploymentId(job.application(), zone);
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installReal));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installReal));
configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), tester.configServer().application(job.application(), zone).get().version().get());
runner.advance(currentRun(job));
}
@@ -616,15 +615,15 @@ public class DeploymentContext {
RunId id = currentRun(job).id();
ZoneId zone = job.type().zone();
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installReal));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installReal));
configServer().convergeServices(id.application(), zone);
runner.advance(currentRun(job));
if (job.type().environment().isManuallyDeployed()) {
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installReal));
- assertTrue(jobs.run(id).get().hasEnded());
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installReal));
+ assertTrue(jobs.run(id).hasEnded());
return;
}
- assertEquals("Status of " + id, succeeded, jobs.run(id).get().stepStatuses().get(Step.installReal));
+ assertEquals("Status of " + id, succeeded, jobs.run(id).stepStatuses().get(Step.installReal));
}
/** Installs tester and starts tests. */
@@ -632,13 +631,13 @@ public class DeploymentContext {
RunId id = currentRun(job).id();
ZoneId zone = job.type().zone();
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installTester));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installTester));
configServer().nodeRepository().doUpgrade(new DeploymentId(TesterId.of(job.application()).id(), zone), Optional.empty(), tester.configServer().application(id.tester().id(), zone).get().version().get());
runner.advance(currentRun(job));
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.installTester));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.installTester));
configServer().convergeServices(TesterId.of(id.application()).id(), zone);
runner.advance(currentRun(job));
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installTester));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installTester));
runner.advance(currentRun(job));
}
@@ -649,15 +648,15 @@ public class DeploymentContext {
// All installation is complete and endpoints are ready, so tests may begin.
if (job.type().isDeployment())
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installReal));
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.installTester));
- assertEquals(succeeded, jobs.run(id).get().stepStatuses().get(Step.startTests));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installReal));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.installTester));
+ assertEquals(succeeded, jobs.run(id).stepStatuses().get(Step.startTests));
- assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(unfinished, jobs.run(id).stepStatuses().get(Step.endTests));
tester.cloud().set(Status.SUCCESS);
runner.advance(currentRun(job));
- assertTrue(jobs.run(id).get().hasEnded());
- assertFalse(jobs.run(id).get().hasFailed());
+ assertTrue(jobs.run(id).hasEnded());
+ assertFalse(jobs.run(id).hasFailed());
Instance instance = tester.application(TenantAndApplicationId.from(instanceId)).require(id.application().instance());
assertEquals(job.type().isProduction(), instance.deployments().containsKey(zone));
assertTrue(configServer().nodeRepository().list(zone, NodeFilter.all().applications(TesterId.of(instance.id()).id())).isEmpty());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
index 4219c52be20..78e7606d7c6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
@@ -136,8 +136,8 @@ public class DeploymentTester {
triggerJobs();
for (Run run : jobs.active()) {
jobs.abort(run.id(), "DeploymentTester.abortAll");
- runner.advance(jobs.run(run.id()).get());
- assertTrue(jobs.run(run.id()).get().hasEnded());
+ runner.advance(jobs.run(run.id()));
+ assertTrue(jobs.run(run.id()).hasEnded());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
index beda9bd551d..d602ee8cde3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
@@ -2267,4 +2267,16 @@ public class DeploymentTriggerTest {
assertEquals(Set.of(), tests.deploymentStatus().jobsToRun().keySet());
}
+ @Test
+ public void testNoTests() {
+ DeploymentContext app = tester.newDeploymentContext();
+ app.submit(new ApplicationPackageBuilder().systemTest().region("us-east-3").build());
+
+ // Declared tests must have run actual tests to succeed.
+ app.failTests(systemTest, true);
+ assertFalse(tester.jobs().last(app.instanceId(), systemTest).get().hasSucceeded());
+ app.failTests(stagingTest, true);
+ assertTrue(tester.jobs().last(app.instanceId(), stagingTest).get().hasSucceeded());
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java
index 031cdaa84ae..d781b1f1d3f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java
@@ -6,7 +6,6 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
@@ -24,23 +23,14 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud.
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.application.pkg.TestPackage;
-import com.yahoo.vespa.hosted.controller.config.ControllerConfig;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobRunner;
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
-import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -60,7 +50,6 @@ import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.success;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.failed;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished;
-import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.temporal.ChronoUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -131,19 +120,19 @@ public class InternalStepRunnerTest {
HostName host = tester.configServer().hostFor(instanceId, zone);
tester.runner().run();
- assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.deployReal));
+ assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.deployReal));
tester.configServer().convergeServices(app.instanceId(), zone);
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal));
tester.configServer().nodeRepository().doRestart(app.deploymentIdIn(zone), Optional.of(host));
tester.configServer().nodeRepository().requestReboot(app.deploymentIdIn(zone), Optional.of(host));
tester.runner().run();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal));
tester.clock().advance(InternalStepRunner.Timeouts.of(system()).noNodesDown().plus(Duration.ofSeconds(1)));
tester.runner().run();
- assertEquals(installationFailed, tester.jobs().run(id).get().status());
+ assertEquals(installationFailed, tester.jobs().run(id).status());
}
@Test
@@ -262,7 +251,7 @@ public class InternalStepRunnerTest {
@Test
public void noTestsThenErrorIsError() {
RunId id = app.startSystemTestTests();
- Run run = tester.jobs().run(id).get();
+ Run run = tester.jobs().run(id);
run = run.with(noTests, new LockedStep(() -> { }, Step.endTests));
assertFalse(run.hasFailed());
run = run.with(RunStatus.error, new LockedStep(() -> { }, Step.deactivateReal));
@@ -275,8 +264,8 @@ public class InternalStepRunnerTest {
RunId id = app.startSystemTestTests();
tester.cloud().set(Status.NO_TESTS);
tester.runner().run();
- assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
- Run run = tester.jobs().run(id).get();
+ assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.endTests));
+ Run run = tester.jobs().run(id);
assertEquals(noTests, run.status());
}
@@ -285,7 +274,7 @@ public class InternalStepRunnerTest {
RunId id = app.startSystemTestTests();
tester.cloud().set(TesterCloud.Status.NOT_STARTED);
tester.runner().run();
- assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests));
}
@Test
@@ -299,7 +288,7 @@ public class InternalStepRunnerTest {
assertTestLogEntries(id, Step.endTests,
new LogEntry(lastId + 1, Instant.ofEpochMilli(321), error, "Failure!"),
new LogEntry(lastId + 2, tester.clock().instant(), info, "Tests failed."));
- assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests));
}
@Test
@@ -310,7 +299,7 @@ public class InternalStepRunnerTest {
long lastId = tester.jobs().details(id).get().lastId().getAsLong();
tester.runner().run();
- assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests));
assertTestLogEntries(id, Step.endTests,
new LogEntry(lastId + 1, Instant.ofEpochMilli(123), error, "Error!"),
new LogEntry(lastId + 2, tester.clock().instant(), info, "Tester failed running its tests!"));
@@ -320,7 +309,7 @@ public class InternalStepRunnerTest {
public void testsSucceedWhenTheyDoRemotely() {
RunId id = app.startSystemTestTests();
tester.runner().run();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.endTests));
var testZone = DeploymentContext.systemTest.zone();
Inspector configObject = SlimeUtils.jsonToSlime(tester.cloud().config()).get();
assertEquals(app.instanceId().serializedForm(), configObject.field("application").asString());
@@ -349,7 +338,7 @@ public class InternalStepRunnerTest {
new LogEntry(lastId + 2, Instant.ofEpochMilli(1234), info, "Steady!"),
new LogEntry(lastId + 3, Instant.ofEpochMilli(12345), info, "Success!"),
new LogEntry(lastId + 4, tester.clock().instant(), info, "Tests completed successfully."));
- assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.endTests));
}
@Test
@@ -362,16 +351,16 @@ public class InternalStepRunnerTest {
long lastId1 = tester.jobs().details(id).get().lastId().getAsLong();
Instant instant1 = tester.clock().instant();
tester.runner().run();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
- assertEquals(running, tester.jobs().run(id).get().status());
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.endTests));
+ assertEquals(running, tester.jobs().run(id).status());
tester.cloud().clearLog();
// Test sleeps for a while.
tester.runner().run();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployTester));
tester.clock().advance(Duration.ofSeconds(899));
tester.runner().run();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployTester));
tester.clock().advance(JobRunner.jobTimeout);
var testZone = DeploymentContext.systemTest.zone();
@@ -380,14 +369,14 @@ public class InternalStepRunnerTest {
tester.configServer().convergeServices(app.instanceId(), testZone);
tester.configServer().convergeServices(app.testerId().id(), testZone);
tester.runner().run();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
- assertTrue(tester.jobs().run(id).get().steps().get(Step.endTests).startTime().isPresent());
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.endTests));
+ assertTrue(tester.jobs().run(id).steps().get(Step.endTests).startTime().isPresent());
tester.cloud().set(TesterCloud.Status.SUCCESS);
tester.cloud().testReport(TestReport.fromJson("{\"bar\":2}"));
long lastId2 = tester.jobs().details(id).get().lastId().getAsLong();
tester.runner().run();
- assertEquals(success, tester.jobs().run(id).get().status());
+ assertEquals(success, tester.jobs().run(id).status());
assertTestLogEntries(id, Step.endTests,
new LogEntry(lastId1 + 1, Instant.ofEpochMilli(123), info, "Not enough data!"),
@@ -405,7 +394,7 @@ public class InternalStepRunnerTest {
tester.jobs().deploy(app.instanceId(), DeploymentContext.devUsEast1, Optional.empty(), applicationPackage());
tester.runner().run();
RunId id = tester.jobs().last(app.instanceId(), DeploymentContext.devUsEast1).get().id();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal));
Version version = new Version("7.8.9");
Future<?> concurrentDeployment = Executors.newSingleThreadExecutor().submit(() -> {
@@ -420,7 +409,7 @@ public class InternalStepRunnerTest {
tester.runner().run(); // Job run order determined by JobType enum order per application.
tester.configServer().convergeServices(app.instanceId(), zone);
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.installReal));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.installReal));
assertEquals(applicationPackage().hash(), tester.configServer().application(app.instanceId(), zone).get().applicationPackage().hash());
assertEquals(otherPackage.hash(), tester.configServer().application(app.instanceId(), DeploymentContext.perfUsEast3.zone()).get().applicationPackage().hash());
@@ -455,40 +444,53 @@ public class InternalStepRunnerTest {
@Test
public void vespaLogIsCopied() {
// Tests fail. We should get logs. This fails too, on the first attempt.
+ tester.controllerTester().computeVersionStatus();
RunId id = app.startSystemTestTests();
tester.cloud().set(TesterCloud.Status.ERROR);
tester.configServer().setLogStream(() -> { throw new ConfigServerException(ConfigServerException.ErrorCode.NOT_FOUND, "404", "context"); });
long lastId = tester.jobs().details(id).get().lastId().getAsLong();
tester.runner().run();
- assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.copyVespaLogs));
+ assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.copyVespaLogs));
assertTestLogEntries(id, Step.copyVespaLogs,
new LogEntry(lastId + 2, tester.clock().instant(), info,
- "Found no logs, but will retry"),
- new LogEntry(lastId + 4, tester.clock().instant(), info,
"Found no logs, but will retry"));
// Config servers now provide the log, and we get it.
- tester.configServer().setLogStream(() -> vespaLog);
+ tester.configServer().setLogStream(() -> vespaLog(tester.clock().instant()));
tester.runner().run();
- assertEquals(failed, tester.jobs().run(id).get().stepStatuses().get(Step.endTests));
+ assertEquals(failed, tester.jobs().run(id).stepStatuses().get(Step.endTests));
assertTestLogEntries(id, Step.copyVespaLogs,
new LogEntry(lastId + 2, tester.clock().instant(), info,
"Found no logs, but will retry"),
- new LogEntry(lastId + 4, tester.clock().instant(), info,
- "Found no logs, but will retry"),
- new LogEntry(lastId + 5, Instant.EPOCH.plus(3554970337935104L, ChronoUnit.MICROS), info,
+ new LogEntry(lastId + 3, tester.clock().instant().minusSeconds(4), info,
+ "17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstdout\n" +
+ "ERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)"),
+ new LogEntry(lastId + 4, tester.clock().instant().minusSeconds(4), info,
+ "17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstdout\n" +
+ "ERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)"),
+ /*
+ new LogEntry(lastId + 5, tester.clock().instant().minusSeconds(4), info,
+ "17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstdout\n" +
+ "ERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)"),
+ new LogEntry(lastId + 6, tester.clock().instant().minusSeconds(4), info,
"17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstdout\n" +
"ERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)"),
- new LogEntry(lastId + 6, Instant.EPOCH.plus(3554970337947777L, ChronoUnit.MICROS), info,
+ */
+ new LogEntry(lastId + 5, tester.clock().instant().minusSeconds(3), info,
"17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstdout\n" +
"ERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)"),
- new LogEntry(lastId + 7, Instant.EPOCH.plus(3554970337947820L, ChronoUnit.MICROS), info,
+ new LogEntry(lastId + 6, tester.clock().instant().minusSeconds(3), warning,
+ "17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstderr\n" +
+ "java.lang.NullPointerException\n\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\n\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)"));
+ /*
+ new LogEntry(lastId + 9, tester.clock().instant().minusSeconds(3), info,
"17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstdout\n" +
"ERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)"),
- new LogEntry(lastId + 8, Instant.EPOCH.plus(3554970337947845L, ChronoUnit.MICROS), warning,
+ new LogEntry(lastId + 10, tester.clock().instant().minusSeconds(3), warning,
"17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tstderr\n" +
"java.lang.NullPointerException\n\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\n\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)"));
+ */
}
@Test
@@ -509,21 +511,21 @@ public class InternalStepRunnerTest {
throw new ConfigServerException(ConfigServerException.ErrorCode.PARENT_HOST_NOT_READY, "provisioning", "deploy tester");
});
tester.runner().run();
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester));
- assertEquals(unfinished, tester.jobs().run(id).get().stepStatuses().get(Step.deployReal));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployTester));
+ assertEquals(unfinished, tester.jobs().run(id).stepStatuses().get(Step.deployReal));
List<X509Certificate> oldTrusted = new ArrayList<>(DeploymentContext.publicApplicationPackage().trustedCertificates());
- X509Certificate oldCert = tester.jobs().run(id).get().testerCertificate().get();
+ X509Certificate oldCert = tester.jobs().run(id).testerCertificate().get();
oldTrusted.add(oldCert);
assertEquals(oldTrusted, tester.configServer().application(app.instanceId(), id.type().zone()).get().applicationPackage().trustedCertificates());
tester.configServer().throwOnNextPrepare(null);
tester.runner().run();
- assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.deployTester));
- assertEquals(succeeded, tester.jobs().run(id).get().stepStatuses().get(Step.deployReal));
+ assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.deployTester));
+ assertEquals(succeeded, tester.jobs().run(id).stepStatuses().get(Step.deployReal));
List<X509Certificate> newTrusted = new ArrayList<>(DeploymentContext.publicApplicationPackage().trustedCertificates());
- X509Certificate newCert = tester.jobs().run(id).get().testerCertificate().get();
+ X509Certificate newCert = tester.jobs().run(id).testerCertificate().get();
newTrusted.add(newCert);
assertEquals(newTrusted, tester.configServer().application(app.instanceId(), id.type().zone()).get().applicationPackage().trustedCertificates());
assertNotEquals(oldCert, newCert);
@@ -536,22 +538,24 @@ public class InternalStepRunnerTest {
RunId id = app.startSystemTestTests();
List<X509Certificate> trusted = new ArrayList<>(DeploymentContext.publicApplicationPackage().trustedCertificates());
- trusted.add(tester.jobs().run(id).get().testerCertificate().get());
+ trusted.add(tester.jobs().run(id).testerCertificate().get());
assertEquals(trusted, tester.configServer().application(app.instanceId(), id.type().zone()).get().applicationPackage().trustedCertificates());
tester.clock().advance(InternalStepRunner.Timeouts.of(system()).testerCertificate().plus(Duration.ofSeconds(1)));
tester.runner().run();
- assertEquals(RunStatus.error, tester.jobs().run(id).get().status());
+ assertEquals(RunStatus.error, tester.jobs().run(id).status());
}
private void assertTestLogEntries(RunId id, Step step, LogEntry... entries) {
assertEquals(List.of(entries), tester.jobs().details(id).get().get(step));
}
- private static final String vespaLog = "-1554970337.084804\t17480180-v6-3.ostk.bm2.prod.ne1.yahoo.com\t5549/832\tcontainer\tContainer.com.yahoo.container.jdisc.ConfiguredApplication\tinfo\tSwitching to the latest deployed set of configurations and components. Application switch number: 2\n" +
- "3554970337.935104\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n" +
- "3554970337.947777\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n" +
- "3554970337.947820\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n" +
- "3554970337.947845\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstderr\twarning\tjava.lang.NullPointerException\\n\\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\\n\\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)";
+ private static String vespaLog(Instant now) {
+ return "-1\t17480180-v6-3.ostk.bm2.prod.ne1.yahoo.com\t5549/832\tcontainer\tContainer.com.yahoo.container.jdisc.ConfiguredApplication\tinfo\tSwitching to the latest deployed set of configurations and components. Application switch number: 2\n" +
+ (now.getEpochSecond() - 4) + "." + now.getNano() / 1000 + "\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n" +
+ (now.getEpochSecond() - 4) + "." + now.getNano() / 1000 + "\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n" +
+ (now.getEpochSecond() - 3) + "." + now.getNano() / 1000 + "\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n" +
+ (now.getEpochSecond() - 3) + "." + now.getNano() / 1000 + "\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstderr\twarning\tjava.lang.NullPointerException\\n\\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\\n\\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)";
+ }
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
index 59ee8cc6eae..67583891765 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
@@ -1,12 +1,14 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import org.junit.Test;
@@ -14,6 +16,7 @@ import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -31,6 +34,9 @@ public class TestConfigSerializerTest {
byte[] json = new TestConfigSerializer(SystemName.PublicCd).configJson(instanceId,
DeploymentContext.systemTest,
true,
+ Version.fromString("1.2.3"),
+ RevisionId.forProduction(321),
+ Instant.ofEpochMilli(222),
Map.of(zone, List.of(Endpoint.of(ApplicationId.defaultId())
.target(EndpointId.of("ai"), ClusterSpec.Id.from("qrs"),
List.of(new DeploymentId(ApplicationId.defaultId(),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
index 650fda3c811..af542521b31 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
@@ -1,10 +1,10 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.integration;
-import com.yahoo.component.annotation.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.AbstractComponent;
import com.yahoo.component.Version;
+import com.yahoo.component.annotation.Inject;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.SystemName;
@@ -15,7 +15,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
import com.yahoo.vespa.hosted.controller.api.integration.archive.MockArchiveService;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AccessControlService;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.MockAccessControlService;
-import com.yahoo.vespa.hosted.controller.api.integration.aws.MockCloudEventFetcher;
import com.yahoo.vespa.hosted.controller.api.integration.aws.MockResourceTagger;
import com.yahoo.vespa.hosted.controller.api.integration.aws.MockRoleService;
import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger;
@@ -38,6 +37,8 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueH
import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClient;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClientMock;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.GcpSecretStore;
+import com.yahoo.vespa.hosted.controller.api.integration.secrets.NoopGcpSecretStore;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.NoopTenantSecretService;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.DummySystemMonitor;
@@ -74,7 +75,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
private final MemoryEntityService memoryEntityService = new MemoryEntityService();
private final DummySystemMonitor systemMonitor = new DummySystemMonitor();
private final CostReportConsumerMock costReportConsumerMock = new CostReportConsumerMock();
- private final MockCloudEventFetcher mockAwsEventFetcher = new MockCloudEventFetcher();
private final ArtifactRepositoryMock artifactRepositoryMock = new ArtifactRepositoryMock();
private final MockTesterCloud mockTesterCloud;
private final ApplicationStoreMock applicationStoreMock = new ApplicationStoreMock();
@@ -176,11 +176,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
}
@Override
- public MockCloudEventFetcher eventFetcherService() {
- return mockAwsEventFetcher;
- }
-
- @Override
public ArtifactRepositoryMock artifactRepository() {
return artifactRepositoryMock;
}
@@ -295,4 +290,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
public RoleMaintainerMock roleMaintainerMock() {
return roleMaintainer;
}
+
+ public GcpSecretStore gcpSecretStore() { return new NoopGcpSecretStore(); }
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
index 5571f957e83..3535417c586 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
@@ -9,6 +9,7 @@ import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
import com.yahoo.vespa.hosted.controller.api.integration.archive.MockArchiveService;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import org.junit.Test;
@@ -33,24 +34,24 @@ public class ArchiveAccessMaintainerTest {
String tenant1role = "arn:aws:iam::123456789012:role/my-role";
String tenant2role = "arn:aws:iam::210987654321:role/my-role";
var tenant1 = createTenantWithAccessRole(tester, "tenant1", tenant1role);
- createTenantWithAccessRole(tester, "tenant2", tenant2role);
+ var tenant2 = createTenantWithAccessRole(tester, "tenant2", tenant2role);
ZoneId testZone = ZoneId.from("prod.aws-us-east-1c");
tester.controller().archiveBucketDb().archiveUriFor(testZone, tenant1, true);
var testBucket = new ArchiveBucket("bucketName", "keyArn").withTenant(tenant1);
MockArchiveService archiveService = (MockArchiveService) tester.controller().serviceRegistry().archiveService();
- assertNull(archiveService.authorizedIamRolesForBucket.get(testBucket));
- assertNull(archiveService.authorizedIamRolesForKey.get(testBucket.keyArn()));
+
+ assertEquals(0, archiveService.authorizeAccessByTenantName.size());
MockMetric metric = new MockMetric();
new ArchiveAccessMaintainer(tester.controller(), metric, Duration.ofMinutes(10)).maintain();
- assertEquals(Map.of(tenant1, tenant1role), archiveService.authorizedIamRolesForBucket.get(testBucket));
- assertEquals(Set.of(tenant1role), archiveService.authorizedIamRolesForKey.get(testBucket.keyArn()));
+ assertEquals(new ArchiveAccess().withAWSRole(tenant1role), archiveService.authorizeAccessByTenantName.get(tenant1));
+ assertEquals(new ArchiveAccess().withAWSRole(tenant2role), archiveService.authorizeAccessByTenantName.get(tenant2));
var expected = Map.of("archive.bucketCount",
tester.controller().zoneRegistry().zonesIncludingSystem().all().ids().stream()
.collect(Collectors.toMap(
- zone -> Map.of("zone", zone.value()),
+ zone -> Map.of("zone", zone.value(), "cloud", "default"),
zone -> zone.equals(testZone) ? 1d : 0d)));
assertEquals(expected, metric.metrics());
@@ -59,7 +60,7 @@ public class ArchiveAccessMaintainerTest {
private TenantName createTenantWithAccessRole(ControllerTester tester, String tenantName, String role) {
var tenant = tester.createTenant(tenantName, Tenant.Type.cloud);
tester.controller().tenants().lockOrThrow(tenant, LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccessRole(Optional.of(role));
+ lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess().withAWSRole(role));
tester.controller().tenants().store(lockedTenant);
});
return tenant;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java
index c1d9c03819d..d208657c1c4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java
@@ -40,27 +40,24 @@ public class ArchiveUriUpdaterTest {
var application = tester.newDeploymentContext(tenant1.value(), "app1", "instance1");
ZoneId zone = ZoneId.from("prod", "aws-us-east-1c");
- // Initially we should not set any archive URIs as the archive service does not return any
+ // Initially we should only is the bucket for hosted-vespa tenant
updater.maintain();
- assertArchiveUris(Map.of(), zone);
- // but the controller zone is always present
- assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/hosted-vespa/"),
- ZoneId.from("prod", "controller"));
+ assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/hosted-vespa/"), zone);
+ assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/hosted-vespa/"), ZoneId.from("prod", "controller"));
// Archive service now has URI for tenant1, but tenant1 is not deployed in zone
setBucketNameInService(Map.of(tenant1, "uri-1"), zone);
- setBucketNameInService(Map.of(tenantInfra, "uri-3"), zone);
updater.maintain();
- assertArchiveUris(Map.of(), zone);
+ assertArchiveUris(Map.of(TenantName.from("hosted-vespa"), "s3://bucketName/hosted-vespa/"), zone);
deploy(application, zone);
updater.maintain();
- assertArchiveUris(Map.of(tenant1, "s3://uri-1/tenant1/", tenantInfra, "s3://uri-3/hosted-vespa/"), zone);
+ assertArchiveUris(Map.of(tenant1, "s3://uri-1/tenant1/", tenantInfra, "s3://bucketName/hosted-vespa/"), zone);
// URI for tenant1 should be updated and removed for tenant2
setArchiveUriInNodeRepo(Map.of(tenant1, "wrong-uri", tenant2, "uri-2"), zone);
updater.maintain();
- assertArchiveUris(Map.of(tenant1, "s3://uri-1/tenant1/", tenantInfra, "s3://uri-3/hosted-vespa/"), zone);
+ assertArchiveUris(Map.of(tenant1, "s3://uri-1/tenant1/", tenantInfra, "s3://bucketName/hosted-vespa/"), zone);
}
private void assertArchiveUris(Map<TenantName, String> expectedUris, ZoneId zone) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTrackerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTrackerTest.java
deleted file mode 100644
index 6ccd307f0d9..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudEventTrackerTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.maintenance;
-
-import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.NodeType;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.ControllerTester;
-import com.yahoo.vespa.hosted.controller.api.integration.aws.CloudEvent;
-import com.yahoo.vespa.hosted.controller.api.integration.aws.MockCloudEventFetcher;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeFilter;
-import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
-import org.junit.Test;
-
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * @author olaa
- */
-public class CloudEventTrackerTest {
-
- private final ControllerTester tester = new ControllerTester();
- private final ZoneApiMock unsupportedZone = createZone("prod.zone3", "region-1", "other");
- private final ZoneApiMock zone1 = createZone("prod.zone1", "region-1", "aws");
- private final ZoneApiMock zone2 = createZone("prod.zone2", "region-2", "aws");
-
- /**
- * Test scenario: Consider three zones, two of which are supported
- *
- * We want to test the following:
- * 1. Unsupported zone is completely ignored
- * 2. Hosts affected by cloud event are deprovisioned
- */
- @Test
- public void maintain() {
- setUpZones();
- CloudEventTracker cloudEventTracker = new CloudEventTracker(tester.controller(), Duration.ofMinutes(15));
- assertEquals(Set.of("host1.com", "host2.com", "host3.com"), hostsNotDeprovisioning(unsupportedZone.getId()));
- assertEquals(Set.of("host1.com", "host2.com", "host3.com"), hostsNotDeprovisioning(zone1.getId()));
- assertEquals(Set.of("host4.com", "host5.com", "confighost.com"), hostsNotDeprovisioning(zone2.getId()));
-
- mockEvents();
- cloudEventTracker.maintain();
- assertEquals(Set.of("host1.com", "host2.com", "host3.com"), hostsNotDeprovisioning(unsupportedZone.getId()));
- assertEquals(Set.of("host3.com"), hostsNotDeprovisioning(zone1.getId()));
- assertEquals(Set.of("host4.com"), hostsNotDeprovisioning(zone2.getId()));
- }
-
- private void mockEvents() {
- MockCloudEventFetcher eventFetcher = (MockCloudEventFetcher) tester.controller().serviceRegistry().eventFetcherService();
-
- Date date = new Date();
- CloudEvent event1 = new CloudEvent("event 1",
- "instance code",
- "description",
- date,
- date,
- date,
- "region-1",
- Set.of("host1", "host2"));
-
- CloudEvent event2 = new CloudEvent("event 2",
- "instance code",
- "description",
- date,
- date,
- date,
- "region-2",
- Set.of("host5", "confighost"));
-
- eventFetcher.addEvent("region-1", event1);
- eventFetcher.addEvent("region-2", event2);
- }
-
- private void setUpZones() {
- tester.zoneRegistry().setZones(
- unsupportedZone,
- zone1,
- zone2);
-
- tester.configServer().nodeRepository().putNodes(
- unsupportedZone.getId(),
- createNodesWithHostnames(
- "host1.com",
- "host2.com",
- "host3.com"
- )
- );
- tester.configServer().nodeRepository().putNodes(
- zone1.getId(),
- createNodesWithHostnames(
- "host1.com",
- "host2.com",
- "host3.com"
- )
- );
- tester.configServer().nodeRepository().putNodes(
- zone2.getId(),
- createNodesWithHostnames(
- "host4.com",
- "host5.com"
- )
- );
- tester.configServer().nodeRepository().putNodes(
- zone2.getId(),
- List.of(createNode("confighost.com", NodeType.confighost))
- );
- }
-
- private List<Node> createNodesWithHostnames(String... hostnames) {
- return Arrays.stream(hostnames)
- .map(hostname -> createNode(hostname, NodeType.host))
- .collect(Collectors.toUnmodifiableList());
- }
-
- private Node createNode(String hostname, NodeType nodeType) {
- return Node.builder()
- .hostname(HostName.of(hostname))
- .type(nodeType)
- .build();
- }
-
- private Set<String> hostsNotDeprovisioning(ZoneId zoneId) {
- return tester.configServer().nodeRepository().list(zoneId, NodeFilter.all())
- .stream()
- .filter(node -> !node.wantToDeprovision())
- .map(node -> node.hostname().value())
- .collect(Collectors.toSet());
- }
-
- private ZoneApiMock createZone(String zoneId, String cloudNativeRegionName, String cloud) {
- return ZoneApiMock.newBuilder().withId(zoneId)
- .withCloudNativeRegionName(cloudNativeRegionName)
- .withCloud(cloud)
- .build();
- }
-
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
index 2e34c6511a7..df30b6b57ee 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java
@@ -38,7 +38,7 @@ public class CloudTrialExpirerTest {
@Test
public void tombstone_inactive_none() {
- registerTenant("none-tenant", "none", Duration.ofDays(28).plusMillis(1));
+ registerTenant("none-tenant", "none", Duration.ofDays(365).plusMillis(1));
expirer.maintain();
assertEquals(Tenant.Type.deleted, tester.controller().tenants().get(TenantName.from("none-tenant"), true).get().type());
}
@@ -75,7 +75,7 @@ public class CloudTrialExpirerTest {
@Test
public void delete_tenants_with_applications_with_no_deployments() {
- registerTenant("with-apps", "trial", Duration.ofDays(30));
+ registerTenant("with-apps", "trial", Duration.ofDays(366));
tester.createApplication("with-apps", "app1", "instance1");
expirer.maintain();
assertPlan("with-apps", "none");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
index 5fd1e8347ef..b9ced334e5a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
@@ -93,8 +93,7 @@ public class JobRunnerTest {
ApplicationId id = appId.defaultInstance();
byte[] testPackageBytes = new byte[0];
jobs.submit(appId, Submission.basic(applicationPackage, testPackageBytes), 2);
-
- start(jobs, id, systemTest);
+ start(jobs, id, systemTest);
try {
start(jobs, id, systemTest);
fail("Job is already running, so this should not be allowed!");
@@ -106,12 +105,16 @@ public class JobRunnerTest {
assertFalse(jobs.last(id, systemTest).get().hasEnded());
assertTrue(jobs.last(id, stagingTest).get().stepStatuses().values().stream().allMatch(unfinished::equals));
assertFalse(jobs.last(id, stagingTest).get().hasEnded());
- runner.maintain();
+ runner.maintain();
phaser.arriveAndAwaitAdvance();
assertTrue(jobs.last(id, systemTest).get().stepStatuses().values().stream().allMatch(succeeded::equals));
- assertTrue(jobs.last(id, stagingTest).get().hasEnded());
assertTrue(jobs.last(id, stagingTest).get().hasFailed());
+
+ runner.maintain();
+ phaser.arriveAndAwaitAdvance();
+ assertTrue(jobs.last(id, systemTest).get().hasEnded());
+ assertTrue(jobs.last(id, stagingTest).get().hasEnded());
}
@Test
@@ -165,8 +168,8 @@ public class JobRunnerTest {
outcomes.put(endTests, testFailure);
runner.maintain();
assertTrue(run.get().hasFailed());
- assertEquals(List.of(copyVespaLogs, deactivateTester), run.get().readySteps());
- assertStepsWithStartTime(run.get(), deployTester, deployReal, installTester, installReal, startTests, endTests, copyVespaLogs, deactivateTester);
+ assertEquals(List.of(copyVespaLogs), run.get().readySteps());
+ assertStepsWithStartTime(run.get(), deployTester, deployReal, installTester, installReal, startTests, endTests, copyVespaLogs);
outcomes.put(copyVespaLogs, running);
runner.maintain();
@@ -442,8 +445,8 @@ public class JobRunnerTest {
@Override public void execute(Runnable command) {
phaser.register();
delegate.execute(() -> {
- command.run();
- phaser.arriveAndDeregister();
+ try { command.run(); }
+ finally { phaser.arriveAndDeregister(); }
});
}
};
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainerTest.java
deleted file mode 100644
index 8b2bfe8ee95..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemRoutingPolicyMaintainerTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.maintenance;
-
-import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.ControllerTester;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
-import com.yahoo.vespa.hosted.controller.application.SystemApplication;
-import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
-import org.junit.Test;
-
-import java.time.Duration;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-
-/**
- * @author mpolden
- */
-public class SystemRoutingPolicyMaintainerTest {
-
- @Test
- public void maintain() {
- var tester = new ControllerTester();
- var updater = new SystemRoutingPolicyMaintainer(tester.controller(), Duration.ofDays(1));
- var dispatcher = new NameServiceDispatcher(tester.controller(), Duration.ofSeconds(Integer.MAX_VALUE));
-
- var zone = ZoneId.from("prod", "us-west-1");
- tester.zoneRegistry().exclusiveRoutingIn(ZoneApiMock.from(zone));
- tester.configServer().putLoadBalancers(zone, List.of(new LoadBalancer("lb1",
- SystemApplication.configServer.id(),
- ClusterSpec.Id.from("config"),
- Optional.of(HostName.of("lb1.example.com")),
- LoadBalancer.State.active,
- Optional.of("dns-zone-1"))));
-
- // Record is created
- updater.run();
- dispatcher.run();
- Set<Record> records = tester.nameService().records();
- assertEquals(1, records.size());
- Record record = records.iterator().next();
- assertSame(Record.Type.CNAME, record.type());
- assertEquals("cfg.prod.us-west-1.test.vip", record.name().asString());
- assertEquals("lb1.example.com.", record.data().asString());
- }
-
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
index 75dbebe96ff..5666f8bafd8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
@@ -20,6 +20,7 @@ import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
@@ -60,7 +61,7 @@ public class NotificationsDbTest {
List.of(TenantContacts.Audience.NOTIFICATIONS),
email)))),
List.of(),
- Optional.empty());
+ new ArchiveAccess());
private static final List<Notification> notifications = List.of(
notification(1001, Type.deployment, Level.error, NotificationSource.from(tenant), "tenant msg"),
notification(1101, Type.applicationPackage, Level.warning, NotificationSource.from(TenantAndApplicationId.from(tenant.value(), "app1")), "app msg"),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java
new file mode 100644
index 00000000000..8bf0e584892
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java
@@ -0,0 +1,87 @@
+package com.yahoo.vespa.hosted.controller.notification;
+
+import com.google.common.collect.ImmutableBiMap;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.InMemoryFlagSource;
+import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer;
+import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
+import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
+import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
+import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+public class NotifierTest {
+ private static final TenantName tenant = TenantName.from("tenant1");
+ private static final String email = "user1@example.com";
+
+ private static final CloudTenant cloudTenant = new CloudTenant(tenant,
+ Instant.now(),
+ LastLoginInfo.EMPTY,
+ Optional.empty(),
+ ImmutableBiMap.of(),
+ TenantInfo.empty()
+ .withContacts(new TenantContacts(
+ List.of(new TenantContacts.EmailContact(
+ List.of(TenantContacts.Audience.NOTIFICATIONS),
+ email)))),
+ List.of(),
+ new ArchiveAccess());
+
+
+ MockCuratorDb curatorDb = new MockCuratorDb(SystemName.Public);
+
+ @Before
+ public void init() {
+ curatorDb.writeTenant(cloudTenant);
+ }
+
+ @Test
+ public void dispatch() {
+ var mailer = new MockMailer();
+ var flagSource = new InMemoryFlagSource().withBooleanFlag(Flags.NOTIFICATION_DISPATCH_FLAG.id(), true);
+ var notifier = new Notifier(curatorDb, new ZoneRegistryMock(SystemName.cd), mailer, flagSource);
+
+ var notification = new Notification(Instant.now(), Notification.Type.testPackage, Notification.Level.warning,
+ NotificationSource.from(ApplicationId.from(tenant, ApplicationName.defaultName(), InstanceName.defaultName())),
+ List.of("test package has production tests, but no production tests are declared in deployment.xml",
+ "see https://docs.vespa.ai/en/testing.html for details on how to write system tests for Vespa"));
+ notifier.dispatch(notification);
+ assertEquals(1, mailer.inbox(email).size());
+ var mail = mailer.inbox(email).get(0);
+
+ assertEquals("[WARNING] Test package Vespa Notification for tenant1.default.default", mail.subject());
+ assertEquals("There are problems with tests for default.default<br>\n" +
+ "<ul>\n" +
+ "<li>test package has production tests, but no production tests are declared in deployment.xml</li><br>\n" +
+ "<li>see <a href=\"https://docs.vespa.ai/en/testing.html\">https://docs.vespa.ai/en/testing.html</a> for details on how to write system tests for Vespa</li></ul>\n" +
+ "<br>\n" +
+ "<a href=\"https://dashboard.tld/tenant1/default\">Vespa Console</a>",
+ mail.htmlMessage().get());
+ }
+
+ @Test
+ public void linkify() {
+ var data = Map.of(
+ "Hello. https://example.com/foo/bar.html is a nice place.", "Hello. <a href=\"https://example.com/foo/bar.html\">https://example.com/foo/bar.html</a> is a nice place.",
+ "No url.", "No url.");
+ data.forEach((input, expected) -> assertEquals(expected, Notifier.linkify(input)));
+ }
+} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java
index fd0ea50e50b..6a01a70eb98 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializerTest.java
@@ -25,7 +25,6 @@ import java.time.Instant;
import java.util.List;
import java.util.Optional;
-import static com.yahoo.config.provision.SystemName.main;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.aborted;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.failed;
@@ -132,6 +131,7 @@ public class RunSerializerTest {
assertEquals(run.end(), phoenix.end());
assertEquals(run.status(), phoenix.status());
assertEquals(run.lastTestLogEntry(), phoenix.lastTestLogEntry());
+ assertEquals(run.lastVespaLogTimestamp(), phoenix.lastVespaLogTimestamp());
assertEquals(run.noNodesDownSince(), phoenix.noNodesDownSince());
assertEquals(run.testerCertificate(), phoenix.testerCertificate());
assertEquals(run.versions(), phoenix.versions());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
index e0d14f19f21..a9e633a78d6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
@@ -6,12 +6,14 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -101,7 +103,7 @@ public class TenantSerializerTest {
otherPublicKey, new SimplePrincipal("jane")),
TenantInfo.empty(),
List.of(),
- Optional.empty()
+ new ArchiveAccess()
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.name(), serialized.name());
@@ -123,13 +125,61 @@ public class TenantSerializerTest {
new TenantSecretStore("ss1", "123", "role1"),
new TenantSecretStore("ss2", "124", "role2")
),
- Optional.of("arn:aws:iam::123456789012:role/my-role")
+ new ArchiveAccess().withAWSRole("arn:aws:iam::123456789012:role/my-role")
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.info(), serialized.info());
assertEquals(tenant.tenantSecretStores(), serialized.tenantSecretStores());
}
+ @Test
+ public void cloud_tenant_with_old_archive_access_serialization() {
+ var json = "{\n" +
+ " \"name\": \"elderly-lady\",\n" +
+ " \"type\": \"cloud\",\n" +
+ " \"createdAt\": 1234,\n" +
+ " \"lastLoginInfo\": {\n" +
+ " \"user\": 123,\n" +
+ " \"developer\": 456\n" +
+ " },\n" +
+ " \"creator\": \"foobar-user\",\n" +
+ " \"pemDeveloperKeys\": [\n" +
+ " {\n" +
+ " \"key\": \"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuKVFA8dXk43kVfYKzkUqhEY2rDT9\\nz/4jKSTHwbYR8wdsOSrJGVEUPbS2nguIJ64OJH7gFnxM6sxUVj+Nm2HlXw==\\n-----END PUBLIC KEY-----\\n\",\n" +
+ " \"user\": \"joe\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"key\": \"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFELzPyinTfQ/sZnTmRp5E4Ve/sbE\\npDhJeqczkyFcT2PysJ5sZwm7rKPEeXDOhzTPCyRvbUqc2SGdWbKUGGa/Yw==\\n-----END PUBLIC KEY-----\\n\",\n" +
+ " \"user\": \"jane\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"billingInfo\": {\n" +
+ " \"customerId\": \"customer\",\n" +
+ " \"productCode\": \"Vespa\"\n" +
+ " },\n" +
+ " \"archiveAccessRole\": \"arn:aws:iam::123456789012:role/my-role\"\n" +
+ "}";
+ var tenant = (CloudTenant) serializer.tenantFrom(SlimeUtils.jsonToSlime(json));
+ assertEquals("arn:aws:iam::123456789012:role/my-role", tenant.archiveAccess().awsRole().get());
+ assertFalse(tenant.archiveAccess().gcpMember().isPresent());
+ }
+
+ @Test
+ public void cloud_tenant_with_archive_access() {
+ CloudTenant tenant = new CloudTenant(TenantName.from("elderly-lady"),
+ Instant.ofEpochMilli(1234L),
+ lastLoginInfo(123L, 456L, null),
+ Optional.of(new SimplePrincipal("foobar-user")),
+ ImmutableBiMap.of(publicKey, new SimplePrincipal("joe"),
+ otherPublicKey, new SimplePrincipal("jane")),
+ TenantInfo.empty(),
+ List.of(),
+ new ArchiveAccess().withAWSRole("arn:aws:iam::123456789012:role/my-role").withGCPMember("user:foo@example.com")
+ );
+ CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
+ assertEquals(serialized.archiveAccess().awsRole().get(), "arn:aws:iam::123456789012:role/my-role");
+ assertEquals(serialized.archiveAccess().gcpMember().get(), "user:foo@example.com");
+ }
@Test
public void cloud_tenant_with_tenant_info_partial() {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
index 32fe8ddecff..845d007c154 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
@@ -24,7 +24,7 @@ public class ProxyResponseTest {
Map.of(), null, List.of(URI.create("http://example.com")), Path.parse("configserver"));
ProxyResponse proxyResponse = new ProxyResponse(
request,
- "response link is http://configserver:1234/bla/bla/",
+ "response link is http://configserver:4443/bla/bla/",
200,
URI.create("http://configserver:1234"),
"application/json");
@@ -42,7 +42,7 @@ public class ProxyResponseTest {
Map.of(), null, List.of(URI.create("http://example.com")), Path.parse("configserver"));
ProxyResponse proxyResponse = new ProxyResponse(
request,
- "response link is http://configserver:1234/bla/bla/",
+ "response link is http://configserver:4443/bla/bla/",
200,
URI.create("http://configserver:1234"),
"application/json");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java
index 5f580b6f6b3..52fd7393c4d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java
@@ -62,12 +62,6 @@ public class ApplicationRequestToDiscFilterRequestWrapper extends DiscFilterRequ
}
@Override
- @Deprecated
- public void setUri(URI uri) {
- throw new UnsupportedOperationException();
- }
-
- @Override
public String getParameter(String name) {
throw new UnsupportedOperationException();
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
index 5368cc73480..26dd6335ab8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.restapi.RestApiException;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
@@ -29,7 +30,6 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import org.junit.Before;
import org.junit.Test;
-import javax.ws.rs.ForbiddenException;
import java.io.File;
import java.util.Collections;
import java.util.Optional;
@@ -80,6 +80,52 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
}
@Test
+ public void tenant_info_profile() {
+ var request = request("/application/v4/tenant/scoober/info/profile", GET)
+ .roles(Set.of(Role.reader(tenantName)));
+ tester.assertResponse(request, "{}", 200);
+
+ var updateRequest = request("/application/v4/tenant/scoober/info/profile", PUT)
+ .data("{\"contact\":{\"name\":\"Some Name\",\"email\":\"foo@example.com\"},\"tenant\":{\"company\":\"Scoober, Inc.\",\"website\":\"https://example.com/\"}}")
+ .roles(Set.of(Role.administrator(tenantName)));
+ tester.assertResponse(updateRequest, "{\"message\":\"Tenant info updated\"}", 200);
+
+ tester.assertResponse(request, "{\"contact\":{\"name\":\"Some Name\",\"email\":\"foo@example.com\"},\"tenant\":{\"company\":\"\",\"website\":\"https://example.com/\"}}", 200);
+ }
+
+ @Test
+ public void tenant_info_billing() {
+ var request = request("/application/v4/tenant/scoober/info/billing", GET)
+ .roles(Set.of(Role.reader(tenantName)));
+ tester.assertResponse(request, "{}", 200);
+
+ var fullAddress = "{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}";
+ var fullBillingContact = "{\"contact\":{\"name\":\"name\",\"email\":\"foo@example\",\"phone\":\"phone\"},\"address\":" + fullAddress + "}";
+
+ var updateRequest = request("/application/v4/tenant/scoober/info/billing", PUT)
+ .data(fullBillingContact)
+ .roles(Set.of(Role.administrator(tenantName)));
+ tester.assertResponse(updateRequest, "{\"message\":\"Tenant info updated\"}", 200);
+
+ tester.assertResponse(request, "{\"contact\":{\"name\":\"name\",\"email\":\"foo@example\",\"phone\":\"phone\"},\"address\":{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}}", 200);
+ }
+
+ @Test
+ public void tenant_info_contacts() {
+ var request = request("/application/v4/tenant/scoober/info/contacts", GET)
+ .roles(Set.of(Role.reader(tenantName)));
+ tester.assertResponse(request, "{\"contacts\":[]}", 200);
+
+
+ var fullContacts = "{\"contacts\":[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\"},{\"audiences\":[\"notifications\"],\"email\":\"contact2@example.com\"},{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"contact3@example.com\"}]}";
+ var updateRequest = request("/application/v4/tenant/scoober/info/contacts", PUT)
+ .data(fullContacts)
+ .roles(Set.of(Role.administrator(tenantName)));
+ tester.assertResponse(updateRequest, "{\"message\":\"Tenant info updated\"}", 200);
+ tester.assertResponse(request, fullContacts, 200);
+ }
+
+ @Test
public void tenant_info_workflow() {
var infoRequest =
request("/application/v4/tenant/scoober/info", GET)
@@ -221,7 +267,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
try {
tester.controller().tenants().create(tenantSpec("tenant2"), credentials("administrator"));
fail("Should not be allowed to create tenant that exceed trial limit");
- } catch (ForbiddenException e) {
+ } catch (RestApiException.Forbidden e) {
assertEquals("Too many tenants with trial plans, please contact the Vespa support team", e.getMessage());
}
}
@@ -315,9 +361,40 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
.data("{\"role\":\"dummy\"}").roles(Role.administrator(tenantName)),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Invalid archive access role 'dummy': Must match expected pattern: 'arn:aws:iam::\\\\d{12}:.+'\"}", 400);
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/aws", PUT)
+ .data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)),
+ "{\"message\":\"AWS archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertTrue(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
+ 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/aws", DELETE).roles(Role.administrator(tenantName)),
+ "{\"message\":\"AWS archive access role removed for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertFalse(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
+ 200);
+
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/gcp", PUT)
+ .data("{\"member\":\"user:test@example.com\"}").roles(Role.administrator(tenantName)),
+ "{\"message\":\"GCP archive access member set to 'user:test@example.com' for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertTrue(response.getBodyAsString().contains("\"gcpMember\":\"user:test@example.com\"")),
+ 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access/gcp", DELETE).roles(Role.administrator(tenantName)),
+ "{\"message\":\"GCP archive access member removed for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertFalse(response.getBodyAsString().contains("\"gcpMember\":\"user:test@example.com\"")),
+ 200);
+
+ tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", PUT)
+ .data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)),
+ "{\"message\":\"AWS archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
+ tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
+ (response) -> assertTrue(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
+ 200);
+
tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", PUT)
.data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)),
- "{\"message\":\"Archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
+ "{\"message\":\"AWS archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200);
tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
(response) -> assertTrue(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")),
200);
@@ -327,7 +404,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
new File("deployment-cloud.json"));
tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", DELETE).roles(Role.administrator(tenantName)),
- "{\"message\":\"Archive access role removed for tenant scoober.\"}", 200);
+ "{\"message\":\"AWS archive access role removed for tenant scoober.\"}", 200);
tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)),
(response) -> assertFalse(response.getBodyAsString().contains("archiveAccessRole")),
200);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 6bfbb044944..d9f0f010104 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -305,7 +305,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/myuser/job/dev-us-east-1/diff/1", GET).userIdentity(HOSTED_VESPA_OPERATOR),
(response) -> assertTrue(response.getBodyAsString(),
- response.getBodyAsString().contains("--- search-definitions/test.sd\n" +
+ response.getBodyAsString().contains("--- schemas/test.sd\n" +
"@@ -1,0 +1,1 @@\n" +
"+ search test { }\n")),
200);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json
index 3a5e6dc5dc3..ac0f2b9f740 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config-dev.json
@@ -3,6 +3,9 @@
"zone": "dev.us-east-1",
"system": "main",
"isCI": false,
+ "platform": "6.1.0",
+ "revision": 1,
+ "deployedAt": 1600000000000,
"endpoints": {
"dev.us-east-1": [
"https://my-user.application1.tenant1.us-east-1.dev.vespa.oath.cloud/"
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json
index 0a9236655ba..671c34cb2c0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/test-config.json
@@ -3,6 +3,9 @@
"zone": "prod.us-central-1",
"system": "main",
"isCI": false,
+ "platform": "6.1.0",
+ "revision": 1,
+ "deployedAt": 1600000000000,
"endpoints": {
"prod.us-central-1": [
"https://application1.tenant1.us-central-1.vespa.oath.cloud/"
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java
index b58e5294f66..c9e86849ed8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java
@@ -79,6 +79,13 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest {
}
@Test
+ public void list_plans() {
+ var listPlansRequest = request("/billing/v1/plans", GET)
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(listPlansRequest, "{\"plans\":[{\"id\":\"trial\",\"name\":\"Free Trial - for testing purposes\"},{\"id\":\"paid\",\"name\":\"Paid Plan - for testing purposes\"},{\"id\":\"none\",\"name\":\"None Plan - for testing purposes\"}]}");
+ }
+
+ @Test
public void setting_and_deleting_instrument() {
assertTrue(billingController.getDefaultInstrument(tenant).isEmpty());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
index 8b2e5578ae0..be28d88abaa 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
@@ -22,9 +22,6 @@
"name": "ChangeRequestMaintainer"
},
{
- "name": "CloudEventTracker"
- },
- {
"name": "CloudTrialExpirer"
},
{
@@ -88,9 +85,6 @@
"name": "RetriggerMaintainer"
},
{
- "name": "SystemRoutingPolicyMaintainer"
- },
- {
"name": "SystemUpgrader"
},
{
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
index 8db6bdf9a4a..279bd289c00 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
@@ -26,8 +26,7 @@ public class BadgeApiTest extends ControllerContainerTest {
public void testBadgeApi() throws IOException {
ContainerTester tester = new ContainerTester(container, responseFiles);
var application = new DeploymentTester(new ControllerTester(tester)).newDeploymentContext("tenant", "application", "default");
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder().systemTest()
- .parallel("us-west-1", "aws-us-east-1a")
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder().parallel("us-west-1", "aws-us-east-1a")
.test("us-west-1")
.region("ap-southeast-1")
.test("ap-southeast-1")
@@ -59,6 +58,8 @@ public class BadgeApiTest extends ControllerContainerTest {
Files.readString(Paths.get(responseFiles + "overview.svg")), 200);
tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/production-us-west-1?historyLength=0"),
Files.readString(Paths.get(responseFiles + "single-running.svg")), 200);
+ tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/system-test"),
+ Files.readString(Paths.get(responseFiles + "running-test.svg")), 200);
tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/production-us-west-1?historyLength=32"),
Files.readString(Paths.get(responseFiles + "history.svg")), 200);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg
index a0005ed6d76..46e4acaace6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg
@@ -1,4 +1,4 @@
-<svg xmlns='http://www.w3.org/2000/svg' width='763.7809900000001' height='20' role='img' aria-label='Deployment Status'>
+<svg xmlns='http://www.w3.org/2000/svg' width='689.25265' height='20' role='img' aria-label='Deployment Status'>
<title>Deployment Status</title>
<linearGradient id='light' x2='0' y2='100%'>
<stop offset='0' stop-color='#fff' stop-opacity='.5'/>
@@ -46,32 +46,29 @@
<animate attributeName='x2' values='-10%;250%;120%;-10%' dur='6s' repeatCount='indefinite' />
</linearGradient>
<clipPath id='rounded'>
- <rect width='763.7809900000001' height='20' rx='3' fill='#fff'/>
+ <rect width='689.25265' height='20' rx='3' fill='#fff'/>
</clipPath>
<g clip-path='url(#rounded)'>
- <rect x='757.7809900000001' rx='3' width='8' height='20' fill='url(#shadow)'/>
- <rect x='725.59036' rx='3' width='38.19063' height='20' fill='url(#run-on-success)'/>
- <polygon points='635.8470950000001 0 635.8470950000001 20 734.59036 20 742.59036 0' fill='#00f844'/>
- <rect x='635.8470950000001' rx='3' width='131.74345499999998' height='20' fill='url(#shade)'/>
- <rect x='635.8470950000001' rx='3' width='8' height='20' fill='url(#shadow)'/>
- <rect x='603.656465' rx='3' width='38.19063' height='20' fill='#bf103c'/>
- <polygon points='486.981225 0 486.981225 20 612.656465 20 620.656465 0' fill='#00f844'/>
- <rect x='486.981225' rx='3' width='158.67543' height='20' fill='url(#shade)'/>
- <rect x='486.981225' rx='3' width='8' height='20' fill='url(#shadow)'/>
- <rect x='348.865175' rx='3' width='144.11604999999997' height='20' fill='url(#run-on-success)'/>
- <rect x='358.865175' rx='3' width='134.11604999999997' height='20' fill='url(#shade)'/>
- <rect x='358.865175' rx='3' width='8' height='20' fill='url(#shadow)'/>
- <rect x='326.674545' rx='3' width='38.19063' height='20' fill='#00f844'/>
- <polygon points='237.71563000000003 0 237.71563000000003 20 335.674545 20 343.674545 0' fill='url(#run-on-failure)'/>
- <rect x='237.71563000000003' rx='3' width='130.959105' height='20' fill='url(#shade)'/>
- <rect x='237.71563000000003' rx='3' width='8' height='20' fill='url(#shadow)'/>
- <rect x='153.18729000000002' rx='3' width='90.52834000000001' height='20' fill='url(#run-on-warning)'/>
- <rect x='163.18729000000002' rx='3' width='80.52834000000001' height='20' fill='url(#shade)'/>
+ <rect x='683.25265' rx='3' width='8' height='20' fill='url(#shadow)'/>
+ <rect x='651.06202' rx='3' width='38.19063' height='20' fill='url(#run-on-success)'/>
+ <polygon points='561.318755 0 561.318755 20 660.06202 20 668.06202 0' fill='#00f844'/>
+ <rect x='561.318755' rx='3' width='131.74345499999998' height='20' fill='url(#shade)'/>
+ <rect x='561.318755' rx='3' width='8' height='20' fill='url(#shadow)'/>
+ <rect x='529.128125' rx='3' width='38.19063' height='20' fill='#bf103c'/>
+ <polygon points='412.452885 0 412.452885 20 538.128125 20 546.128125 0' fill='#00f844'/>
+ <rect x='412.452885' rx='3' width='158.67543' height='20' fill='url(#shade)'/>
+ <rect x='412.452885' rx='3' width='8' height='20' fill='url(#shadow)'/>
+ <rect x='274.336835' rx='3' width='144.11604999999997' height='20' fill='url(#run-on-success)'/>
+ <rect x='284.336835' rx='3' width='134.11604999999997' height='20' fill='url(#shade)'/>
+ <rect x='284.336835' rx='3' width='8' height='20' fill='url(#shadow)'/>
+ <rect x='252.146205' rx='3' width='38.19063' height='20' fill='#00f844'/>
+ <polygon points='163.18729000000002 0 163.18729000000002 20 261.146205 20 269.146205 0' fill='url(#run-on-failure)'/>
+ <rect x='163.18729000000002' rx='3' width='130.959105' height='20' fill='url(#shade)'/>
<rect width='169.18729000000002' height='20' fill='#404040'/>
<rect x='-6.0' rx='3' width='175.18729000000002' height='20' fill='url(#shade)'/>
<rect width='2' height='20' fill='url(#left-light)'/>
- <rect x='761.7809900000001' width='2' height='20' fill='url(#right-shadow)'/>
- <rect width='763.7809900000001' height='20' fill='url(#light)'/>
+ <rect x='687.25265' width='2' height='20' fill='url(#right-shadow)'/>
+ <rect width='689.25265' height='20' fill='url(#light)'/>
</g>
<g fill='#fff' text-anchor='middle' font-family='Verdana,Geneva,DejaVu Sans,sans-serif' text-rendering='geometricPrecision' font-size='11'>
<svg x='6.5' y='3.0' width='16.0' height='16.0' viewBox='0 0 150 150'>
@@ -96,29 +93,27 @@
</svg>
<text font-size='11' x='96.09364500000001' y='15' fill='#000' fill-opacity='.4' textLength='135.18729000000002'>tenant.application.default</text>
<text font-size='11' x='95.59364500000001' y='14' fill='#fff' textLength='135.18729000000002'>tenant.application.default</text>
- <text font-size='11' x='206.95146000000003' y='15' fill='#000' fill-opacity='.4' textLength='62.52834000000001'>system-test</text>
- <text font-size='11' x='206.45146000000003' y='14' fill='#fff' textLength='62.52834000000001'>system-test</text>
- <text font-size='11' x='276.60659250000003' y='15' fill='#000' fill-opacity='.4' textLength='52.781925'>us-west-1</text>
- <text font-size='11' x='276.10659250000003' y='14' fill='#fff' textLength='52.781925'>us-west-1</text>
- <text font-size='9' x='323.08605000000006' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
- <text font-size='9' x='322.58605000000006' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
- <text font-size='9' x='351.26986000000005' y='15' fill='#000' fill-opacity='.4' textLength='16.190630000000002'>test</text>
- <text font-size='9' x='350.76986000000005' y='14' fill='#fff' textLength='16.190630000000002'>test</text>
- <text font-size='11' x='412.334705' y='15' fill='#000' fill-opacity='.4' textLength='81.93905999999998'>aws-us-east-1a</text>
- <text font-size='11' x='411.834705' y='14' fill='#fff' textLength='81.93905999999998'>aws-us-east-1a</text>
- <text font-size='9' x='473.39273000000003' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
- <text font-size='9' x='472.89273000000003' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
- <text font-size='11' x='539.73035' y='15' fill='#000' fill-opacity='.4' textLength='80.49825'>ap-southeast-1</text>
- <text font-size='11' x='539.23035' y='14' fill='#fff' textLength='80.49825'>ap-southeast-1</text>
- <text font-size='9' x='600.06797' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
- <text font-size='9' x='599.56797' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
- <text font-size='9' x='628.25178' y='15' fill='#000' fill-opacity='.4' textLength='16.190630000000002'>test</text>
- <text font-size='9' x='627.75178' y='14' fill='#fff' textLength='16.190630000000002'>test</text>
- <text font-size='11' x='675.1302325' y='15' fill='#000' fill-opacity='.4' textLength='53.566275'>eu-west-1</text>
- <text font-size='11' x='674.6302325' y='14' fill='#fff' textLength='53.566275'>eu-west-1</text>
- <text font-size='9' x='722.0018650000001' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
- <text font-size='9' x='721.5018650000001' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
- <text font-size='9' x='750.1856750000001' y='15' fill='#000' fill-opacity='.4' textLength='16.190630000000002'>test</text>
- <text font-size='9' x='749.6856750000001' y='14' fill='#fff' textLength='16.190630000000002'>test</text>
+ <text font-size='11' x='202.07825250000002' y='15' fill='#000' fill-opacity='.4' textLength='52.781925'>us-west-1</text>
+ <text font-size='11' x='201.57825250000002' y='14' fill='#fff' textLength='52.781925'>us-west-1</text>
+ <text font-size='9' x='248.55771000000001' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
+ <text font-size='9' x='248.05771000000001' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
+ <text font-size='9' x='276.74152000000004' y='15' fill='#000' fill-opacity='.4' textLength='16.190630000000002'>test</text>
+ <text font-size='9' x='276.24152000000004' y='14' fill='#fff' textLength='16.190630000000002'>test</text>
+ <text font-size='11' x='337.806365' y='15' fill='#000' fill-opacity='.4' textLength='81.93905999999998'>aws-us-east-1a</text>
+ <text font-size='11' x='337.306365' y='14' fill='#fff' textLength='81.93905999999998'>aws-us-east-1a</text>
+ <text font-size='9' x='398.86439' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
+ <text font-size='9' x='398.36439' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
+ <text font-size='11' x='465.20201' y='15' fill='#000' fill-opacity='.4' textLength='80.49825'>ap-southeast-1</text>
+ <text font-size='11' x='464.70201' y='14' fill='#fff' textLength='80.49825'>ap-southeast-1</text>
+ <text font-size='9' x='525.53963' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
+ <text font-size='9' x='525.03963' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
+ <text font-size='9' x='553.72344' y='15' fill='#000' fill-opacity='.4' textLength='16.190630000000002'>test</text>
+ <text font-size='9' x='553.22344' y='14' fill='#fff' textLength='16.190630000000002'>test</text>
+ <text font-size='11' x='600.6018925' y='15' fill='#000' fill-opacity='.4' textLength='53.566275'>eu-west-1</text>
+ <text font-size='11' x='600.1018925' y='14' fill='#fff' textLength='53.566275'>eu-west-1</text>
+ <text font-size='9' x='647.473525' y='15' fill='#000' fill-opacity='.4' textLength='28.176989999999996'>deploy</text>
+ <text font-size='9' x='646.973525' y='14' fill='#fff' textLength='28.176989999999996'>deploy</text>
+ <text font-size='9' x='675.657335' y='15' fill='#000' fill-opacity='.4' textLength='16.190630000000002'>test</text>
+ <text font-size='9' x='675.157335' y='14' fill='#fff' textLength='16.190630000000002'>test</text>
</g>
</svg>
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/running-test.svg b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/running-test.svg
new file mode 100644
index 00000000000..9463c01e8ad
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/running-test.svg
@@ -0,0 +1,93 @@
+<svg xmlns='http://www.w3.org/2000/svg' width='300.38627894289516' height='20' role='img' aria-label='Deployment Status'>
+ <title>Deployment Status</title>
+ <linearGradient id='light' x2='0' y2='100%'>
+ <stop offset='0' stop-color='#fff' stop-opacity='.5'/>
+ <stop offset='.1' stop-color='#fff' stop-opacity='.15'/>
+ <stop offset='.9' stop-color='#000' stop-opacity='.15'/>
+ <stop offset='1' stop-color='#000' stop-opacity='.5'/>
+ </linearGradient>
+ <linearGradient id='left-light' x2='100%' y2='0'>
+ <stop offset='0' stop-color='#fff' stop-opacity='.3'/>
+ <stop offset='.5' stop-color='#fff' stop-opacity='.1'/>
+ <stop offset='1' stop-color='#fff' stop-opacity='.0'/>
+ </linearGradient>
+ <linearGradient id='right-shadow' x2='100%' y2='0'>
+ <stop offset='0' stop-color='#000' stop-opacity='.0'/>
+ <stop offset='.5' stop-color='#000' stop-opacity='.1'/>
+ <stop offset='1' stop-color='#000' stop-opacity='.3'/>
+ </linearGradient>
+ <linearGradient id='shadow' x2='100%' y2='0'>
+ <stop offset='0' stop-color='#222' stop-opacity='.3'/>
+ <stop offset='.625' stop-color='#555' stop-opacity='.3'/>
+ <stop offset='.9' stop-color='#555' stop-opacity='.05'/>
+ <stop offset='1' stop-color='#555' stop-opacity='.0'/>
+ </linearGradient>
+ <linearGradient id='shade' x2='100%' y2='0'>
+ <stop offset='0' stop-color='#000' stop-opacity='.20'/>
+ <stop offset='0.05' stop-color='#000' stop-opacity='.10'/>
+ <stop offset='1' stop-color='#000' stop-opacity='.0'/>
+ </linearGradient>
+ <linearGradient id='run-on-failure' x1='40%' x2='80%' y2='0%'>
+ <stop offset='0' stop-color='#ab83ff' />
+ <stop offset='1' stop-color='#bf103c' />
+ <animate attributeName='x1' values='-110%;150%;20%;-110%' dur='6s' repeatCount='indefinite' />
+ <animate attributeName='x2' values='-10%;250%;120%;-10%' dur='6s' repeatCount='indefinite' />
+ </linearGradient>
+ <linearGradient id='run-on-warning' x1='40%' x2='80%' y2='0%'>
+ <stop offset='0' stop-color='#ab83ff' />
+ <stop offset='1' stop-color='#bd890b' />
+ <animate attributeName='x1' values='-110%;150%;20%;-110%' dur='6s' repeatCount='indefinite' />
+ <animate attributeName='x2' values='-10%;250%;120%;-10%' dur='6s' repeatCount='indefinite' />
+ </linearGradient>
+ <linearGradient id='run-on-success' x1='40%' x2='80%' y2='0%'>
+ <stop offset='0' stop-color='#ab83ff' />
+ <stop offset='1' stop-color='#00f844' />
+ <animate attributeName='x1' values='-110%;150%;20%;-110%' dur='6s' repeatCount='indefinite' />
+ <animate attributeName='x2' values='-10%;250%;120%;-10%' dur='6s' repeatCount='indefinite' />
+ </linearGradient>
+ <clipPath id='rounded'>
+ <rect width='300.38627894289516' height='20' rx='3' fill='#fff'/>
+ </clipPath>
+ <g clip-path='url(#rounded)'>
+ <rect x='297.7936599677133' rx='3' width='8' height='20' fill='url(#shadow)'/>
+ <rect x='267.7546449838567' rx='3' width='36.03901498385664' height='20' fill='#00f844'/>
+ <rect x='267.7546449838567' rx='3' width='36.03901498385664' height='20' fill='url(#shade)'/>
+ <rect x='271.5979829411765' rx='3' width='8' height='20' fill='url(#shadow)'/>
+ <rect x='237.71563000000003' rx='3' width='39.88235294117647' height='20' fill='#00f844'/>
+ <rect x='237.71563000000003' rx='3' width='39.88235294117647' height='20' fill='url(#shade)'/>
+ <rect x='237.71563000000003' rx='3' width='8' height='20' fill='url(#shadow)'/>
+ <rect x='163.18729000000002' rx='3' width='80.52834000000001' height='20' fill='url(#run-on-warning)'/>
+ <rect x='163.18729000000002' rx='3' width='80.52834000000001' height='20' fill='url(#shade)'/>
+ <rect width='169.18729000000002' height='20' fill='#404040'/>
+ <rect x='-6.0' rx='3' width='175.18729000000002' height='20' fill='url(#shade)'/>
+ <rect width='2' height='20' fill='url(#left-light)'/>
+ <rect x='298.38627894289516' width='2' height='20' fill='url(#right-shadow)'/>
+ <rect width='300.38627894289516' height='20' fill='url(#light)'/>
+ </g>
+ <g fill='#fff' text-anchor='middle' font-family='Verdana,Geneva,DejaVu Sans,sans-serif' text-rendering='geometricPrecision' font-size='11'>
+ <svg x='6.5' y='3.0' width='16.0' height='16.0' viewBox='0 0 150 150'>
+ <polygon fill='#402a14' fill-opacity='0.5' points='84.84 10 34.1 44.46 34.1 103.78 84.84 68.02 135.57 103.78 135.57 44.46 84.84 10'/>
+ <polygon fill='#402a14' fill-opacity='0.5' points='84.84 68.02 84.84 10 135.57 44.46 135.57 103.78 84.84 68.02'/>
+ <polygon fill='#061a29' fill-opacity='0.5' points='65.07 81.99 14.34 46.22 14.34 105.54 65.07 140 115.81 105.54 115.81 46.22 65.07 81.99'/>
+ <polygon fill='#061a29' fill-opacity='0.5' points='65.07 81.99 65.07 140 14.34 105.54 14.34 46.22 65.07 81.99'/>
+ </svg>
+ <svg x='6.0' y='2.0' width='16.0' height='16.0' viewBox='0 0 150 150'>
+ <linearGradient id='yellow-shaded' x1='91.17' y1='44.83' x2='136.24' y2='73.4' gradientUnits='userSpaceOnUse'>
+ <stop offset='0.01' stop-color='#c6783e'/>
+ <stop offset='0.54' stop-color='#ff9750'/>
+ </linearGradient>
+ <linearGradient id='blue-shaded' x1='60.71' y1='104.56' x2='-15.54' y2='63' gradientUnits='userSpaceOnUse'>
+ <stop offset='0' stop-color='#005a8e'/>
+ <stop offset='0.54' stop-color='#1a7db6'/>
+ </linearGradient>
+ <polygon fill='#ff9d4b' points='84.84 10 34.1 44.46 34.1 103.78 84.84 68.02 135.57 103.78 135.57 44.46 84.84 10'/>
+ <polygon fill='url(#yellow-shaded)' points='84.84 68.02 84.84 10 135.57 44.46 135.57 103.78 84.84 68.02'/>
+ <polygon fill='#1a7db6' points='65.07 81.99 14.34 46.22 14.34 105.54 65.07 140 115.81 105.54 115.81 46.22 65.07 81.99'/>
+ <polygon fill='url(#blue-shaded)' points='65.07 81.99 65.07 140 14.34 105.54 14.34 46.22 65.07 81.99'/>
+ </svg>
+ <text font-size='11' x='96.09364500000001' y='15' fill='#000' fill-opacity='.4' textLength='135.18729000000002'>tenant.application.default</text>
+ <text font-size='11' x='95.59364500000001' y='14' fill='#fff' textLength='135.18729000000002'>tenant.application.default</text>
+ <text font-size='11' x='206.95146000000003' y='15' fill='#000' fill-opacity='.4' textLength='62.52834000000001'>system-test</text>
+ <text font-size='11' x='206.45146000000003' y='14' fill='#fff' textLength='62.52834000000001'>system-test</text>
+ </g>
+</svg>
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
index 15c7dbf73ab..9024d7c8e7e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
@@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
@@ -76,7 +77,7 @@ public class SignatureFilterTest {
ImmutableBiMap.of(),
TenantInfo.empty(),
List.of(),
- Optional.empty()));
+ new ArchiveAccess()));
tester.curator().writeApplication(new Application(appId, tester.clock().instant()));
}
@@ -122,7 +123,7 @@ public class SignatureFilterTest {
ImmutableBiMap.of(publicKey, () -> "user"),
TenantInfo.empty(),
List.of(),
- Optional.empty()));
+ new ArchiveAccess()));
verifySecurityContext(requestOf(signer.signed(request.copy(), Method.POST, () -> new ByteArrayInputStream(hiBytes)), hiBytes),
new SecurityContext(new SimplePrincipal("user"),
Set.of(Role.reader(id.tenant()),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
index 5c210616cb1..15e7a804143 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
@@ -95,14 +95,18 @@ public class OsApiTest extends ControllerContainerTest {
assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": \"7.5.1\", \"cloud\": \"cloud1\", \"force\": true, \"upgradeBudget\": \"PT0S\"}", Request.Method.PATCH),
"{\"message\":\"Set target OS version for cloud 'cloud1' to 7.5.1 with upgrade budget PT0S\"}", 200);
+ // Clear target for a given cloud
+ assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": null, \"cloud\": \"cloud2\"}", Request.Method.PATCH),
+ "{\"message\":\"Cleared target OS version for cloud 'cloud2'\"}", 200);
+
// Error: Missing fields
assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": \"7.6\"}", Request.Method.PATCH),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Fields 'version', 'cloud' and 'upgradeBudget' are required\"}", 400);
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Field 'cloud' is required\"}", 400);
assertResponse(new Request("http://localhost:8080/os/v1/", "{\"cloud\": \"cloud1\"}", Request.Method.PATCH),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Fields 'version', 'cloud' and 'upgradeBudget' are required\"}", 400);
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Field 'version' is required\"}", 400);
// Error: Invalid versions
- assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": null, \"cloud\": \"cloud1\", \"upgradeBudget\": \"PT0S\"}", Request.Method.PATCH),
+ assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": \"0.0.0\", \"cloud\": \"cloud1\", \"upgradeBudget\": \"PT0S\"}", Request.Method.PATCH),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Invalid version '0.0.0'\"}", 400);
assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": \"foo\", \"cloud\": \"cloud1\", \"upgradeBudget\": \"PT0S\"}", Request.Method.PATCH),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Invalid version 'foo': For input string: \\\"foo\\\"\"}", 400);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json
index 3237e99783d..f980f9231f3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-keys.json
@@ -24,6 +24,7 @@
"budgetUsed": 0.0,
"clusterSize": 5
},
+ "archiveAccess": { },
"applications": [
{
"tenant": "my-tenant",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json
index 0cc8ba2cd9e..1152033791b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-with-secrets.json
@@ -32,6 +32,7 @@
"budgetUsed": 0.0,
"clusterSize": 5
},
+ "archiveAccess": { },
"applications": [
{
"tenant": "my-tenant",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json
index 3153c6e218a..631346181a1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json
@@ -15,6 +15,7 @@
"budgetUsed": 0.0,
"clusterSize": 5
},
+ "archiveAccess": { },
"applications": [ ],
"metaData": {
"createdAtMillis": 1600000000000
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
index 303230b91ad..c0fb9b3d8c7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
@@ -25,14 +25,12 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
-import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
-import com.yahoo.vespa.hosted.controller.maintenance.NameServiceDispatcher;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import org.junit.Test;
@@ -693,22 +691,6 @@ public class RoutingPoliciesTest {
}
@Test
- public void config_server_routing_policy() {
- var tester = new RoutingPoliciesTester();
- var app = SystemApplication.configServer.id();
- RecordName name = RecordName.from("cfg.prod.us-west-1.test.vip");
-
- tester.provisionLoadBalancers(1, app, zone1);
- tester.routingPolicies().refresh(new DeploymentId(app, zone1), DeploymentSpec.empty);
- new NameServiceDispatcher(tester.tester.controller(), Duration.ofSeconds(Integer.MAX_VALUE)).run();
-
- List<Record> records = tester.controllerTester().nameService().findRecords(Record.Type.CNAME, name);
- assertEquals(1, records.size());
- assertEquals(RecordData.from("lb-0--hosted-vespa.zone-config-servers.default--prod.us-west-1."),
- records.get(0).data());
- }
-
- @Test
public void application_endpoint_routing_policy() {
RoutingPoliciesTester tester = new RoutingPoliciesTester();
TenantAndApplicationId application = TenantAndApplicationId.from("tenant1", "app1");
diff --git a/controller-server/src/test/resources/application-packages/changed-deployment-xml.zip b/controller-server/src/test/resources/application-packages/changed-deployment-xml.zip
index e4ec61c50ab..e6482904b22 100644
--- a/controller-server/src/test/resources/application-packages/changed-deployment-xml.zip
+++ b/controller-server/src/test/resources/application-packages/changed-deployment-xml.zip
Binary files differ
diff --git a/controller-server/src/test/resources/application-packages/changed-services-xml.zip b/controller-server/src/test/resources/application-packages/changed-services-xml.zip
index daaa1bd9e3c..e11b1ef162e 100644
--- a/controller-server/src/test/resources/application-packages/changed-services-xml.zip
+++ b/controller-server/src/test/resources/application-packages/changed-services-xml.zip
Binary files differ
diff --git a/controller-server/src/test/resources/application-packages/include-absolute.zip b/controller-server/src/test/resources/application-packages/include-absolute.zip
index 3b30cd8265a..49c99ff5da9 100644
--- a/controller-server/src/test/resources/application-packages/include-absolute.zip
+++ b/controller-server/src/test/resources/application-packages/include-absolute.zip
Binary files differ
diff --git a/controller-server/src/test/resources/application-packages/include-parent.zip b/controller-server/src/test/resources/application-packages/include-parent.zip
index 18c1b0f5e37..8702b512c98 100644
--- a/controller-server/src/test/resources/application-packages/include-parent.zip
+++ b/controller-server/src/test/resources/application-packages/include-parent.zip
Binary files differ
diff --git a/controller-server/src/test/resources/application-packages/original.zip b/controller-server/src/test/resources/application-packages/original.zip
index 3963527a6cd..cabac1999c3 100644
--- a/controller-server/src/test/resources/application-packages/original.zip
+++ b/controller-server/src/test/resources/application-packages/original.zip
Binary files differ
diff --git a/controller-server/src/test/resources/application-packages/similar-deployment-xml.zip b/controller-server/src/test/resources/application-packages/similar-deployment-xml.zip
index 4075ee08ce3..67c38c344c0 100644
--- a/controller-server/src/test/resources/application-packages/similar-deployment-xml.zip
+++ b/controller-server/src/test/resources/application-packages/similar-deployment-xml.zip
Binary files differ
diff --git a/controller-server/src/test/resources/testConfig.json b/controller-server/src/test/resources/testConfig.json
index 5c3d5942001..0ea4b163992 100644
--- a/controller-server/src/test/resources/testConfig.json
+++ b/controller-server/src/test/resources/testConfig.json
@@ -3,6 +3,9 @@
"zone": "test.us-east-1",
"system": "publiccd",
"isCI": true,
+ "platform": "1.2.3",
+ "revision": 321,
+ "deployedAt": 222,
"endpoints": {
"test.us-east-1": [
"https://ai.default.default.global.vespa.oath.cloud/"